diff options
-rw-r--r-- | Makefile | 44 | ||||
-rw-r--r-- | gxp-int.c | 47 | ||||
-rw-r--r-- | gxp-int.h | 20 | ||||
-rw-r--r-- | gxpp.1 | 90 | ||||
-rw-r--r-- | gxpp.c | 351 | ||||
-rw-r--r-- | gxpp.spec | 42 |
6 files changed, 594 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1aa1d9f --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ + +TARGETS := gxpp + +all: $(TARGETS) + +CFLAGS := -Wall -g +gxpp: gxp-int.c +gxpp: CFLAGS += -I/usr/include/libxml2 +gxpp: LOADLIBES := -lxml2 + +install: all + for i in $(TARGETS); do \ + install -D $$i $(DESTDIR)/usr/bin/$$i; \ + install -D $$i.1 $(DESTDIR)/usr/share/man/man1/$$i.1; \ + done + +clean: + $(RM) $(TARGETS) + + +VERSION := $(shell awk '/^Version:/ { print $$2 }' gxpp.spec) + +ifdef DIST + RPMDEFS := -D "dist $(DIST)" +endif + +tarfiles := gxpp.spec Makefile +tarfiles += $(patsubst %,%.c,$(TARGETS)) +tarfiles += gxp-int.c gxp-int.h +tarfiles += $(patsubst %,%.1,$(TARGETS)) + +tarball := gxpp-$(VERSION).tar.bz2 + +$(tarball): $(tarfiles) + -$(RM) $@ + tar cjf $@ $^ + +tarball: $(tarball) + +rpm: $(tarball) $(tarfiles) + rpmbuild -ta $< $(RPMDEFS) + +srpm: $(tarball) $(tarfiles) + rpmbuild -ts $< $(RPMDEFS) diff --git a/gxp-int.c b/gxp-int.c new file mode 100644 index 0000000..c36aa5f --- /dev/null +++ b/gxp-int.c @@ -0,0 +1,47 @@ +/* + * Copyright © 2007 Red Hat, Inc. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions of the + * GNU General Public License v.2. This program is distributed in the hope + * that it will be useful, but WITHOUT ANY WARRANTY expressed or implied, + * including the implied warranties of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat + * trademarks that are incorporated in the source code or documentation are not + * subject to the GNU General Public License and may only be used or replicated + * with the express permission of Red Hat, Inc. + * + * Red Hat Author(s): Nathan Straz <nstraz@redhat.com> + */ + +#include <libxml/tree.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +xmlXPathContextPtr +generate_context(xmlDocPtr doc, const char *defprefix) +{ + xmlXPathContextPtr ctx; + xmlNodePtr root; + xmlNsPtr ns; + + root = xmlDocGetRootElement(doc); + ctx = xmlXPathNewContext(doc); + + for (ns = root->ns; ns; ns=ns->next) { + if (ns->href) { + if (ns->prefix) { + xmlXPathRegisterNs(ctx, ns->prefix, ns->href); + } else if (defprefix) { + xmlXPathRegisterNs(ctx, (xmlChar *)defprefix, ns->href); + } else { + fprintf(stderr, "WARNING: document uses default namespace, use -p option to specify a prefix\n"); + } + } + } + + return ctx; +} diff --git a/gxp-int.h b/gxp-int.h new file mode 100644 index 0000000..a9b1096 --- /dev/null +++ b/gxp-int.h @@ -0,0 +1,20 @@ +/* + * Copyright © 2007 Red Hat, Inc. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions of the + * GNU General Public License v.2. This program is distributed in the hope + * that it will be useful, but WITHOUT ANY WARRANTY expressed or implied, + * including the implied warranties of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat + * trademarks that are incorporated in the source code or documentation are not + * subject to the GNU General Public License and may only be used or replicated + * with the express permission of Red Hat, Inc. + * + * Red Hat Author(s): Nathan Straz <nstraz@redhat.com> + */ + +xmlXPathContextPtr generate_context(xmlDocPtr doc, const char *defprefix); @@ -0,0 +1,90 @@ +.TH GXPP 1 "3 July 2008" +.SH NAME +gxpp \- print elements matching an XPath expression +.SH SYNOPSIS +\fBgxpp\fR [\fIoptions\fR] \fIXPATH \fR[\fIFILE\fR...] +.SH DESCRIPTION + +.B Gxpp +prints the results of an XPath expression when applied to the +specified XML document or standard input. + +.SH OPTIONS + +.TP +.B \-c +Print the count of the expression matches instead of printing the +matches. + +.TP +.B \-H +Print the filename for each match. + +.TP +.B \-h +Suppress the printing of filenames when multiple files are specified. + +.TP +.B \-L +Suppress normal output; instead print the name of each input document for which the XPath expression does not match. + +.TP +.B \-l +Suppress normal output; instead print the name of each input document for which the XPath expression matches. + +.TP +.BI \-p " PRE" +The prefix to use in the XPath expression to match the default XML +name space in the document. XPath 1.0 does not define a way to match +the default name space when an XML document contains XML name spaces. +By specifying a default name space prefix here, you can match on the +default prefix in the document. + +.TP +.B \-q +Exit with the normal exit status, but don't generate any output. + +.TP +.B \-x +Print the XML sub-tree(s) which match the XPath expression instead of +the contents of the XML elements. + +.SH EXAMPLES +.nf +.B Example document +<A> + <B name="foo"> + <C>C of foo</C> + </B> + <B name="bar"> + <C>C of bar</C> + </B> + <B name="baz"> + <C>C of baz</C> + </B> +</A> + +.B # gxpp /A/B/@name example.xml +foo +bar +baz + +.B # gxpp -c /A/B/@name example.xml +3 + +.B # gxpp -x "/A/B[@name='baz']" example.xml +<B name="baz"> + <C>C of baz</C> +</B> + +.B # gxpp "/A/B[@name='bar']/C" example.xml +C of bar + +.B # gxpp //C example.xml +C of foo +C of bar +C of baz + +.fi +.SH "SEE ALSO" +xpm(1), grep(1), http://www.w3.org/TR/xpath @@ -0,0 +1,351 @@ +/* + * gxpp.c -- Simple XPath Util + * + * Print the string result of the XPath query to stdout. + * Optionaly print only the number of matches. + * + * Copyright © 2006-20078 Red Hat, Inc. All rights reserved. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions of the + * GNU General Public License v.2. This program is distributed in the hope + * that it will be useful, but WITHOUT ANY WARRANTY expressed or implied, + * including the implied warranties of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat + * trademarks that are incorporated in the source code or documentation are not + * subject to the GNU General Public License and may only be used or replicated + * with the express permission of Red Hat, Inc. + * + * Red Hat Author(s): Dean Jansa <djansa@redhat.com> + * Nathan Straz <nstraz@redhat.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <ctype.h> +#include <math.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> +#include <libxml/xinclude.h> + +#include "gxp-int.h" + +static int gxpp(char *xfile, const char *xpath_query, const char *prefix); +static int xpath(xmlDocPtr doc, xmlXPathContextPtr ctx, const char *xpathstr, + const char *xfile); +static int xpath_nodedump(xmlXPathContextPtr ctx, const char *xpathstr, + const char *xfile); +static void usage(const char *pname); + +static struct { + unsigned countonly : 1; /* -c */ + unsigned addfileprefix : 1; /* -H or multi file args */ + unsigned omitfileprefix : 1; /* -h with multi file args */ + unsigned filenomatch : 1; /* -L */ + unsigned filematch : 1; /* -l */ + unsigned quiet : 1; /* -q */ + unsigned xmlnodedump : 1; /* -x */ +} gxpp_opts; + + + +int +main(int argc, char **argv) +{ + char *xpath_query; + char *prefix; + int retval; + int c; + + gxpp_opts.countonly = 0; + gxpp_opts.addfileprefix = 0; + gxpp_opts.omitfileprefix = 0; + gxpp_opts.filenomatch = 0; + gxpp_opts.filematch = 0; + gxpp_opts.quiet = 0; + gxpp_opts.xmlnodedump = 0; + xpath_query = NULL; + prefix = NULL; + + while ((c = getopt(argc, argv, "+cHhLlqxp:")) != -1) { + switch (c) { + case 'c': + gxpp_opts.countonly = 1; + break; + case 'H': + gxpp_opts.addfileprefix = 1; + gxpp_opts.omitfileprefix = 0; + break; + case 'h': + gxpp_opts.addfileprefix = 0; + gxpp_opts.omitfileprefix = 1; + break; + case 'L': + gxpp_opts.filenomatch = 1; + gxpp_opts.filematch = 0; + gxpp_opts.quiet = 1; + break; + case 'l': + gxpp_opts.filenomatch = 0; + gxpp_opts.filematch = 1; + gxpp_opts.quiet = 1; + break; + case 'p': + prefix = strdup(optarg); + break; + case 'q': + gxpp_opts.quiet = 1; + break; + case 'x': + gxpp_opts.xmlnodedump = 1; + break; + case '?': + usage(argv[0]); + exit(2); + break; + } + } + + xpath_query = argv[optind]; + if (!xpath_query) { + fprintf(stderr, "Missing XPath Query\n"); + usage(argv[0]); + exit(2); + } + + optind++; + if (!argv[optind]) { + /* No files given, use stdin */ + retval = gxpp("-", xpath_query, prefix); + } else { + /* Rest of the args are the files to search */ + if (((argc - optind) > 1) + && (!gxpp_opts.omitfileprefix)) gxpp_opts.addfileprefix = 1; + + for (c = optind; c < argc; c++) { + retval = gxpp(argv[c], xpath_query, prefix); + if (retval < 0) { + break; + } + if (gxpp_opts.filematch && retval > 0) printf("%s\n", argv[c]); + if (gxpp_opts.filenomatch && !retval) printf("%s\n", argv[c]); + } + } + + if (retval == -1) { + retval = 2; + } else { + retval = !retval; + } + + exit(retval); +} + + +/* + * gxpp -- + * + * Open file, parse doc and run the xpath query. + * + * Returns: + * Number of matches on success, less than zero on failure. + * No matches is not a failure, return of 0 is just no matches found. + */ + +static int +gxpp(char *xfile, const char *xpath_query, const char *prefix) +{ + xmlDocPtr doc; + xmlXPathContextPtr ctx; + char *cp; + int retval; + + if (!(doc = xmlParseFile(xfile))) { + fprintf(stderr, "XML Parse failed\n"); + return -1; + } + + ctx = generate_context(doc, prefix); + + /* FIXME: do some error checking */ + xmlXIncludeProcess(doc); + if (gxpp_opts.xmlnodedump) { + retval = xpath_nodedump(ctx, xpath_query, xfile); + } else { + if (gxpp_opts.countonly) { + cp = malloc(strlen(xpath_query) + strlen("count()") + 1); + sprintf(cp, "count(%s)", xpath_query); + xpath_query = cp; + } + + retval = xpath(doc, ctx, xpath_query, xfile); + } + + xmlXPathFreeContext(ctx); + xmlFreeDoc(doc); + + return retval; +} + + +/* + * xpath_nodedump -- + * + * Dump all XML nodes which match XPath query to stdout. + * + * Returns: + * Number of nodes which matched XPath query + */ + +static int +xpath_nodedump(xmlXPathContextPtr ctx, const char *xpathstr, const char *xfile) +{ + xmlXPathObjectPtr obj = NULL; + xmlNodePtr node; + xmlBufferPtr xmlbp; + int i; + + obj = xmlXPathEvalExpression((xmlChar *)xpathstr, ctx); + + if ((obj == NULL) || (obj->type != XPATH_NODESET) + || (obj->nodesetval == NULL)) { + return 0; + } + + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + node = xmlXPathNodeSetItem(obj->nodesetval, i); + + xmlbp = xmlBufferCreate(); + xmlNodeDump(xmlbp, NULL, node, 0, 0); + if (!gxpp_opts.quiet) { + if (gxpp_opts.addfileprefix) printf("%s: ", xfile); + printf("%s\n", xmlBufferContent(xmlbp)); + } + xmlBufferFree(xmlbp); + } + + xmlXPathFreeObject(obj); + + return i; +} + + +/* + * xpath -- + * + * Dump all XML nodes string values which match XPath query to stdout. + * + * Returns: + * Number of nodes which matched XPath query. + * -1 on error. + */ + +static int +xpath(xmlDocPtr doc, xmlXPathContextPtr ctx, const char *xpathstr, + const char *xfile) +{ + xmlNodeSetPtr nodeset; + xmlXPathObjectPtr result; + xmlChar *strval; + int matchc = 0; + double num; + int i; + + result = xmlXPathEvalExpression((xmlChar *)xpathstr, ctx); + if (result == NULL) { + return -1; + } + + switch (result->type) { + case XPATH_NODESET: + if (xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + return 0; + } + + nodeset = result->nodesetval; + matchc=nodeset->nodeNr; + for (i=0; i < nodeset->nodeNr; i++) { + strval = xmlNodeListGetString(doc, + nodeset->nodeTab[i]->xmlChildrenNode, 1); + if (strval) { + if (!gxpp_opts.quiet) { + if (gxpp_opts.addfileprefix) printf("%s: ", xfile); + printf("%s\n", strval); + } + xmlFree(strval); + } + } + + break; + + case XPATH_BOOLEAN: + matchc=1; + if (!gxpp_opts.quiet) { + if (gxpp_opts.addfileprefix) printf("%s: ", xfile); + printf("%d\n", xmlXPathCastToBoolean(result)); + } + break; + + case XPATH_NUMBER: + matchc=1; + num = xmlXPathCastToNumber(result); + if (!gxpp_opts.quiet) { + if (gxpp_opts.addfileprefix) printf("%s: ", xfile); + if ((ceil(num) - num) == 0) { + printf("%lld\n", (long long int)num); + } else { + printf("%f\n", num); + } + } + break; + + case XPATH_STRING: + matchc=1; + /* + * xmlXPathCastToString(result) -- return the string value of + * the object, NULL in case of error. A new string is allocated + * only if needed (result isn't a string object). We are only + * in this case if result is a string obj, no need to free() + * the return value. + */ + if (!gxpp_opts.quiet) { + if (gxpp_opts.addfileprefix) printf("%s: ", xfile); + printf("%s\n", xmlXPathCastToString(result)); + } + break; + + default: + matchc=0; + break; + } + + xmlXPathFreeObject(result); + return matchc; +} + + +static void +usage(const char *pname) +{ + fprintf(stderr,"Usage: %s [OPTION]... XPATH [FILE]...\n", pname); + fprintf(stderr,"Options:\n" + "-c Print count of matches in file only\n" + "-H Print filename for each match. \n" + "-h Suppress filenames when multiple files are searched\n" + "-L Print the filename of files without matches\n" + "-l Print the filename of files with matches\n" + "-q Suppress output of matches\n" + "-x Dump nodes which match in XML format\n" + "-p prefix Set prefix for default namespace\n"); + + return; +} diff --git a/gxpp.spec b/gxpp.spec new file mode 100644 index 0000000..5dbe4fa --- /dev/null +++ b/gxpp.spec @@ -0,0 +1,42 @@ +Name: gxpp +Version: 1.0 +Release: 1%{?dist} +Summary: Simple XPath command line tools + +Group: QA +License: GPL +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Source0: gxpp-%{version}.tar.bz2 +BuildRequires: libxml2-devel + + +%description +This is a set of simple but powerful tools to query and manipulate +XML files using XPath expressions. + +%prep +%setup -q -c + + +%build +make %{?_smp_mflags} + + +%install +rm -rf $RPM_BUILD_ROOT +make install DESTDIR=$RPM_BUILD_ROOT + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +/usr/bin/gxpp +%doc %{_mandir}/* + + +%changelog +* Mon Aug 04 2008 1.0-1 +- Initial packaging |