1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2025-01-25 06:03:34 +03:00
libxml2/shell.c
Nick Wellnhofer 1341deac13 xmllint: Move shell to xmllint
Move source code for xmllint shell to shell.c and move it from the
libxml2 library to the xmllint executable.

Also allow shell to run without XPath and debug modules.

Add stubs for old shell API functions in legacy build mode.
2024-06-16 18:47:12 +02:00

1630 lines
49 KiB
C

/*
* shell.c: The xmllint shell
*
* See Copyright for the status of this software.
*
* Daniel Veillard <daniel@veillard.com>
*/
#include "libxml.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_LIBREADLINE
#include <readline/readline.h>
#ifdef HAVE_LIBHISTORY
#include <readline/history.h>
#endif
#endif
#include <libxml/debugXML.h>
#include <libxml/HTMLtree.h>
#include <libxml/parser.h>
#include <libxml/uri.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#ifdef LIBXML_SCHEMAS_ENABLED
#include <libxml/relaxng.h>
#endif
#include "private/shell.h"
/*
* TODO: Improvement/cleanups for the XML shell
* - allow to shell out an editor on a subpart
* - cleanup function registrations (with help) and calling
* - provide registration routines
*/
typedef struct _xmllintShellCtxt xmllintShellCtxt;
typedef xmllintShellCtxt *xmllintShellCtxtPtr;
struct _xmllintShellCtxt {
char *filename;
xmlDocPtr doc;
xmlNodePtr node;
#ifdef LIBXML_XPATH_ENABLED
xmlXPathContextPtr pctxt;
#endif
int loaded;
FILE *output;
};
/**
* xmllintLsCountNode:
* @node: the node to count
*
* Count the children of @node.
*
* Returns the number of children of @node.
*/
static int
xmllintLsCountNode(xmlNodePtr node) {
int ret = 0;
xmlNodePtr list = NULL;
if (node == NULL)
return(0);
switch (node->type) {
case XML_ELEMENT_NODE:
list = node->children;
break;
case XML_DOCUMENT_NODE:
case XML_HTML_DOCUMENT_NODE:
list = ((xmlDocPtr) node)->children;
break;
case XML_ATTRIBUTE_NODE:
list = ((xmlAttrPtr) node)->children;
break;
case XML_TEXT_NODE:
case XML_CDATA_SECTION_NODE:
case XML_PI_NODE:
case XML_COMMENT_NODE:
if (node->content != NULL) {
ret = xmlStrlen(node->content);
}
break;
case XML_ENTITY_REF_NODE:
case XML_DOCUMENT_TYPE_NODE:
case XML_ENTITY_NODE:
case XML_DOCUMENT_FRAG_NODE:
case XML_NOTATION_NODE:
case XML_DTD_NODE:
case XML_ELEMENT_DECL:
case XML_ATTRIBUTE_DECL:
case XML_ENTITY_DECL:
case XML_NAMESPACE_DECL:
case XML_XINCLUDE_START:
case XML_XINCLUDE_END:
ret = 1;
break;
}
for (;list != NULL;ret++)
list = list->next;
return(ret);
}
/**
* xmllintLsOneNode:
* @output: the FILE * for the output
* @node: the node to dump
*
* Dump to @output the type and name of @node.
*/
static void
xmllintLsOneNode(FILE *output, xmlNodePtr node) {
if (output == NULL) return;
if (node == NULL) {
fprintf(output, "NULL\n");
return;
}
switch (node->type) {
case XML_ELEMENT_NODE:
fprintf(output, "-");
break;
case XML_ATTRIBUTE_NODE:
fprintf(output, "a");
break;
case XML_TEXT_NODE:
fprintf(output, "t");
break;
case XML_CDATA_SECTION_NODE:
fprintf(output, "C");
break;
case XML_ENTITY_REF_NODE:
fprintf(output, "e");
break;
case XML_ENTITY_NODE:
fprintf(output, "E");
break;
case XML_PI_NODE:
fprintf(output, "p");
break;
case XML_COMMENT_NODE:
fprintf(output, "c");
break;
case XML_DOCUMENT_NODE:
fprintf(output, "d");
break;
case XML_HTML_DOCUMENT_NODE:
fprintf(output, "h");
break;
case XML_DOCUMENT_TYPE_NODE:
fprintf(output, "T");
break;
case XML_DOCUMENT_FRAG_NODE:
fprintf(output, "F");
break;
case XML_NOTATION_NODE:
fprintf(output, "N");
break;
case XML_NAMESPACE_DECL:
fprintf(output, "n");
break;
default:
fprintf(output, "?");
}
if (node->type != XML_NAMESPACE_DECL) {
if (node->properties != NULL)
fprintf(output, "a");
else
fprintf(output, "-");
if (node->nsDef != NULL)
fprintf(output, "n");
else
fprintf(output, "-");
}
fprintf(output, " %8d ", xmllintLsCountNode(node));
switch (node->type) {
case XML_ELEMENT_NODE:
if (node->name != NULL) {
if ((node->ns != NULL) && (node->ns->prefix != NULL))
fprintf(output, "%s:", node->ns->prefix);
fprintf(output, "%s", (const char *) node->name);
}
break;
case XML_ATTRIBUTE_NODE:
if (node->name != NULL)
fprintf(output, "%s", (const char *) node->name);
break;
case XML_TEXT_NODE:
#ifdef LIBXML_DEBUG_ENABLED
if (node->content != NULL) {
xmlDebugDumpString(output, node->content);
}
#endif
break;
case XML_CDATA_SECTION_NODE:
break;
case XML_ENTITY_REF_NODE:
if (node->name != NULL)
fprintf(output, "%s", (const char *) node->name);
break;
case XML_ENTITY_NODE:
if (node->name != NULL)
fprintf(output, "%s", (const char *) node->name);
break;
case XML_PI_NODE:
if (node->name != NULL)
fprintf(output, "%s", (const char *) node->name);
break;
case XML_COMMENT_NODE:
break;
case XML_DOCUMENT_NODE:
break;
case XML_HTML_DOCUMENT_NODE:
break;
case XML_DOCUMENT_TYPE_NODE:
break;
case XML_DOCUMENT_FRAG_NODE:
break;
case XML_NOTATION_NODE:
break;
case XML_NAMESPACE_DECL: {
xmlNsPtr ns = (xmlNsPtr) node;
if (ns->prefix == NULL)
fprintf(output, "default -> %s", (char *)ns->href);
else
fprintf(output, "%s -> %s", (char *)ns->prefix,
(char *)ns->href);
break;
}
default:
if (node->name != NULL)
fprintf(output, "%s", (const char *) node->name);
}
fprintf(output, "\n");
}
/**
* xmllintShellList:
* @ctxt: the shell context
* @arg: unused
* @node: a node
* @node2: unused
*
* Implements the XML shell function "ls"
* Does an Unix like listing of the given node (like a directory)
*
* Returns 0
*/
static int
xmllintShellList(xmllintShellCtxtPtr ctxt,
char *arg ATTRIBUTE_UNUSED, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlNodePtr cur;
if (!ctxt)
return (0);
if (node == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
cur = ((xmlDocPtr) node)->children;
} else if (node->type == XML_NAMESPACE_DECL) {
xmllintLsOneNode(ctxt->output, node);
return (0);
} else if (node->children != NULL) {
cur = node->children;
} else {
xmllintLsOneNode(ctxt->output, node);
return (0);
}
while (cur != NULL) {
xmllintLsOneNode(ctxt->output, cur);
cur = cur->next;
}
return (0);
}
/**
* xmllintShellBase:
* @ctxt: the shell context
* @arg: unused
* @node: a node
* @node2: unused
*
* Implements the XML shell function "base"
* dumps the current XML base of the node
*
* Returns 0
*/
static int
xmllintShellBase(xmllintShellCtxtPtr ctxt,
char *arg ATTRIBUTE_UNUSED, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlChar *base;
if (!ctxt)
return 0;
if (node == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
base = xmlNodeGetBase(node->doc, node);
if (base == NULL) {
fprintf(ctxt->output, " No base found !!!\n");
} else {
fprintf(ctxt->output, "%s\n", base);
xmlFree(base);
}
return (0);
}
/**
* xmllintShellSetBase:
* @ctxt: the shell context
* @arg: the new base
* @node: a node
* @node2: unused
*
* Implements the XML shell function "setbase"
* change the current XML base of the node
*
* Returns 0
*/
static int
xmllintShellSetBase(xmllintShellCtxtPtr ctxt ATTRIBUTE_UNUSED,
char *arg ATTRIBUTE_UNUSED, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlNodeSetBase(node, (xmlChar*) arg);
return (0);
}
#ifdef LIBXML_XPATH_ENABLED
/**
* xmllintShellRegisterNamespace:
* @ctxt: the shell context
* @arg: a string in prefix=nsuri format
* @node: unused
* @node2: unused
*
* Implements the XML shell function "setns"
* register/unregister a prefix=namespace pair
* on the XPath context
*
* Returns 0 on success and a negative value otherwise.
*/
static int
xmllintShellRegisterNamespace(xmllintShellCtxtPtr ctxt, char *arg,
xmlNodePtr node ATTRIBUTE_UNUSED, xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlChar* nsListDup;
xmlChar* prefix;
xmlChar* href;
xmlChar* next;
nsListDup = xmlStrdup((xmlChar *) arg);
next = nsListDup;
while(next != NULL) {
/* skip spaces */
/*while((*next) == ' ') next++;*/
if((*next) == '\0') break;
/* find prefix */
prefix = next;
next = (xmlChar*)xmlStrchr(next, '=');
if(next == NULL) {
fprintf(ctxt->output, "setns: prefix=[nsuri] required\n");
xmlFree(nsListDup);
return(-1);
}
*(next++) = '\0';
/* find href */
href = next;
next = (xmlChar*)xmlStrchr(next, ' ');
if(next != NULL) {
*(next++) = '\0';
}
/* do register namespace */
if(xmlXPathRegisterNs(ctxt->pctxt, prefix, href) != 0) {
fprintf(ctxt->output,"Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", prefix, href);
xmlFree(nsListDup);
return(-1);
}
}
xmlFree(nsListDup);
return(0);
}
/**
* xmllintShellRegisterRootNamespaces:
* @ctxt: the shell context
* @arg: unused
* @node: the root element
* @node2: unused
*
* Implements the XML shell function "setrootns"
* which registers all namespaces declarations found on the root element.
*
* Returns 0 on success and a negative value otherwise.
*/
static int
xmllintShellRegisterRootNamespaces(xmllintShellCtxtPtr ctxt, char *arg ATTRIBUTE_UNUSED,
xmlNodePtr root, xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlNsPtr ns;
if ((root == NULL) || (root->type != XML_ELEMENT_NODE) ||
(root->nsDef == NULL) || (ctxt == NULL) || (ctxt->pctxt == NULL))
return(-1);
ns = root->nsDef;
while (ns != NULL) {
if (ns->prefix == NULL)
xmlXPathRegisterNs(ctxt->pctxt, BAD_CAST "defaultns", ns->href);
else
xmlXPathRegisterNs(ctxt->pctxt, ns->prefix, ns->href);
ns = ns->next;
}
return(0);
}
#endif
/**
* xmllintShellGrep:
* @ctxt: the shell context
* @arg: the string or regular expression to find
* @node: a node
* @node2: unused
*
* Implements the XML shell function "grep"
* dumps information about the node (namespace, attributes, content).
*
* Returns 0
*/
static int
xmllintShellGrep(xmllintShellCtxtPtr ctxt ATTRIBUTE_UNUSED,
char *arg, xmlNodePtr node, xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
if (!ctxt)
return (0);
if (node == NULL)
return (0);
if (arg == NULL)
return (0);
#ifdef LIBXML_REGEXP_ENABLED
if ((xmlStrchr((xmlChar *) arg, '?')) ||
(xmlStrchr((xmlChar *) arg, '*')) ||
(xmlStrchr((xmlChar *) arg, '.')) ||
(xmlStrchr((xmlChar *) arg, '['))) {
}
#endif
while (node != NULL) {
if (node->type == XML_COMMENT_NODE) {
if (xmlStrstr(node->content, (xmlChar *) arg)) {
fprintf(ctxt->output, "%s : ", xmlGetNodePath(node));
xmllintShellList(ctxt, NULL, node, NULL);
}
} else if (node->type == XML_TEXT_NODE) {
if (xmlStrstr(node->content, (xmlChar *) arg)) {
fprintf(ctxt->output, "%s : ", xmlGetNodePath(node->parent));
xmllintShellList(ctxt, NULL, node->parent, NULL);
}
}
/*
* Browse the full subtree, deep first
*/
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
node = ((xmlDocPtr) node)->children;
} else if ((node->children != NULL)
&& (node->type != XML_ENTITY_REF_NODE)) {
/* deep first */
node = node->children;
} else if (node->next != NULL) {
/* then siblings */
node = node->next;
} else {
/* go up to parents->next if needed */
while (node != NULL) {
if (node->parent != NULL) {
node = node->parent;
}
if (node->next != NULL) {
node = node->next;
break;
}
if (node->parent == NULL) {
node = NULL;
break;
}
}
}
}
return (0);
}
/**
* xmllintShellDir:
* @ctxt: the shell context
* @arg: unused
* @node: a node
* @node2: unused
*
* Implements the XML shell function "dir"
* dumps information about the node (namespace, attributes, content).
*
* Returns 0
*/
static int
xmllintShellDir(xmllintShellCtxtPtr ctxt ATTRIBUTE_UNUSED,
char *arg ATTRIBUTE_UNUSED, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
if (!ctxt)
return (0);
if (node == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
#ifdef LIBXML_DEBUG_ENABLED
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
xmlDebugDumpDocumentHead(ctxt->output, (xmlDocPtr) node);
} else if (node->type == XML_ATTRIBUTE_NODE) {
xmlDebugDumpAttr(ctxt->output, (xmlAttrPtr) node, 0);
} else {
xmlDebugDumpOneNode(ctxt->output, node, 0);
}
#endif
return (0);
}
/**
* xmllintShellSetContent:
* @ctxt: the shell context
* @value: the content as a string
* @node: a node
* @node2: unused
*
* Implements the XML shell function "dir"
* dumps information about the node (namespace, attributes, content).
*
* Returns 0
*/
static int
xmllintShellSetContent(xmllintShellCtxtPtr ctxt ATTRIBUTE_UNUSED,
char *value, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlNodePtr results;
xmlParserErrors ret;
if (!ctxt)
return (0);
if (node == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
if (value == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
ret = xmlParseInNodeContext(node, value, strlen(value), 0, &results);
if (ret == XML_ERR_OK) {
if (node->children != NULL) {
xmlFreeNodeList(node->children);
node->children = NULL;
node->last = NULL;
}
xmlAddChildList(node, results);
} else {
fprintf(ctxt->output, "failed to parse content\n");
}
return (0);
}
#if defined(LIBXML_VALID_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED)
static void
xmllintShellPrintf(void *ctx, const char *msg, ...) {
xmllintShellCtxtPtr sctxt = ctx;
va_list ap;
va_start(ap, msg);
vfprintf(sctxt->output, msg, ap);
va_end(ap);
}
#endif /* defined(LIBXML_VALID_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) */
#ifdef LIBXML_SCHEMAS_ENABLED
/**
* xmllintShellRNGValidate:
* @ctxt: the shell context
* @schemas: the path to the Relax-NG schemas
* @node: a node
* @node2: unused
*
* Implements the XML shell function "relaxng"
* validating the instance against a Relax-NG schemas
*
* Returns 0
*/
static int
xmllintShellRNGValidate(xmllintShellCtxtPtr sctxt, char *schemas,
xmlNodePtr node ATTRIBUTE_UNUSED,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlRelaxNGPtr relaxngschemas;
xmlRelaxNGParserCtxtPtr ctxt;
xmlRelaxNGValidCtxtPtr vctxt;
int ret;
ctxt = xmlRelaxNGNewParserCtxt(schemas);
xmlRelaxNGSetParserErrors(ctxt, xmllintShellPrintf, xmllintShellPrintf, sctxt);
relaxngschemas = xmlRelaxNGParse(ctxt);
xmlRelaxNGFreeParserCtxt(ctxt);
if (relaxngschemas == NULL) {
fprintf(sctxt->output,
"Relax-NG schema %s failed to compile\n", schemas);
return(-1);
}
vctxt = xmlRelaxNGNewValidCtxt(relaxngschemas);
xmlRelaxNGSetValidErrors(vctxt, xmllintShellPrintf, xmllintShellPrintf, sctxt);
ret = xmlRelaxNGValidateDoc(vctxt, sctxt->doc);
if (ret == 0) {
fprintf(sctxt->output, "%s validates\n", sctxt->filename);
} else if (ret > 0) {
fprintf(sctxt->output, "%s fails to validate\n", sctxt->filename);
} else {
fprintf(sctxt->output, "%s validation generated an internal error\n",
sctxt->filename);
}
xmlRelaxNGFreeValidCtxt(vctxt);
if (relaxngschemas != NULL)
xmlRelaxNGFree(relaxngschemas);
return(0);
}
#endif
#ifdef LIBXML_OUTPUT_ENABLED
/**
* xmllintShellCat:
* @ctxt: the shell context
* @arg: unused
* @node: a node
* @node2: unused
*
* Implements the XML shell function "cat"
* dumps the serialization node content (XML or HTML).
*
* Returns 0
*/
static int
xmllintShellCat(xmllintShellCtxtPtr ctxt, char *arg ATTRIBUTE_UNUSED,
xmlNodePtr node, xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
if (!ctxt)
return (0);
if (node == NULL) {
fprintf(ctxt->output, "NULL\n");
return (0);
}
if (ctxt->doc->type == XML_HTML_DOCUMENT_NODE) {
#ifdef LIBXML_HTML_ENABLED
if (node->type == XML_HTML_DOCUMENT_NODE)
htmlDocDump(ctxt->output, (htmlDocPtr) node);
else
htmlNodeDumpFile(ctxt->output, ctxt->doc, node);
#else
if (node->type == XML_DOCUMENT_NODE)
xmlDocDump(ctxt->output, (xmlDocPtr) node);
else
xmlElemDump(ctxt->output, ctxt->doc, node);
#endif /* LIBXML_HTML_ENABLED */
} else {
if (node->type == XML_DOCUMENT_NODE)
xmlDocDump(ctxt->output, (xmlDocPtr) node);
else
xmlElemDump(ctxt->output, ctxt->doc, node);
}
fprintf(ctxt->output, "\n");
return (0);
}
#endif /* LIBXML_OUTPUT_ENABLED */
/**
* xmllintShellLoad:
* @ctxt: the shell context
* @filename: the file name
* @node: unused
* @node2: unused
*
* Implements the XML shell function "load"
* loads a new document specified by the filename
*
* Returns 0 or -1 if loading failed
*/
static int
xmllintShellLoad(xmllintShellCtxtPtr ctxt, char *filename,
xmlNodePtr node ATTRIBUTE_UNUSED,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlDocPtr doc;
int html = 0;
if ((ctxt == NULL) || (filename == NULL)) return(-1);
if (ctxt->doc != NULL)
html = (ctxt->doc->type == XML_HTML_DOCUMENT_NODE);
if (html) {
#ifdef LIBXML_HTML_ENABLED
doc = htmlParseFile(filename, NULL);
#else
fprintf(ctxt->output, "HTML support not compiled in\n");
doc = NULL;
#endif /* LIBXML_HTML_ENABLED */
} else {
doc = xmlReadFile(filename,NULL,0);
}
if (doc != NULL) {
if (ctxt->loaded == 1) {
xmlFreeDoc(ctxt->doc);
}
ctxt->loaded = 1;
#ifdef LIBXML_XPATH_ENABLED
xmlXPathFreeContext(ctxt->pctxt);
#endif /* LIBXML_XPATH_ENABLED */
xmlFree(ctxt->filename);
ctxt->doc = doc;
ctxt->node = (xmlNodePtr) doc;
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt = xmlXPathNewContext(doc);
#endif /* LIBXML_XPATH_ENABLED */
ctxt->filename = (char *) xmlCanonicPath((xmlChar *) filename);
} else
return (-1);
return (0);
}
#ifdef LIBXML_OUTPUT_ENABLED
/**
* xmllintShellWrite:
* @ctxt: the shell context
* @filename: the file name
* @node: a node in the tree
* @node2: unused
*
* Implements the XML shell function "write"
* Write the current node to the filename, it saves the serialization
* of the subtree under the @node specified
*
* Returns 0 or -1 in case of error
*/
static int
xmllintShellWrite(xmllintShellCtxtPtr ctxt, char *filename, xmlNodePtr node,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
if (node == NULL)
return (-1);
if ((filename == NULL) || (filename[0] == 0)) {
return (-1);
}
#ifdef W_OK
if (access((char *) filename, W_OK)) {
fprintf(ctxt->output,
"Cannot write to %s\n", filename);
return (-1);
}
#endif
switch (node->type) {
case XML_DOCUMENT_NODE:
if (xmlSaveFile((char *) filename, ctxt->doc) < -1) {
fprintf(ctxt->output,
"Failed to write to %s\n", filename);
return (-1);
}
break;
case XML_HTML_DOCUMENT_NODE:
#ifdef LIBXML_HTML_ENABLED
if (htmlSaveFile((char *) filename, ctxt->doc) < 0) {
fprintf(ctxt->output,
"Failed to write to %s\n", filename);
return (-1);
}
#else
if (xmlSaveFile((char *) filename, ctxt->doc) < -1) {
fprintf(ctxt->output,
"Failed to write to %s\n", filename);
return (-1);
}
#endif /* LIBXML_HTML_ENABLED */
break;
default:{
FILE *f;
f = fopen((char *) filename, "w");
if (f == NULL) {
fprintf(ctxt->output,
"Failed to write to %s\n", filename);
return (-1);
}
xmlElemDump(f, ctxt->doc, node);
fclose(f);
}
}
return (0);
}
/**
* xmllintShellSave:
* @ctxt: the shell context
* @filename: the file name (optional)
* @node: unused
* @node2: unused
*
* Implements the XML shell function "save"
* Write the current document to the filename, or it's original name
*
* Returns 0 or -1 in case of error
*/
static int
xmllintShellSave(xmllintShellCtxtPtr ctxt, char *filename,
xmlNodePtr node ATTRIBUTE_UNUSED,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
if ((ctxt == NULL) || (ctxt->doc == NULL))
return (-1);
if ((filename == NULL) || (filename[0] == 0))
filename = ctxt->filename;
if (filename == NULL)
return (-1);
#ifdef W_OK
if (access((char *) filename, W_OK)) {
fprintf(ctxt->output,
"Cannot save to %s\n", filename);
return (-1);
}
#endif
switch (ctxt->doc->type) {
case XML_DOCUMENT_NODE:
if (xmlSaveFile((char *) filename, ctxt->doc) < 0) {
fprintf(ctxt->output,
"Failed to save to %s\n", filename);
}
break;
case XML_HTML_DOCUMENT_NODE:
#ifdef LIBXML_HTML_ENABLED
if (htmlSaveFile((char *) filename, ctxt->doc) < 0) {
fprintf(ctxt->output,
"Failed to save to %s\n", filename);
}
#else
if (xmlSaveFile((char *) filename, ctxt->doc) < 0) {
fprintf(ctxt->output,
"Failed to save to %s\n", filename);
}
#endif /* LIBXML_HTML_ENABLED */
break;
default:
fprintf(ctxt->output,
"To save to subparts of a document use the 'write' command\n");
return (-1);
}
return (0);
}
#endif /* LIBXML_OUTPUT_ENABLED */
#ifdef LIBXML_VALID_ENABLED
/**
* xmllintShellValidate:
* @ctxt: the shell context
* @dtd: the DTD URI (optional)
* @node: unused
* @node2: unused
*
* Implements the XML shell function "validate"
* Validate the document, if a DTD path is provided, then the validation
* is done against the given DTD.
*
* Returns 0 or -1 in case of error
*/
static int
xmllintShellValidate(xmllintShellCtxtPtr ctxt, char *dtd,
xmlNodePtr node ATTRIBUTE_UNUSED,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlValidCtxt vctxt;
int res = -1;
if ((ctxt == NULL) || (ctxt->doc == NULL)) return(-1);
memset(&vctxt, 0, sizeof(vctxt));
vctxt.error = xmllintShellPrintf;
vctxt.warning = xmllintShellPrintf;
vctxt.userData = ctxt;
if ((dtd == NULL) || (dtd[0] == 0)) {
res = xmlValidateDocument(&vctxt, ctxt->doc);
} else {
xmlDtdPtr subset;
subset = xmlParseDTD(NULL, (xmlChar *) dtd);
if (subset != NULL) {
res = xmlValidateDtd(&vctxt, ctxt->doc, subset);
xmlFreeDtd(subset);
}
}
return (res);
}
#endif /* LIBXML_VALID_ENABLED */
/**
* xmllintShellDu:
* @ctxt: the shell context
* @arg: unused
* @tree: a node defining a subtree
* @node2: unused
*
* Implements the XML shell function "du"
* show the structure of the subtree under node @tree
* If @tree is null, the command works on the current node.
*
* Returns 0 or -1 in case of error
*/
static int
xmllintShellDu(xmllintShellCtxtPtr ctxt,
char *arg ATTRIBUTE_UNUSED, xmlNodePtr tree,
xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlNodePtr node;
int indent = 0, i;
if (!ctxt)
return (-1);
if (tree == NULL)
return (-1);
node = tree;
while (node != NULL) {
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
fprintf(ctxt->output, "/\n");
} else if (node->type == XML_ELEMENT_NODE) {
for (i = 0; i < indent; i++)
fprintf(ctxt->output, " ");
if ((node->ns) && (node->ns->prefix))
fprintf(ctxt->output, "%s:", node->ns->prefix);
fprintf(ctxt->output, "%s\n", node->name);
} else {
}
/*
* Browse the full subtree, deep first
*/
if ((node->type == XML_DOCUMENT_NODE) ||
(node->type == XML_HTML_DOCUMENT_NODE)) {
node = ((xmlDocPtr) node)->children;
} else if ((node->children != NULL)
&& (node->type != XML_ENTITY_REF_NODE)) {
/* deep first */
node = node->children;
indent++;
} else if ((node != tree) && (node->next != NULL)) {
/* then siblings */
node = node->next;
} else if (node != tree) {
/* go up to parents->next if needed */
while (node != tree) {
if (node->parent != NULL) {
node = node->parent;
indent--;
}
if ((node != tree) && (node->next != NULL)) {
node = node->next;
break;
}
if (node->parent == NULL) {
node = NULL;
break;
}
if (node == tree) {
node = NULL;
break;
}
}
/* exit condition */
if (node == tree)
node = NULL;
} else
node = NULL;
}
return (0);
}
/**
* xmllintShellPwd:
* @ctxt: the shell context
* @buffer: the output buffer
* @node: a node
* @node2: unused
*
* Implements the XML shell function "pwd"
* Show the full path from the root to the node, if needed building
* thumblers when similar elements exists at a given ancestor level.
* The output is compatible with XPath commands.
*
* Returns 0 or -1 in case of error
*/
static int
xmllintShellPwd(xmllintShellCtxtPtr ctxt ATTRIBUTE_UNUSED, char *buffer,
xmlNodePtr node, xmlNodePtr node2 ATTRIBUTE_UNUSED)
{
xmlChar *path;
if ((node == NULL) || (buffer == NULL))
return (-1);
path = xmlGetNodePath(node);
if (path == NULL)
return (-1);
/*
* This test prevents buffer overflow, because this routine
* is only called by xmllintShell, in which the second argument is
* 500 chars long.
* It is a dirty hack before a cleaner solution is found.
* Documentation should mention that the second argument must
* be at least 500 chars long, and could be stripped if too long.
*/
snprintf(buffer, 499, "%s", path);
buffer[499] = '0';
xmlFree(path);
return (0);
}
/**
* xmllintShellReadline:
* @prompt: the prompt value
*
* Read a string
*
* Returns a pointer to it or NULL on EOF the caller is expected to
* free the returned string.
*/
static char *
xmllintShellReadline(char *prompt) {
#ifdef HAVE_LIBREADLINE
char *line_read;
/* Get a line from the user. */
line_read = readline (prompt);
/* If the line has any text in it, save it on the history. */
if (line_read && *line_read)
add_history (line_read);
return (line_read);
#else
char line_read[501];
char *ret;
int len;
if (prompt != NULL)
fprintf(stdout, "%s", prompt);
fflush(stdout);
if (!fgets(line_read, 500, stdin))
return(NULL);
line_read[500] = 0;
len = strlen(line_read);
ret = (char *) malloc(len + 1);
if (ret != NULL) {
memcpy (ret, line_read, len + 1);
}
return(ret);
#endif
}
/**
* xmllintShell:
* @doc: the initial document
* @filename: the output buffer
* @input: the line reading function
* @output: the output FILE*, defaults to stdout if NULL
*
* Implements the XML shell
* This allow to load, validate, view, modify and save a document
* using a environment similar to a UNIX commandline.
*/
void
xmllintShell(xmlDocPtr doc, const char *filename, FILE * output)
{
char prompt[500] = "/ > ";
char *cmdline = NULL, *cur;
char command[100];
char arg[400];
int i;
xmllintShellCtxtPtr ctxt;
#ifdef LIBXML_XPATH_ENABLED
xmlXPathObjectPtr list;
#endif
if (doc == NULL)
return;
if (filename == NULL)
return;
if (output == NULL)
output = stdout;
ctxt = (xmllintShellCtxtPtr) xmlMalloc(sizeof(xmllintShellCtxt));
if (ctxt == NULL)
return;
ctxt->loaded = 0;
ctxt->doc = doc;
ctxt->output = output;
ctxt->filename = (char *) xmlStrdup((xmlChar *) filename);
ctxt->node = (xmlNodePtr) ctxt->doc;
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt = xmlXPathNewContext(ctxt->doc);
if (ctxt->pctxt == NULL) {
xmlFree(ctxt);
return;
}
#endif /* LIBXML_XPATH_ENABLED */
while (1) {
if (ctxt->node == (xmlNodePtr) ctxt->doc)
snprintf(prompt, sizeof(prompt), "%s > ", "/");
else if ((ctxt->node != NULL) && (ctxt->node->name) &&
(ctxt->node->ns) && (ctxt->node->ns->prefix))
snprintf(prompt, sizeof(prompt), "%s:%s > ",
(ctxt->node->ns->prefix), ctxt->node->name);
else if ((ctxt->node != NULL) && (ctxt->node->name))
snprintf(prompt, sizeof(prompt), "%s > ", ctxt->node->name);
else
snprintf(prompt, sizeof(prompt), "? > ");
prompt[sizeof(prompt) - 1] = 0;
/*
* Get a new command line
*/
cmdline = xmllintShellReadline(prompt);
if (cmdline == NULL)
break;
/*
* Parse the command itself
*/
cur = cmdline;
while ((*cur == ' ') || (*cur == '\t'))
cur++;
i = 0;
while ((*cur != ' ') && (*cur != '\t') &&
(*cur != '\n') && (*cur != '\r')) {
if (*cur == 0)
break;
command[i++] = *cur++;
}
command[i] = 0;
if (i == 0)
continue;
/*
* Parse the argument
*/
while ((*cur == ' ') || (*cur == '\t'))
cur++;
i = 0;
while ((*cur != '\n') && (*cur != '\r') && (*cur != 0)) {
if (*cur == 0)
break;
arg[i++] = *cur++;
}
arg[i] = 0;
/*
* start interpreting the command
*/
if (!strcmp(command, "exit"))
break;
if (!strcmp(command, "quit"))
break;
if (!strcmp(command, "bye"))
break;
if (!strcmp(command, "help")) {
fprintf(ctxt->output, "\tbase display XML base of the node\n");
fprintf(ctxt->output, "\tsetbase URI change the XML base of the node\n");
fprintf(ctxt->output, "\tbye leave shell\n");
fprintf(ctxt->output, "\tcat [node] display node or current node\n");
fprintf(ctxt->output, "\tcd [path] change directory to path or to root\n");
fprintf(ctxt->output, "\tdir [path] dumps information about the node (namespace, attributes, content)\n");
fprintf(ctxt->output, "\tdu [path] show the structure of the subtree under path or the current node\n");
fprintf(ctxt->output, "\texit leave shell\n");
fprintf(ctxt->output, "\thelp display this help\n");
fprintf(ctxt->output, "\tfree display memory usage\n");
fprintf(ctxt->output, "\tload [name] load a new document with name\n");
fprintf(ctxt->output, "\tls [path] list contents of path or the current directory\n");
fprintf(ctxt->output, "\tset xml_fragment replace the current node content with the fragment parsed in context\n");
#ifdef LIBXML_XPATH_ENABLED
fprintf(ctxt->output, "\txpath expr evaluate the XPath expression in that context and print the result\n");
fprintf(ctxt->output, "\tsetns nsreg register a namespace to a prefix in the XPath evaluation context\n");
fprintf(ctxt->output, "\t format for nsreg is: prefix=[nsuri] (i.e. prefix= unsets a prefix)\n");
fprintf(ctxt->output, "\tsetrootns register all namespace found on the root element\n");
fprintf(ctxt->output, "\t the default namespace if any uses 'defaultns' prefix\n");
#endif /* LIBXML_XPATH_ENABLED */
fprintf(ctxt->output, "\tpwd display current working directory\n");
fprintf(ctxt->output, "\twhereis display absolute path of [path] or current working directory\n");
fprintf(ctxt->output, "\tquit leave shell\n");
#ifdef LIBXML_OUTPUT_ENABLED
fprintf(ctxt->output, "\tsave [name] save this document to name or the original name\n");
fprintf(ctxt->output, "\twrite [name] write the current node to the filename\n");
#endif /* LIBXML_OUTPUT_ENABLED */
#ifdef LIBXML_VALID_ENABLED
fprintf(ctxt->output, "\tvalidate check the document for errors\n");
#endif /* LIBXML_VALID_ENABLED */
#ifdef LIBXML_SCHEMAS_ENABLED
fprintf(ctxt->output, "\trelaxng rng validate the document against the Relax-NG schemas\n");
#endif
fprintf(ctxt->output, "\tgrep string search for a string in the subtree\n");
#ifdef LIBXML_VALID_ENABLED
} else if (!strcmp(command, "validate")) {
xmllintShellValidate(ctxt, arg, NULL, NULL);
#endif /* LIBXML_VALID_ENABLED */
} else if (!strcmp(command, "load")) {
xmllintShellLoad(ctxt, arg, NULL, NULL);
#ifdef LIBXML_SCHEMAS_ENABLED
} else if (!strcmp(command, "relaxng")) {
xmllintShellRNGValidate(ctxt, arg, NULL, NULL);
#endif
#ifdef LIBXML_OUTPUT_ENABLED
} else if (!strcmp(command, "save")) {
xmllintShellSave(ctxt, arg, NULL, NULL);
} else if (!strcmp(command, "write")) {
if (arg[0] == 0)
fprintf(ctxt->output,
"Write command requires a filename argument\n");
else
xmllintShellWrite(ctxt, arg, ctxt->node, NULL);
#endif /* LIBXML_OUTPUT_ENABLED */
} else if (!strcmp(command, "grep")) {
xmllintShellGrep(ctxt, arg, ctxt->node, NULL);
} else if (!strcmp(command, "pwd")) {
char dir[500];
if (!xmllintShellPwd(ctxt, dir, ctxt->node, NULL))
fprintf(ctxt->output, "%s\n", dir);
} else if (!strcmp(command, "du")) {
if (arg[0] == 0) {
xmllintShellDu(ctxt, NULL, ctxt->node, NULL);
} else {
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt->node = ctxt->node;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
if (list != NULL) {
switch (list->type) {
case XPATH_UNDEFINED:
fprintf(ctxt->output,
"%s: no such node\n", arg);
break;
case XPATH_NODESET:{
int indx;
if (list->nodesetval == NULL)
break;
for (indx = 0;
indx < list->nodesetval->nodeNr;
indx++)
xmllintShellDu(ctxt, NULL,
list->nodesetval->
nodeTab[indx], NULL);
break;
}
case XPATH_BOOLEAN:
fprintf(ctxt->output,
"%s is a Boolean\n", arg);
break;
case XPATH_NUMBER:
fprintf(ctxt->output,
"%s is a number\n", arg);
break;
case XPATH_STRING:
fprintf(ctxt->output,
"%s is a string\n", arg);
break;
case XPATH_USERS:
fprintf(ctxt->output,
"%s is user-defined\n", arg);
break;
case XPATH_XSLT_TREE:
fprintf(ctxt->output,
"%s is an XSLT value tree\n",
arg);
break;
}
xmlXPathFreeObject(list);
} else {
fprintf(ctxt->output,
"%s: no such node\n", arg);
}
ctxt->pctxt->node = NULL;
#endif /* LIBXML_XPATH_ENABLED */
}
} else if (!strcmp(command, "base")) {
xmllintShellBase(ctxt, NULL, ctxt->node, NULL);
} else if (!strcmp(command, "set")) {
xmllintShellSetContent(ctxt, arg, ctxt->node, NULL);
#ifdef LIBXML_XPATH_ENABLED
} else if (!strcmp(command, "setns")) {
if (arg[0] == 0) {
fprintf(ctxt->output,
"setns: prefix=[nsuri] required\n");
} else {
xmllintShellRegisterNamespace(ctxt, arg, NULL, NULL);
}
} else if (!strcmp(command, "setrootns")) {
xmlNodePtr root;
root = xmlDocGetRootElement(ctxt->doc);
xmllintShellRegisterRootNamespaces(ctxt, NULL, root, NULL);
#ifdef LIBXML_DEBUG_ENABLED
} else if (!strcmp(command, "xpath")) {
if (arg[0] == 0) {
fprintf(ctxt->output,
"xpath: expression required\n");
} else {
ctxt->pctxt->node = ctxt->node;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
xmlXPathDebugDumpObject(ctxt->output, list, 0);
xmlXPathFreeObject(list);
}
#endif /* LIBXML_DEBUG_ENABLED */
#endif /* LIBXML_XPATH_ENABLED */
} else if (!strcmp(command, "setbase")) {
xmllintShellSetBase(ctxt, arg, ctxt->node, NULL);
} else if ((!strcmp(command, "ls")) || (!strcmp(command, "dir"))) {
int dir = (!strcmp(command, "dir"));
if (arg[0] == 0) {
if (dir)
xmllintShellDir(ctxt, NULL, ctxt->node, NULL);
else
xmllintShellList(ctxt, NULL, ctxt->node, NULL);
} else {
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt->node = ctxt->node;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
if (list != NULL) {
switch (list->type) {
case XPATH_UNDEFINED:
fprintf(ctxt->output,
"%s: no such node\n", arg);
break;
case XPATH_NODESET:{
int indx;
if (list->nodesetval == NULL)
break;
for (indx = 0;
indx < list->nodesetval->nodeNr;
indx++) {
if (dir)
xmllintShellDir(ctxt, NULL,
list->nodesetval->
nodeTab[indx], NULL);
else
xmllintShellList(ctxt, NULL,
list->nodesetval->
nodeTab[indx], NULL);
}
break;
}
case XPATH_BOOLEAN:
fprintf(ctxt->output,
"%s is a Boolean\n", arg);
break;
case XPATH_NUMBER:
fprintf(ctxt->output,
"%s is a number\n", arg);
break;
case XPATH_STRING:
fprintf(ctxt->output,
"%s is a string\n", arg);
break;
case XPATH_USERS:
fprintf(ctxt->output,
"%s is user-defined\n", arg);
break;
case XPATH_XSLT_TREE:
fprintf(ctxt->output,
"%s is an XSLT value tree\n",
arg);
break;
}
xmlXPathFreeObject(list);
} else {
fprintf(ctxt->output,
"%s: no such node\n", arg);
}
ctxt->pctxt->node = NULL;
#endif /* LIBXML_XPATH_ENABLED */
}
} else if (!strcmp(command, "whereis")) {
char dir[500];
if (arg[0] == 0) {
if (!xmllintShellPwd(ctxt, dir, ctxt->node, NULL))
fprintf(ctxt->output, "%s\n", dir);
} else {
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt->node = ctxt->node;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
if (list != NULL) {
switch (list->type) {
case XPATH_UNDEFINED:
fprintf(ctxt->output,
"%s: no such node\n", arg);
break;
case XPATH_NODESET:{
int indx;
if (list->nodesetval == NULL)
break;
for (indx = 0;
indx < list->nodesetval->nodeNr;
indx++) {
if (!xmllintShellPwd(ctxt, dir, list->nodesetval->
nodeTab[indx], NULL))
fprintf(ctxt->output, "%s\n", dir);
}
break;
}
case XPATH_BOOLEAN:
fprintf(ctxt->output,
"%s is a Boolean\n", arg);
break;
case XPATH_NUMBER:
fprintf(ctxt->output,
"%s is a number\n", arg);
break;
case XPATH_STRING:
fprintf(ctxt->output,
"%s is a string\n", arg);
break;
case XPATH_USERS:
fprintf(ctxt->output,
"%s is user-defined\n", arg);
break;
case XPATH_XSLT_TREE:
fprintf(ctxt->output,
"%s is an XSLT value tree\n",
arg);
break;
}
xmlXPathFreeObject(list);
} else {
fprintf(ctxt->output,
"%s: no such node\n", arg);
}
ctxt->pctxt->node = NULL;
#endif /* LIBXML_XPATH_ENABLED */
}
} else if (!strcmp(command, "cd")) {
if (arg[0] == 0) {
ctxt->node = (xmlNodePtr) ctxt->doc;
} else {
#ifdef LIBXML_XPATH_ENABLED
int l;
ctxt->pctxt->node = ctxt->node;
l = strlen(arg);
if ((l >= 2) && (arg[l - 1] == '/'))
arg[l - 1] = 0;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
if (list != NULL) {
switch (list->type) {
case XPATH_UNDEFINED:
fprintf(ctxt->output,
"%s: no such node\n", arg);
break;
case XPATH_NODESET:
if (list->nodesetval != NULL) {
if (list->nodesetval->nodeNr == 1) {
ctxt->node = list->nodesetval->nodeTab[0];
if ((ctxt->node != NULL) &&
(ctxt->node->type ==
XML_NAMESPACE_DECL)) {
fprintf(ctxt->output,
"cannot cd to namespace\n");
ctxt->node = NULL;
}
} else
fprintf(ctxt->output,
"%s is a %d Node Set\n",
arg,
list->nodesetval->nodeNr);
} else
fprintf(ctxt->output,
"%s is an empty Node Set\n",
arg);
break;
case XPATH_BOOLEAN:
fprintf(ctxt->output,
"%s is a Boolean\n", arg);
break;
case XPATH_NUMBER:
fprintf(ctxt->output,
"%s is a number\n", arg);
break;
case XPATH_STRING:
fprintf(ctxt->output,
"%s is a string\n", arg);
break;
case XPATH_USERS:
fprintf(ctxt->output,
"%s is user-defined\n", arg);
break;
case XPATH_XSLT_TREE:
fprintf(ctxt->output,
"%s is an XSLT value tree\n",
arg);
break;
}
xmlXPathFreeObject(list);
} else {
fprintf(ctxt->output,
"%s: no such node\n", arg);
}
ctxt->pctxt->node = NULL;
#endif /* LIBXML_XPATH_ENABLED */
}
#ifdef LIBXML_OUTPUT_ENABLED
} else if (!strcmp(command, "cat")) {
if (arg[0] == 0) {
xmllintShellCat(ctxt, NULL, ctxt->node, NULL);
} else {
#ifdef LIBXML_XPATH_ENABLED
ctxt->pctxt->node = ctxt->node;
list = xmlXPathEval((xmlChar *) arg, ctxt->pctxt);
if (list != NULL) {
switch (list->type) {
case XPATH_UNDEFINED:
fprintf(ctxt->output,
"%s: no such node\n", arg);
break;
case XPATH_NODESET:{
int indx;
if (list->nodesetval == NULL)
break;
for (indx = 0;
indx < list->nodesetval->nodeNr;
indx++) {
if (i > 0)
fprintf(ctxt->output, " -------\n");
xmllintShellCat(ctxt, NULL,
list->nodesetval->
nodeTab[indx], NULL);
}
break;
}
case XPATH_BOOLEAN:
fprintf(ctxt->output,
"%s is a Boolean\n", arg);
break;
case XPATH_NUMBER:
fprintf(ctxt->output,
"%s is a number\n", arg);
break;
case XPATH_STRING:
fprintf(ctxt->output,
"%s is a string\n", arg);
break;
case XPATH_USERS:
fprintf(ctxt->output,
"%s is user-defined\n", arg);
break;
case XPATH_XSLT_TREE:
fprintf(ctxt->output,
"%s is an XSLT value tree\n",
arg);
break;
}
xmlXPathFreeObject(list);
} else {
fprintf(ctxt->output,
"%s: no such node\n", arg);
}
ctxt->pctxt->node = NULL;
#endif /* LIBXML_XPATH_ENABLED */
}
#endif /* LIBXML_OUTPUT_ENABLED */
} else {
fprintf(ctxt->output,
"Unknown command %s\n", command);
}
free(cmdline); /* not xmlFree here ! */
cmdline = NULL;
}
#ifdef LIBXML_XPATH_ENABLED
xmlXPathFreeContext(ctxt->pctxt);
#endif /* LIBXML_XPATH_ENABLED */
if (ctxt->loaded) {
xmlFreeDoc(ctxt->doc);
}
if (ctxt->filename != NULL)
xmlFree(ctxt->filename);
xmlFree(ctxt);
if (cmdline != NULL)
free(cmdline); /* not xmlFree here ! */
}