1326 lines
36 KiB
C
1326 lines
36 KiB
C
/* $Id$
|
|
*
|
|
* This is free software, you may use it and distribute it under the same terms as
|
|
* Perl itself.
|
|
*
|
|
* Copyright 2001-2003 AxKit.com Ltd., 2002-2006 Christian Glahn, 2006-2009 Petr Pajas
|
|
*/
|
|
|
|
#include "dom.h"
|
|
#include "perl-libxml-mm.h"
|
|
|
|
/* #define warn(string) fprintf(stderr, string) */
|
|
|
|
#ifdef XS_WARNINGS
|
|
#define xs_warn(string) warn("%s",string)
|
|
#else
|
|
#define xs_warn(string)
|
|
#endif
|
|
|
|
void
|
|
domClearPSVIInList(xmlNodePtr list);
|
|
|
|
void
|
|
domClearPSVI(xmlNodePtr tree) {
|
|
xmlAttrPtr prop;
|
|
|
|
if (tree == NULL)
|
|
return;
|
|
if (tree->type == XML_ELEMENT_NODE) {
|
|
tree->psvi = NULL;
|
|
prop = tree->properties;
|
|
while (prop != NULL) {
|
|
if (tree->type == XML_ATTRIBUTE_NODE)
|
|
((xmlAttrPtr) prop)->psvi = NULL;
|
|
domClearPSVIInList(prop->children);
|
|
prop = prop->next;
|
|
}
|
|
} else if (tree->type == XML_DOCUMENT_NODE) {
|
|
((xmlDocPtr) tree)->psvi = NULL;
|
|
}
|
|
if (tree->children != NULL)
|
|
domClearPSVIInList(tree->children);
|
|
}
|
|
|
|
void
|
|
domClearPSVIInList(xmlNodePtr list) {
|
|
xmlNodePtr cur;
|
|
|
|
if (list == NULL)
|
|
return;
|
|
cur = list;
|
|
while (cur != NULL) {
|
|
domClearPSVI(cur);
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Name: domReconcileNs
|
|
* Synopsis: void domReconcileNs( xmlNodePtr tree );
|
|
* @tree: the tree to reconcile
|
|
*
|
|
* Reconciles namespacing on a tree by removing declarations
|
|
* of element and attribute namespaces that are already
|
|
* declared in the scope of the corresponding node.
|
|
**/
|
|
|
|
void
|
|
domAddNsDef(xmlNodePtr tree, xmlNsPtr ns)
|
|
{
|
|
xmlNsPtr i = tree->nsDef;
|
|
while(i != NULL && i != ns)
|
|
i = i->next;
|
|
if( i == NULL )
|
|
{
|
|
ns->next = tree->nsDef;
|
|
tree->nsDef = ns;
|
|
}
|
|
}
|
|
|
|
char
|
|
domRemoveNsDef(xmlNodePtr tree, xmlNsPtr ns)
|
|
{
|
|
xmlNsPtr i = tree->nsDef;
|
|
|
|
if( ns == tree->nsDef )
|
|
{
|
|
tree->nsDef = tree->nsDef->next;
|
|
ns->next = NULL;
|
|
return(1);
|
|
}
|
|
while( i != NULL )
|
|
{
|
|
if( i->next == ns )
|
|
{
|
|
i->next = ns->next;
|
|
ns->next = NULL;
|
|
return(1);
|
|
}
|
|
i = i->next;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* ns->next must be NULL, or bad things could happen */
|
|
xmlNsPtr
|
|
_domAddNsChain(xmlNsPtr c, xmlNsPtr ns)
|
|
{
|
|
if( c == NULL )
|
|
return(ns);
|
|
else
|
|
{
|
|
xmlNsPtr i = c;
|
|
while(i != NULL && i != ns)
|
|
i = i->next;
|
|
if(i == NULL)
|
|
{
|
|
ns->next = c;
|
|
return(ns);
|
|
}
|
|
}
|
|
return(c);
|
|
}
|
|
|
|
/* We need to be smarter with attributes, because the declaration is on the parent element */
|
|
void
|
|
_domReconcileNsAttr(xmlAttrPtr attr, xmlNsPtr * unused)
|
|
{
|
|
xmlNodePtr tree = attr->parent;
|
|
if (tree == NULL)
|
|
return;
|
|
if( attr->ns != NULL )
|
|
{
|
|
xmlNsPtr ns;
|
|
if ((attr->ns->prefix != NULL) &&
|
|
(xmlStrEqual(attr->ns->prefix, BAD_CAST "xml"))) {
|
|
/* prefix 'xml' has no visible declaration */
|
|
ns = xmlSearchNsByHref(tree->doc, tree, XML_XML_NAMESPACE);
|
|
attr->ns = ns;
|
|
return;
|
|
} else {
|
|
ns = xmlSearchNs( tree->doc, tree->parent, attr->ns->prefix );
|
|
}
|
|
if( ns != NULL && ns->href != NULL && attr->ns->href != NULL &&
|
|
xmlStrcmp(ns->href,attr->ns->href) == 0 )
|
|
{
|
|
/* Remove the declaration from the element */
|
|
if( domRemoveNsDef(tree, attr->ns) )
|
|
/* Queue up this namespace for freeing */
|
|
*unused = _domAddNsChain(*unused, attr->ns);
|
|
|
|
/* Replace the namespace with the one found */
|
|
attr->ns = ns;
|
|
}
|
|
else
|
|
{
|
|
/* If the declaration is here, we don't need to do anything */
|
|
if( domRemoveNsDef(tree, attr->ns) )
|
|
domAddNsDef(tree, attr->ns);
|
|
else
|
|
{
|
|
/* Replace/Add the namespace declaration on the element */
|
|
attr->ns = xmlCopyNamespace(attr->ns);
|
|
if (attr->ns) {
|
|
domAddNsDef(tree, attr->ns);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
_domReconcileNs(xmlNodePtr tree, xmlNsPtr * unused)
|
|
{
|
|
if( tree->ns != NULL
|
|
&& ((tree->type == XML_ELEMENT_NODE)
|
|
|| (tree->type == XML_ATTRIBUTE_NODE)))
|
|
{
|
|
xmlNsPtr ns = xmlSearchNs( tree->doc, tree->parent, tree->ns->prefix );
|
|
if( ns != NULL && ns->href != NULL && tree->ns->href != NULL &&
|
|
xmlStrcmp(ns->href,tree->ns->href) == 0 )
|
|
{
|
|
/* Remove the declaration (if present) */
|
|
if( domRemoveNsDef(tree, tree->ns) )
|
|
/* Queue the namespace for freeing */
|
|
*unused = _domAddNsChain(*unused, tree->ns);
|
|
|
|
/* Replace the namespace with the one found */
|
|
tree->ns = ns;
|
|
}
|
|
else
|
|
{
|
|
/* If the declaration is here, we don't need to do anything */
|
|
if( domRemoveNsDef(tree, tree->ns) ) {
|
|
domAddNsDef(tree, tree->ns);
|
|
}
|
|
else
|
|
{
|
|
/* Restart the namespace at this point */
|
|
tree->ns = xmlCopyNamespace(tree->ns);
|
|
domAddNsDef(tree, tree->ns);
|
|
}
|
|
}
|
|
}
|
|
/* Fix attribute namespacing */
|
|
if( tree->type == XML_ELEMENT_NODE )
|
|
{
|
|
xmlElementPtr ele = (xmlElementPtr) tree;
|
|
/* attributes is set to xmlAttributePtr,
|
|
but is an xmlAttrPtr??? */
|
|
xmlAttrPtr attr = (xmlAttrPtr) ele->attributes;
|
|
while( attr != NULL )
|
|
{
|
|
_domReconcileNsAttr(attr, unused);
|
|
attr = attr->next;
|
|
}
|
|
}
|
|
{
|
|
/* Recurse through all child nodes */
|
|
xmlNodePtr child = tree->children;
|
|
while( child != NULL )
|
|
{
|
|
_domReconcileNs(child, unused);
|
|
child = child->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
domReconcileNs(xmlNodePtr tree)
|
|
{
|
|
xmlNsPtr unused = NULL;
|
|
_domReconcileNs(tree, &unused);
|
|
if( unused != NULL )
|
|
xmlFreeNsList(unused);
|
|
}
|
|
|
|
/**
|
|
* NAME domParseChar
|
|
* TYPE function
|
|
* SYNOPSIS
|
|
* int utf8char = domParseChar( curchar, &len );
|
|
*
|
|
* The current char value, if using UTF-8 this may actually span
|
|
* multiple bytes in the given string. This function parses an utf8
|
|
* character from a string into a UTF8 character (an integer). It uses
|
|
* a slightly modified version of libxml2's character parser. libxml2
|
|
* itself does not provide any function to parse characters dircetly
|
|
* from a string and test if they are valid utf8 characters.
|
|
*
|
|
* XML::LibXML uses this function rather than perls native UTF8
|
|
* support for two reasons:
|
|
* 1) perls UTF8 handling functions often lead to encoding errors,
|
|
* which partly comes, that they are badly documented.
|
|
* 2) not all perl versions XML::LibXML intends to run with have native
|
|
* UTF8 support.
|
|
*
|
|
* domParseChar() allows to use the very same code with all versions
|
|
* of perl :)
|
|
*
|
|
* Returns the current char value and its length
|
|
*
|
|
* NOTE: If the character passed to this function is not a UTF
|
|
* character, the return value will be 0 and the length of the
|
|
* character is -1!
|
|
*/
|
|
int
|
|
domParseChar( xmlChar *cur, int *len )
|
|
{
|
|
unsigned char c;
|
|
unsigned int val;
|
|
|
|
/*
|
|
* We are supposed to handle UTF8, check it's valid
|
|
* From rfc2044: encoding of the Unicode values on UTF-8:
|
|
*
|
|
* UCS-4 range (hex.) UTF-8 octet sequence (binary)
|
|
* 0000 0000-0000 007F 0xxxxxxx
|
|
* 0000 0080-0000 07FF 110xxxxx 10xxxxxx
|
|
* 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
|
|
*
|
|
* Check for the 0x110000 limit too
|
|
*/
|
|
|
|
if ( cur == NULL || *cur == 0 ) {
|
|
*len = 0;
|
|
return(0);
|
|
}
|
|
|
|
c = *cur;
|
|
if ( c & 0x80 ) {
|
|
if ((c & 0xe0) == 0xe0) {
|
|
if ((c & 0xf0) == 0xf0) {
|
|
/* 4-byte code */
|
|
*len = 4;
|
|
val = (cur[0] & 0x7) << 18;
|
|
val |= (cur[1] & 0x3f) << 12;
|
|
val |= (cur[2] & 0x3f) << 6;
|
|
val |= cur[3] & 0x3f;
|
|
} else {
|
|
/* 3-byte code */
|
|
*len = 3;
|
|
val = (cur[0] & 0xf) << 12;
|
|
val |= (cur[1] & 0x3f) << 6;
|
|
val |= cur[2] & 0x3f;
|
|
}
|
|
} else {
|
|
/* 2-byte code */
|
|
*len = 2;
|
|
val = (cur[0] & 0x1f) << 6;
|
|
val |= cur[1] & 0x3f;
|
|
}
|
|
if ( !IS_CHAR(val) ) {
|
|
*len = -1;
|
|
return(0);
|
|
}
|
|
return(val);
|
|
}
|
|
else {
|
|
/* 1-byte code */
|
|
*len = 1;
|
|
return((int)c);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Name: domReadWellBalancedString
|
|
* Synopsis: xmlNodePtr domReadWellBalancedString( xmlDocPtr doc, xmlChar *string )
|
|
* @doc: the document, the string should belong to
|
|
* @string: the string to parse
|
|
*
|
|
* this function is pretty neat, since you can read in well balanced
|
|
* strings and get a list of nodes, which can be added to any other node.
|
|
* (sure - this should return a doucment_fragment, but still it doesn't)
|
|
*
|
|
* the code is pretty heavy i think, but deep in my heard i believe it's
|
|
* worth it :) (e.g. if you like to read a chunk of well-balanced code
|
|
* from a databasefield)
|
|
*
|
|
* in 99% the cases i believe it is faster than to create the dom by hand,
|
|
* and skip the parsing job which has to be done here.
|
|
*
|
|
* the repair flag will not be recognized with the current libxml2
|
|
**/
|
|
xmlNodePtr
|
|
domReadWellBalancedString( xmlDocPtr doc, xmlChar* block, int repair ) {
|
|
int retCode = -1;
|
|
xmlNodePtr nodes = NULL;
|
|
|
|
if ( block ) {
|
|
/* read and encode the chunk */
|
|
retCode = xmlParseBalancedChunkMemory( doc,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
block,
|
|
&nodes );
|
|
|
|
/* retCode = xmlParseBalancedChunkMemoryRecover( doc, */
|
|
/* NULL, */
|
|
/* NULL, */
|
|
/* 0, */
|
|
/* block, */
|
|
/* &nodes, */
|
|
/* repair ); */
|
|
|
|
/* error handling */
|
|
if ( retCode != 0 && repair == 0 ) {
|
|
/* if the code was not well balanced, we will not return
|
|
* a bad node list, but we have to free the nodes */
|
|
xmlFreeNodeList( nodes );
|
|
nodes = NULL;
|
|
}
|
|
else {
|
|
xmlSetListDoc(nodes,doc);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
/**
|
|
* internal helper: insert node to nodelist
|
|
* synopsis: xmlNodePtr insert_node_to_nodelist( leader, insertnode, followup );
|
|
* while leader and followup are already list nodes. both may be NULL
|
|
* if leader is null the parents children will be reset
|
|
* if followup is null the parent last will be reset.
|
|
* leader and followup has to be followups in the nodelist!!!
|
|
* the function returns the node inserted. if a fragment was inserted,
|
|
* the first node of the list will returned
|
|
*
|
|
* i ran into a misconception here. there should be a normalization function
|
|
* for the DOM, so sequences of text nodes can get replaced by a single
|
|
* text node. as i see DOM Level 1 does not allow text node sequences, while
|
|
* Level 2 and 3 do.
|
|
**/
|
|
int
|
|
domAddNodeToList(xmlNodePtr cur, xmlNodePtr leader, xmlNodePtr followup)
|
|
{
|
|
xmlNodePtr c1 = NULL, c2 = NULL, p = NULL;
|
|
if ( cur ) {
|
|
c1 = c2 = cur;
|
|
if( leader ) {
|
|
p = leader->parent;
|
|
}
|
|
else if( followup ) {
|
|
p = followup->parent;
|
|
}
|
|
else {
|
|
return 0; /* can't insert */
|
|
}
|
|
|
|
if ( cur->type == XML_DOCUMENT_FRAG_NODE ) {
|
|
c1 = cur->children;
|
|
while ( c1 ){
|
|
c1->parent = p;
|
|
c1 = c1->next;
|
|
}
|
|
c1 = cur->children;
|
|
c2 = cur->last;
|
|
cur->last = cur->children = NULL;
|
|
}
|
|
else {
|
|
cur->parent = p;
|
|
}
|
|
|
|
if (c1 && c2 && c1!=leader) {
|
|
if ( leader ) {
|
|
leader->next = c1;
|
|
c1->prev = leader;
|
|
}
|
|
else if ( p ) {
|
|
p->children = c1;
|
|
}
|
|
|
|
if ( followup ) {
|
|
followup->prev = c2;
|
|
c2->next = followup;
|
|
}
|
|
else if ( p ) {
|
|
p->last = c2;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* domIsParent tests, if testnode is parent of the reference
|
|
* node. this test is very important to avoid circular constructs in
|
|
* trees. if the ref is a parent of the cur node the
|
|
* function returns 1 (TRUE), otherwise 0 (FALSE).
|
|
**/
|
|
int
|
|
domIsParent( xmlNodePtr cur, xmlNodePtr refNode ) {
|
|
xmlNodePtr helper = NULL;
|
|
|
|
if ( cur == NULL || refNode == NULL) return 0;
|
|
if (refNode==cur) return 1;
|
|
if ( cur->doc != refNode->doc
|
|
|| refNode->children == NULL
|
|
|| cur->parent == (xmlNodePtr)cur->doc
|
|
|| cur->parent == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
if( refNode->type == XML_DOCUMENT_NODE ) {
|
|
return 1;
|
|
}
|
|
|
|
helper= cur;
|
|
while ( helper && (xmlDocPtr) helper != cur->doc ) {
|
|
if( helper == refNode ) {
|
|
return 1;
|
|
}
|
|
helper = helper->parent;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
domTestHierarchy(xmlNodePtr cur, xmlNodePtr refNode)
|
|
{
|
|
if ( !refNode || !cur ) {
|
|
return 0;
|
|
}
|
|
if (cur->type == XML_ATTRIBUTE_NODE) {
|
|
switch ( refNode->type ){
|
|
case XML_TEXT_NODE:
|
|
case XML_ENTITY_REF_NODE:
|
|
return 1;
|
|
break;
|
|
default:
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch ( refNode->type ){
|
|
case XML_ATTRIBUTE_NODE:
|
|
case XML_DOCUMENT_NODE:
|
|
return 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( domIsParent( cur, refNode ) ) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
domTestDocument(xmlNodePtr cur, xmlNodePtr refNode)
|
|
{
|
|
if ( cur->type == XML_DOCUMENT_NODE ) {
|
|
switch ( refNode->type ) {
|
|
case XML_ATTRIBUTE_NODE:
|
|
case XML_ELEMENT_NODE:
|
|
case XML_ENTITY_NODE:
|
|
case XML_ENTITY_REF_NODE:
|
|
case XML_TEXT_NODE:
|
|
case XML_CDATA_SECTION_NODE:
|
|
case XML_NAMESPACE_DECL:
|
|
return 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
domUnlinkNode( xmlNodePtr node ) {
|
|
if ( node == NULL
|
|
|| ( node->prev == NULL
|
|
&& node->next == NULL
|
|
&& node->parent == NULL ) ) {
|
|
return;
|
|
}
|
|
|
|
if (node->type == XML_DTD_NODE) {
|
|
/* This clears the doc->intSubset pointer. */
|
|
xmlUnlinkNode(node);
|
|
return;
|
|
}
|
|
|
|
if ( node->prev != NULL ) {
|
|
node->prev->next = node->next;
|
|
}
|
|
|
|
if ( node->next != NULL ) {
|
|
node->next->prev = node->prev;
|
|
}
|
|
|
|
if ( node->parent != NULL ) {
|
|
if ( node == node->parent->last ) {
|
|
node->parent->last = node->prev;
|
|
}
|
|
|
|
if ( node == node->parent->children ) {
|
|
node->parent->children = node->next;
|
|
}
|
|
}
|
|
|
|
node->prev = NULL;
|
|
node->next = NULL;
|
|
node->parent = NULL;
|
|
}
|
|
|
|
xmlNodePtr
|
|
domImportNode( xmlDocPtr doc, xmlNodePtr node, int move, int reconcileNS ) {
|
|
xmlNodePtr return_node = node;
|
|
|
|
if ( move ) {
|
|
return_node = node;
|
|
domUnlinkNode( node );
|
|
}
|
|
else {
|
|
if ( node->type == XML_DTD_NODE ) {
|
|
return_node = (xmlNodePtr) xmlCopyDtd((xmlDtdPtr) node);
|
|
}
|
|
else {
|
|
return_node = xmlDocCopyNode( node, doc, 1 );
|
|
}
|
|
}
|
|
|
|
|
|
/* tell all children about the new boss */
|
|
if ( node && node->doc != doc ) {
|
|
/* if the source document contained psvi, mark the current document as psvi tainted */
|
|
if (PmmIsPSVITainted(node->doc))
|
|
PmmInvalidatePSVI(doc);
|
|
xmlSetTreeDoc(return_node, doc);
|
|
}
|
|
|
|
if ( reconcileNS && doc && return_node
|
|
&& return_node->type != XML_ENTITY_REF_NODE ) {
|
|
domReconcileNs(return_node);
|
|
}
|
|
|
|
return return_node;
|
|
}
|
|
|
|
/**
|
|
* Name: domName
|
|
* Synopsis: string = domName( node );
|
|
*
|
|
* domName returns the full name for the current node.
|
|
* If the node belongs to a namespace it returns the prefix and
|
|
* the local name. otherwise only the local name is returned.
|
|
**/
|
|
xmlChar*
|
|
domName(xmlNodePtr node) {
|
|
const xmlChar *prefix = NULL;
|
|
const xmlChar *name = NULL;
|
|
xmlChar *qname = NULL;
|
|
|
|
if ( node == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
switch ( node->type ) {
|
|
case XML_XINCLUDE_START :
|
|
case XML_XINCLUDE_END :
|
|
case XML_ENTITY_REF_NODE :
|
|
case XML_ENTITY_NODE :
|
|
case XML_DTD_NODE :
|
|
case XML_ENTITY_DECL :
|
|
case XML_DOCUMENT_TYPE_NODE :
|
|
case XML_PI_NODE :
|
|
case XML_NOTATION_NODE :
|
|
case XML_NAMESPACE_DECL :
|
|
name = node->name;
|
|
break;
|
|
|
|
case XML_COMMENT_NODE :
|
|
name = (const xmlChar *) "#comment";
|
|
break;
|
|
|
|
case XML_CDATA_SECTION_NODE :
|
|
name = (const xmlChar *) "#cdata-section";
|
|
break;
|
|
|
|
case XML_TEXT_NODE :
|
|
name = (const xmlChar *) "#text";
|
|
break;
|
|
|
|
|
|
case XML_DOCUMENT_NODE :
|
|
case XML_HTML_DOCUMENT_NODE :
|
|
case XML_DOCB_DOCUMENT_NODE :
|
|
name = (const xmlChar *) "#document";
|
|
break;
|
|
|
|
case XML_DOCUMENT_FRAG_NODE :
|
|
name = (const xmlChar *) "#document-fragment";
|
|
break;
|
|
|
|
case XML_ELEMENT_NODE :
|
|
case XML_ATTRIBUTE_NODE :
|
|
if ( node->ns != NULL ) {
|
|
prefix = node->ns->prefix;
|
|
}
|
|
name = node->name;
|
|
break;
|
|
|
|
case XML_ELEMENT_DECL :
|
|
prefix = ((xmlElementPtr) node)->prefix;
|
|
name = node->name;
|
|
break;
|
|
|
|
case XML_ATTRIBUTE_DECL :
|
|
prefix = ((xmlAttributePtr) node)->prefix;
|
|
name = node->name;
|
|
break;
|
|
}
|
|
|
|
if ( prefix != NULL ) {
|
|
qname = xmlStrdup( prefix );
|
|
qname = xmlStrcat( qname , (const xmlChar *) ":" );
|
|
qname = xmlStrcat( qname , name );
|
|
}
|
|
else {
|
|
qname = xmlStrdup( name );
|
|
}
|
|
|
|
return qname;
|
|
}
|
|
|
|
/**
|
|
* Name: domAppendChild
|
|
* Synopsis: xmlNodePtr domAppendChild( xmlNodePtr par, xmlNodePtr newCld );
|
|
* @par: the node to append to
|
|
* @newCld: the node to append
|
|
*
|
|
* Returns newCld on success otherwise NULL
|
|
* The function will unbind newCld first if nesseccary. As well the
|
|
* function will fail, if par or newCld is a Attribute Node OR if newCld
|
|
* is a parent of par.
|
|
*
|
|
* If newCld belongs to a different DOM the node will be imported
|
|
* implicit before it gets appended.
|
|
**/
|
|
xmlNodePtr
|
|
domAppendChild( xmlNodePtr self,
|
|
xmlNodePtr newChild ){
|
|
xmlNodePtr fragment = NULL;
|
|
if ( self == NULL ) {
|
|
return newChild;
|
|
}
|
|
|
|
if ( !(domTestHierarchy(self, newChild)
|
|
&& domTestDocument(self, newChild))){
|
|
croak("appendChild: HIERARCHY_REQUEST_ERR\n");
|
|
return NULL;
|
|
}
|
|
|
|
if ( newChild->doc == self->doc ){
|
|
domUnlinkNode( newChild );
|
|
}
|
|
else {
|
|
xs_warn("WRONG_DOCUMENT_ERR - non conform implementation\n");
|
|
/* xmlGenericError(xmlGenericErrorContext,"WRONG_DOCUMENT_ERR\n"); */
|
|
newChild = domImportNode( self->doc, newChild, 1, 0 );
|
|
}
|
|
|
|
if ( self->children != NULL ) {
|
|
if (newChild->type == XML_DOCUMENT_FRAG_NODE )
|
|
fragment = newChild->children;
|
|
domAddNodeToList( newChild, self->last, NULL );
|
|
}
|
|
else if (newChild->type == XML_DOCUMENT_FRAG_NODE ) {
|
|
xmlNodePtr c1 = NULL;
|
|
self->children = newChild->children;
|
|
fragment = newChild->children;
|
|
c1 = fragment;
|
|
while ( c1 ){
|
|
c1->parent = self;
|
|
c1 = c1->next;
|
|
}
|
|
self->last = newChild->last;
|
|
newChild->last = newChild->children = NULL;
|
|
}
|
|
else {
|
|
self->children = newChild;
|
|
self->last = newChild;
|
|
newChild->parent= self;
|
|
}
|
|
|
|
if ( fragment ) {
|
|
/* we must reconcile all nodes in the fragment */
|
|
newChild = fragment; /* return the first node in the fragment */
|
|
while ( fragment ) {
|
|
domReconcileNs(fragment);
|
|
fragment = fragment->next;
|
|
}
|
|
}
|
|
else if ( newChild->type != XML_ENTITY_REF_NODE ) {
|
|
domReconcileNs(newChild);
|
|
}
|
|
|
|
return newChild;
|
|
}
|
|
|
|
xmlNodePtr
|
|
domRemoveChild( xmlNodePtr self, xmlNodePtr old ) {
|
|
if ( self == NULL || old == NULL ) {
|
|
return NULL;
|
|
}
|
|
if ( old->type == XML_ATTRIBUTE_NODE
|
|
|| old->type == XML_NAMESPACE_DECL ) {
|
|
return NULL;
|
|
}
|
|
if ( self != old->parent ) {
|
|
/* not a child! */
|
|
return NULL;
|
|
}
|
|
|
|
domUnlinkNode( old );
|
|
if ( old->type == XML_ELEMENT_NODE ) {
|
|
domReconcileNs( old );
|
|
}
|
|
|
|
return old ;
|
|
}
|
|
|
|
xmlNodePtr
|
|
domReplaceChild( xmlNodePtr self, xmlNodePtr new, xmlNodePtr old ) {
|
|
xmlNodePtr fragment = NULL;
|
|
xmlNodePtr fragment_next = NULL;
|
|
if ( self== NULL )
|
|
return NULL;
|
|
|
|
if ( new == old )
|
|
return NULL;
|
|
|
|
if ( new == NULL ) {
|
|
/* level2 sais nothing about this case :( */
|
|
return domRemoveChild( self, old );
|
|
}
|
|
|
|
if ( old == NULL ) {
|
|
domAppendChild( self, new );
|
|
return old;
|
|
}
|
|
|
|
if ( !(domTestHierarchy(self, new)
|
|
&& domTestDocument(self, new))){
|
|
croak("replaceChild: HIERARCHY_REQUEST_ERR\n");
|
|
return NULL;
|
|
}
|
|
|
|
if ( new->doc == self->doc ) {
|
|
domUnlinkNode( new );
|
|
}
|
|
else {
|
|
/* WRONG_DOCUMENT_ERR - non conform implementation */
|
|
new = domImportNode( self->doc, new, 1, 1 );
|
|
}
|
|
|
|
if( old == self->children && old == self->last ) {
|
|
domRemoveChild( self, old );
|
|
domAppendChild( self, new );
|
|
}
|
|
else if ( new->type == XML_DOCUMENT_FRAG_NODE
|
|
&& new->children == NULL ) {
|
|
/* want to replace with an empty fragment, then remove ... */
|
|
fragment = new->children;
|
|
fragment_next = old->next;
|
|
domRemoveChild( self, old );
|
|
}
|
|
else {
|
|
domAddNodeToList(new, old->prev, old->next );
|
|
old->parent = old->next = old->prev = NULL;
|
|
}
|
|
if ( fragment ) {
|
|
while ( fragment && fragment != fragment_next ) {
|
|
domReconcileNs(fragment);
|
|
fragment = fragment->next;
|
|
}
|
|
} else if ( new->type != XML_ENTITY_REF_NODE ) {
|
|
domReconcileNs(new);
|
|
}
|
|
|
|
return old;
|
|
}
|
|
|
|
|
|
xmlNodePtr
|
|
domInsertBefore( xmlNodePtr self,
|
|
xmlNodePtr newChild,
|
|
xmlNodePtr refChild ){
|
|
xmlNodePtr fragment = NULL;
|
|
if ( refChild == newChild ) {
|
|
return newChild;
|
|
}
|
|
|
|
if ( self == NULL || newChild == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( refChild != NULL ) {
|
|
if ( refChild->parent != self
|
|
|| ( newChild->type == XML_DOCUMENT_FRAG_NODE
|
|
&& newChild->children == NULL ) ) {
|
|
/* NOT_FOUND_ERR */
|
|
xmlGenericError(xmlGenericErrorContext,"NOT_FOUND_ERR\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ( self->children == NULL ) {
|
|
return domAppendChild( self, newChild );
|
|
}
|
|
|
|
if ( !(domTestHierarchy( self, newChild )
|
|
&& domTestDocument( self, newChild ))) {
|
|
croak("insertBefore/insertAfter: HIERARCHY_REQUEST_ERR\n");
|
|
return NULL;
|
|
}
|
|
|
|
if ( self->doc == newChild->doc ){
|
|
domUnlinkNode( newChild );
|
|
}
|
|
else {
|
|
newChild = domImportNode( self->doc, newChild, 1, 0 );
|
|
}
|
|
|
|
if ( newChild->type == XML_DOCUMENT_FRAG_NODE ) {
|
|
fragment = newChild->children;
|
|
}
|
|
if ( refChild == NULL ) {
|
|
domAddNodeToList(newChild, self->last, NULL);
|
|
}
|
|
else {
|
|
domAddNodeToList(newChild, refChild->prev, refChild);
|
|
}
|
|
|
|
if ( fragment ) {
|
|
newChild = fragment; /* return the first node in the fragment */
|
|
while ( fragment && fragment != refChild ) {
|
|
domReconcileNs(fragment);
|
|
fragment = fragment->next;
|
|
}
|
|
} else if ( newChild->type != XML_ENTITY_REF_NODE ) {
|
|
domReconcileNs(newChild);
|
|
}
|
|
|
|
return newChild;
|
|
}
|
|
|
|
/*
|
|
* this function does not exist in the spec although it's useful
|
|
*/
|
|
xmlNodePtr
|
|
domInsertAfter( xmlNodePtr self,
|
|
xmlNodePtr newChild,
|
|
xmlNodePtr refChild ){
|
|
if ( refChild == NULL ) {
|
|
return domInsertBefore( self, newChild, NULL );
|
|
}
|
|
return domInsertBefore( self, newChild, refChild->next );
|
|
}
|
|
|
|
xmlNodePtr
|
|
domReplaceNode( xmlNodePtr oldNode, xmlNodePtr newNode ) {
|
|
xmlNodePtr prev = NULL, next = NULL, par = NULL, fragment = NULL;
|
|
|
|
if ( oldNode == NULL
|
|
|| newNode == NULL ) {
|
|
/* NOT_FOUND_ERROR */
|
|
return NULL;
|
|
}
|
|
|
|
if ( oldNode->type == XML_ATTRIBUTE_NODE
|
|
|| newNode->type == XML_ATTRIBUTE_NODE
|
|
|| newNode->type == XML_DOCUMENT_NODE
|
|
|| domIsParent( newNode, oldNode ) ) {
|
|
/* HIERARCHY_REQUEST_ERR
|
|
* wrong node type
|
|
* new node is parent of itself
|
|
*/
|
|
croak("replaceNode: HIERARCHY_REQUEST_ERR\n");
|
|
return NULL;
|
|
}
|
|
|
|
par = oldNode->parent;
|
|
prev = oldNode->prev;
|
|
next = oldNode->next;
|
|
|
|
if ( oldNode->_private == NULL ) {
|
|
xmlUnlinkNode( oldNode );
|
|
}
|
|
else {
|
|
domUnlinkNode( oldNode );
|
|
}
|
|
|
|
if ( newNode->type == XML_DOCUMENT_FRAG_NODE ) {
|
|
fragment = newNode->children;
|
|
}
|
|
if( prev == NULL && next == NULL ) {
|
|
/* oldNode was the only child */
|
|
domAppendChild( par , newNode );
|
|
}
|
|
else {
|
|
domAddNodeToList( newNode, prev, next );
|
|
}
|
|
|
|
if ( fragment ) {
|
|
while ( fragment && fragment != next ) {
|
|
domReconcileNs(fragment);
|
|
fragment = fragment->next;
|
|
}
|
|
} else if ( newNode->type != XML_ENTITY_REF_NODE ) {
|
|
domReconcileNs(newNode);
|
|
}
|
|
|
|
return oldNode;
|
|
}
|
|
|
|
xmlChar*
|
|
domGetNodeValue( xmlNodePtr n ) {
|
|
xmlChar * retval = NULL;
|
|
if( n != NULL ) {
|
|
switch ( n->type ) {
|
|
case XML_ATTRIBUTE_NODE:
|
|
case XML_ENTITY_DECL:
|
|
case XML_TEXT_NODE:
|
|
case XML_COMMENT_NODE:
|
|
case XML_CDATA_SECTION_NODE:
|
|
case XML_PI_NODE:
|
|
case XML_ENTITY_REF_NODE:
|
|
break;
|
|
default:
|
|
return retval;
|
|
break;
|
|
}
|
|
if ( n->type != XML_ENTITY_DECL ) {
|
|
retval = xmlXPathCastNodeToString(n);
|
|
}
|
|
else {
|
|
if ( n->content != NULL ) {
|
|
xs_warn(" dublicate content\n" );
|
|
retval = xmlStrdup(n->content);
|
|
}
|
|
else if ( n->children != NULL ) {
|
|
xmlNodePtr cnode = n->children;
|
|
xs_warn(" use child content\n" );
|
|
/* ok then toString in this case ... */
|
|
while (cnode) {
|
|
xmlBufferPtr buffer = xmlBufferCreate();
|
|
/* buffer = xmlBufferCreate(); */
|
|
xmlNodeDump( buffer, n->doc, cnode, 0, 0 );
|
|
if ( buffer->content != NULL ) {
|
|
xs_warn( "add item" );
|
|
if ( retval != NULL ) {
|
|
retval = xmlStrcat( retval, buffer->content );
|
|
}
|
|
else {
|
|
retval = xmlStrdup( buffer->content );
|
|
}
|
|
}
|
|
xmlBufferFree( buffer );
|
|
cnode = cnode->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
domSetNodeValue( xmlNodePtr n , xmlChar* val ){
|
|
if ( n == NULL )
|
|
return;
|
|
if ( val == NULL ){
|
|
val = (xmlChar *) "";
|
|
}
|
|
|
|
if( n->type == XML_ATTRIBUTE_NODE ){
|
|
/* can't use xmlNodeSetContent - for Attrs it parses entities */
|
|
if ( n->children != NULL ) {
|
|
n->last = NULL;
|
|
xmlFreeNodeList( n->children );
|
|
}
|
|
n->children = xmlNewText( val );
|
|
n->children->parent = n;
|
|
n->children->doc = n->doc;
|
|
n->last = n->children;
|
|
}
|
|
else {
|
|
xmlNodeSetContent( n, val );
|
|
}
|
|
}
|
|
|
|
|
|
xmlNodeSetPtr
|
|
domGetElementsByTagName( xmlNodePtr n, xmlChar* name ){
|
|
xmlNodeSetPtr rv = NULL;
|
|
xmlNodePtr cld = NULL;
|
|
|
|
if ( n != NULL && name != NULL ) {
|
|
cld = n->children;
|
|
while ( cld != NULL ) {
|
|
if ( xmlStrcmp( name, cld->name ) == 0 ){
|
|
if ( rv == NULL ) {
|
|
rv = xmlXPathNodeSetCreate( cld ) ;
|
|
}
|
|
else {
|
|
xmlXPathNodeSetAdd( rv, cld );
|
|
}
|
|
}
|
|
cld = cld->next;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
xmlNodeSetPtr
|
|
domGetElementsByTagNameNS( xmlNodePtr n, xmlChar* nsURI, xmlChar* name ){
|
|
xmlNodeSetPtr rv = NULL;
|
|
|
|
if ( nsURI == NULL ) {
|
|
return domGetElementsByTagName( n, name );
|
|
}
|
|
|
|
if ( n != NULL && name != NULL ) {
|
|
xmlNodePtr cld = n->children;
|
|
while ( cld != NULL ) {
|
|
if ( xmlStrcmp( name, cld->name ) == 0
|
|
&& cld->ns != NULL
|
|
&& xmlStrcmp( nsURI, cld->ns->href ) == 0 ){
|
|
if ( rv == NULL ) {
|
|
rv = xmlXPathNodeSetCreate( cld ) ;
|
|
}
|
|
else {
|
|
xmlXPathNodeSetAdd( rv, cld );
|
|
}
|
|
}
|
|
cld = cld->next;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
xmlNsPtr
|
|
domNewNs ( xmlNodePtr elem , xmlChar *prefix, xmlChar *href ) {
|
|
xmlNsPtr ns = NULL;
|
|
|
|
if (elem != NULL) {
|
|
ns = xmlSearchNs( elem->doc, elem, prefix );
|
|
}
|
|
/* prefix is not in use */
|
|
if (ns == NULL) {
|
|
ns = xmlNewNs( elem , href , prefix );
|
|
} else {
|
|
/* prefix is in use; if it has same URI, let it go, otherwise it's
|
|
an error */
|
|
if (!xmlStrEqual(href, ns->href)) {
|
|
ns = NULL;
|
|
}
|
|
}
|
|
return ns;
|
|
}
|
|
|
|
xmlAttrPtr
|
|
domGetAttrNode(xmlNodePtr node, const xmlChar *qname) {
|
|
xmlChar * prefix = NULL;
|
|
xmlChar * localname = NULL;
|
|
xmlAttrPtr ret = NULL;
|
|
xmlNsPtr ns = NULL;
|
|
|
|
if ( qname == NULL || node == NULL )
|
|
return NULL;
|
|
|
|
/* first try qname without namespace */
|
|
ret = xmlHasNsProp(node, qname, NULL);
|
|
if ( ret == NULL ) {
|
|
localname = xmlSplitQName2(qname, &prefix);
|
|
if ( localname != NULL ) {
|
|
ns = xmlSearchNs( node->doc, node, prefix );
|
|
if ( ns != NULL ) {
|
|
/* then try localname with the namespace bound to prefix */
|
|
ret = xmlHasNsProp( node, localname, ns->href );
|
|
}
|
|
if ( prefix != NULL) {
|
|
xmlFree( prefix );
|
|
}
|
|
xmlFree( localname );
|
|
}
|
|
}
|
|
if (ret && ret->type != XML_ATTRIBUTE_NODE) {
|
|
return NULL; /* we don't want fixed attribute decls */
|
|
}
|
|
else {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
xmlAttrPtr
|
|
domSetAttributeNode( xmlNodePtr node, xmlAttrPtr attr ) {
|
|
if ( node == NULL || attr == NULL ) {
|
|
return attr;
|
|
}
|
|
if ( attr != NULL && attr->type != XML_ATTRIBUTE_NODE )
|
|
return NULL;
|
|
if ( node == attr->parent ) {
|
|
return attr; /* attribute is already part of the node */
|
|
}
|
|
if ( attr->doc != node->doc ){
|
|
attr = (xmlAttrPtr) domImportNode( node->doc, (xmlNodePtr) attr, 1, 1 );
|
|
}
|
|
else {
|
|
xmlUnlinkNode( (xmlNodePtr) attr );
|
|
}
|
|
|
|
/* stolen from libxml2 */
|
|
if ( attr != NULL ) {
|
|
if (node->properties == NULL) {
|
|
node->properties = attr;
|
|
} else {
|
|
xmlAttrPtr prev = node->properties;
|
|
|
|
while (prev->next != NULL) prev = prev->next;
|
|
prev->next = attr;
|
|
attr->prev = prev;
|
|
}
|
|
}
|
|
|
|
return attr;
|
|
}
|
|
|
|
void
|
|
domAttrSerializeContent(xmlBufferPtr buffer, xmlAttrPtr attr)
|
|
{
|
|
xmlNodePtr children;
|
|
|
|
children = attr->children;
|
|
while (children != NULL) {
|
|
switch (children->type) {
|
|
case XML_TEXT_NODE:
|
|
xmlAttrSerializeTxtContent(buffer, attr->doc,
|
|
attr, children->content);
|
|
break;
|
|
case XML_ENTITY_REF_NODE:
|
|
xmlBufferAdd(buffer, BAD_CAST "&", 1);
|
|
xmlBufferAdd(buffer, children->name,
|
|
xmlStrlen(children->name));
|
|
xmlBufferAdd(buffer, BAD_CAST ";", 1);
|
|
break;
|
|
default:
|
|
/* should not happen unless we have a badly built tree */
|
|
break;
|
|
}
|
|
children = children->next;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
domNodeNormalize( xmlNodePtr node );
|
|
|
|
int
|
|
domNodeNormalizeList( xmlNodePtr nodelist )
|
|
{
|
|
while ( nodelist ){
|
|
if ( domNodeNormalize( nodelist ) == 0 )
|
|
return(0);
|
|
nodelist = nodelist->next;
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
int
|
|
domNodeNormalize( xmlNodePtr node )
|
|
{
|
|
xmlNodePtr next = NULL;
|
|
|
|
if ( node == NULL )
|
|
return(0);
|
|
|
|
switch ( node->type ) {
|
|
case XML_TEXT_NODE:
|
|
while ( node->next
|
|
&& node->next->type == XML_TEXT_NODE ) {
|
|
next = node->next;
|
|
xmlNodeAddContent(node, next->content);
|
|
xmlUnlinkNode( next );
|
|
|
|
/**
|
|
* keep only nodes that are referred by perl (or GDOME)
|
|
*/
|
|
if ( !next->_private )
|
|
xmlFreeNode( next );
|
|
}
|
|
break;
|
|
case XML_ELEMENT_NODE:
|
|
domNodeNormalizeList( (xmlNodePtr) node->properties );
|
|
case XML_ATTRIBUTE_NODE:
|
|
case XML_DOCUMENT_NODE:
|
|
return( domNodeNormalizeList( node->children ) );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
int
|
|
domRemoveNsRefs(xmlNodePtr tree, xmlNsPtr ns) {
|
|
xmlAttrPtr attr;
|
|
xmlNodePtr node = tree;
|
|
|
|
if ((node == NULL) || (node->type != XML_ELEMENT_NODE)) return(0);
|
|
while (node != NULL) {
|
|
if (node->ns == ns)
|
|
node->ns = NULL; /* remove namespace reference */
|
|
attr = node->properties;
|
|
while (attr != NULL) {
|
|
if (attr->ns == ns)
|
|
attr->ns = NULL; /* remove namespace reference */
|
|
attr = attr->next;
|
|
}
|
|
/*
|
|
* Browse the full subtree, deep first
|
|
*/
|
|
if (node->children != NULL && node->type != XML_ENTITY_REF_NODE) {
|
|
/* deep first */
|
|
node = node->children;
|
|
} 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;
|
|
if ((node != tree) && (node->next != NULL)) {
|
|
node = node->next;
|
|
break;
|
|
}
|
|
if (node->parent == NULL) {
|
|
node = NULL;
|
|
break;
|
|
}
|
|
}
|
|
/* exit condition */
|
|
if (node == tree)
|
|
node = NULL;
|
|
} else
|
|
break;
|
|
}
|
|
return(1);
|
|
}
|
|
|