pike.git / lib / modules / Parser.pmod / XML.pmod / Tree.pmod

version» Context lines:

pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1:   #pike __REAL_VERSION__      /*    */      //! XML parser that generates node-trees.   //!   //! Has some support for XML namespaces - //! @url{http://www.w3.org/TR/REC-xml-names/@} - //! RFC 2518 23.4. + //! @url{http://www.w3.org/TR/REC-xml-names/@} @rfc{2518:23.4@}.   //!   //! @note   //! This module defines two sets of node trees;   //! the @[SimpleNode]-based, and the @[Node]-based.   //! The main difference between the two, is that   //! the @[Node]-based trees have parent pointers,   //! which tend to generate circular data references   //! and thus garbage.   //!   //! There are some more subtle differences between
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:142:    } else {    out += text_quote(data[opos..pos]);    pos++;    }    opos = pos;    }    return out + text_quote(data[opos..]);   }      //! Quotes the string given in @[data] by escaping &, <, >, ' and ". - string attribute_quote(string data) + string attribute_quote(string data, void|string ignore)   { -  +  switch(ignore) +  { +  case "\"": +  return replace(data, ([ "'":"&apos;", +  "&":"&amp;", +  "\n":"&#a;", +  "\r":"&#d;", +  "<":"&lt;", +  ">":"&gt;" ]) ); +  case "'":    return replace(data, ([ "\"":"&quot;", -  +  "&":"&amp;", +  "\n":"&#a;", +  "\r":"&#d;", +  "<":"&lt;", +  ">":"&gt;" ]) ); +  +  default: +  return replace(data, ([ "\"":"&quot;",    "'":"&apos;",    "&":"&amp;", -  +  "\n":"&#a;", +  "\r":"&#d;",    "<":"&lt;",    ">":"&gt;" ]) );    } -  + }      //! Quotes strings just like @[attribute_quote], but entities in the   //! form @tt{&foo.bar;@} will not be quoted.   string roxen_attribute_quote(string data)   {    return replace(roxen_text_quote(data),    ([ "\"":"&quot;",    "'":"&apos;" ]));   }   
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:173:    str = "Parser.XML.Tree: " + str;    error(str, @args);   }      //! Namespace aware parser.   class XMLNSParser {    ADT.Stack namespace_stack = ADT.Stack();       void create()    { -  namespace_stack->push(([])); // Sentinel. +  // Sentinel and default namespaces. +  namespace_stack->push(([ +  "xml":"http://www.w3.org/XML/1998/namespace", +  "xmlns":"http://www.w3.org/2000/xmlns/", +  ]));    }       //! Check @[attrs] for namespaces.    //!    //! @returns    //! Returns the namespace expanded version of @[attrs].    mapping(string:string) Enter(mapping(string:string) attrs)    {    mapping(string:string) namespaces = namespace_stack->top() + ([]);    foreach(attrs; string attr; string val) {    if (attr == "xmlns") {    if (val == "") error("Bad namespace specification (%O=\"\")\n", attr); -  +  if ((val == "http://www.w3.org/2000/xmlns/") || +  (val == "http://www.w3.org/XML/1998/namespace")) { +  error("Invalid namespace declaration.\n"); +  }    namespaces[0] = val;    } else if (has_prefix(attr, "xmlns:")) {    if (val == "") error("Bad namespace specification (%O=\"\")\n", attr); -  +  if ((val == "http://www.w3.org/2000/xmlns/") || +  (val == "http://www.w3.org/XML/1998/namespace") || +  (attr == "xmlns:xml") || (attr == "xmlns:xmlns")) { +  if ((attr != "xmlns:xml") || +  (val != "http://www.w3.org/XML/1998/namespace")) { +  error("Invalid namespace declaration.\n"); +  } +  }    namespaces[attr[6..]] = val;    }    }    namespace_stack->push(namespaces);    // Now that we know what the namespaces are, we    // can expand the namespaces in the other attributes.    mapping(string:string) result = ([]);    foreach(attrs; string attr; string val) {    int i;    if (!has_prefix(attr, "xmlns:") && (i = search(attr, ":")) >= 0) {
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:221:    }       string Decode(string name)    {    int i = search(name, ":");    string key;    if (i >= 0) {    key = name[..i-1];    name = name[i+1..];    } -  if (has_prefix(key||"", "xml") || (name == "xmlns")) { +  if ((key || name) == "xmlns") {    if (key) name = key + ":" + name;    return name;    }    string prefix = namespace_stack->top()[key];    if (!prefix) {    if (key) {    error("Unknown namespace %O for tag %O\n",    key, name);    } else {    error("No default namespace, and tag without namespace: %O\n",
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:266:       void Leave()    {    namespace_stack->pop();    }   }      //! Base class for nodes.   class AbstractSimpleNode {    // Private member variables -  /* static */ array(AbstractSimpleNode) mChildren = ({ }); +  /* protected */ array(AbstractSimpleNode) mChildren = ({ });       //! Returns all the nodes children.    array(AbstractSimpleNode) get_children() { return (mChildren); }       //! Returns the number of children of the node.    int count_children() { return (sizeof(mChildren)); }       //! Returns an initialized copy of the node.    //! @note    //! The returned node has no children. -  AbstractSimpleNode low_clone() { +  optional AbstractSimpleNode low_clone() {    return AbstractSimpleNode();    }       //! Returns a clone of the sub-tree rooted in the node.    AbstractSimpleNode clone() {    AbstractSimpleNode n = low_clone();    foreach(mChildren, AbstractSimpleNode child)    n->add_child( child->clone() );    return n;    }
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:380:    mChildren -= ({ c });    }       //! Replaces the nodes children with the provided ones.    void replace_children(array(AbstractSimpleNode) children) {    mChildren = children;    }          //! Replaces the first occurrence of the old node child with -  //! the new node child. +  //! the new node child or children.    //! @note    //! The return value differs from the one returned    //! by @[Node()->replace_child()].    //! @returns    //! Returns the current node on success, and @expr{0@} (zero)    //! if the node @[old] wasn't found.    AbstractSimpleNode replace_child(AbstractSimpleNode old, -  AbstractSimpleNode new) +  AbstractSimpleNode|array(AbstractSimpleNode) new)    {    int index = search(mChildren, old);    if (index < 0)    return 0; -  +  if( arrayp(new) ) +  mChildren = mChildren[..index-1] + new + mChildren[index+1..]; +  else    mChildren[index] = new;    return this;    }       //! Destruct the tree recursively. When the inheriting    //! @[AbstractNode] or @[Node] is used, which have parent pointers,    //! this function should be called for every tree that no longer is    //! in use to avoid frequent garbage collector runs.    void zap_tree()    {
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:505:    {    array res;       // Walk subtrees in document order and add to resulting list    res = include_self ? ({ this }) : ({ });    foreach(mChildren, AbstractSimpleNode child) {    res += child->get_descendants(1);    }    return (res);    } +  +  //! Optional factory for creating contained nodes. +  //! +  //! @param type +  //! Type of node to create. One of: +  //! @int +  //! @value XML_TEXT +  //! XML text. @[text] contains a string with the text. +  //! @value XML_COMMENT +  //! XML comment. @[text] contains a string with the comment text. +  //! @value XML_HEADER +  //! @tt{<?xml?>@}-header @[attr] contains a mapping with +  //! the attributes. +  //! @value XML_PI +  //! XML processing instruction. @[name] contains the name of the +  //! processing instruction and @[text] the remainder. +  //! @value XML_ELEMENT +  //! XML element tag. @[name] contains the name of the tag and +  //! @[attr] the attributes. +  //! @value XML_DOCTYPE +  //! @value DTD_ENTITY +  //! @value DTD_ELEMENT +  //! @value DTD_ATTLIST +  //! @value DTD_NOTATION +  //! DTD information. +  //! @endint +  //! +  //! @param name +  //! Name of the tag if applicable. +  //! +  //! @param attr +  //! Attributes for the tag if applicable. +  //! +  //! @param text +  //! Contained text of the tab if any. +  //! +  //! This function is called during parsning to create the various +  //! XML nodes. +  //! +  //! Define this function to provide application-specific XML nodes. +  //! +  //! @returns +  //! Returns one of +  //! @mixed +  //! @type AbstractSimpleNode +  //! A node object representing the XML tag. +  //! @type int(0..0) +  //! @expr{0@} (zero) if the subtree rooted here should be cut. +  //! @type zero +  //! @expr{UNDEFINED@} to fall back to the next level of parser +  //! (ie behave as if this function does not exist). +  //! @endmixed +  //! +  //! @note +  //! This function is only relevant for @[XML_ELEMENT] nodes. +  //! +  //! @note +  //! This function is not available in Pike 7.6 and earlier. +  //! +  //! @note +  //! In Pike 8.0 and earlier this function was only called in +  //! root nodes. +  optional this_program node_factory(int type, string name, +  mapping attr, string text);   }      //! Base class for nodes with parent pointers.   class AbstractNode {    inherit AbstractSimpleNode;    // Private member variables -  /* static */ AbstractNode mParent = 0; +  /* protected */ AbstractNode mParent = 0;       // Instruct Pike.count_memory to search three steps: mChildren (in    // VirtualNode also mAttrNodes) -> array value -> mParent.    constant pike_cycle_depth = 3;       // Public methods       //! Sets the parent node to @[parent].    void set_parent(AbstractNode parent) { mParent = parent; }   
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:535: Inside #if 0
  #if 0    protected void create()    {    error("Creating a plain AbstractNode.\n");    }   #endif /* 0 */       //! Returns an initialized copy of the node.    //! @note    //! The returned node has no children, and no parent. -  AbstractNode low_clone() +  optional AbstractNode low_clone()    {    return AbstractNode();    }       //! Clones the node, optionally connected to parts of the tree.    //! If direction is -1 the cloned nodes parent will be set, if    //! direction is 1 the clone nodes childen will be set.    AbstractNode clone(void|int(-1..1) direction) {    AbstractNode n = low_clone();    if(mParent && direction!=1)
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:615:       //! Variants of @[add_child], @[add_child_before] and    //! @[add_child_after] that doesn't set the parent pointer in the    //! newly added children.    //!    //! This is useful while building a node tree, to get efficient    //! refcount garbage collection if the build stops abruptly.    //! @[fix_tree] has to be called on the root node when the building    //! is done.    AbstractNode tmp_add_child(AbstractNode c) -  {::add_child (c); return c;} +  { +  ::add_child (c); return c; +  }    AbstractNode tmp_add_child_before (AbstractNode c, AbstractNode old) -  {return ::add_child_before (c, old);} +  { +  return ::add_child_before (c, old); +  }    AbstractNode tmp_add_child_after (AbstractNode c, AbstractNode old) -  {return ::add_child_after (c, old);} +  { +  return ::add_child_after (c, old); +  }       //! Fix all parent pointers recursively in a tree that has been    //! built with @[tmp_add_child].    void fix_tree()    {    foreach (mChildren, AbstractNode c) -  if (c->mParent != this_object()) { -  c->mParent = this_object(); +  if (c->mParent != this) { +  c->mParent = this;    c->fix_tree();    }    }       //! Removes all occurrences of the provided node from the called nodes    //! list of children. The removed nodes parent reference is set to null.    void remove_child(AbstractNode c)    {    ::remove_child(c);    c->mParent = 0;
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:656:    //! references are updated.    void replace_children(array(AbstractNode) children) {    foreach(mChildren, AbstractNode c)    c->mParent = 0;    ::replace_children(children);    foreach(mChildren, AbstractNode c)    c->mParent = this;    }       -  //! Replaces the first occurrence of the old node child with -  //! the new node child. All parent references are updated. +  //! Replaces the first occurrence of the old node child with the new +  //! node child or children. All parent references are updated.    //! @note    //! The returned value is NOT the current node.    //! @returns    //! Returns the new child node.    AbstractNode replace_child(AbstractNode old, -  AbstractNode new) +  AbstractNode|array(AbstractNode) new)    {    if (!::replace_child(old, new)) return 0;    new->mParent = this;    old->mParent = 0;    return new;    }       //! Replaces this node with the provided one.    //! @returns    //! Returns the new node. -  AbstractNode replace_node(AbstractNode new) { +  AbstractNode|array(AbstractNode) +  replace_node(AbstractNode|array(AbstractNode) new) +  {    mParent->replace_child(this, new);    return new;    }       //! Returns all preceding siblings, i.e. all siblings present before    //! this node in the parents children list.    array(AbstractNode) get_preceding_siblings()    {    array siblings;    int pos;
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1020:    set_short_namespaces();    if(preserve_roxen_entities)    low_render_xml(data, this, roxen_text_quote, roxen_attribute_quote);    else    low_render_xml(data, this, text_quote, attribute_quote);    return (string)data;    }       //! It is possible to cast a node to a string, which will return    //! @[render_xml()] for that node. -  mixed cast(string to) { -  if(to=="object") return this; +  protected mixed cast(string to) {    if(to=="string") return render_xml(); -  error( "Can not case Node to "+to+".\n" ); +  return UNDEFINED;    }       // FIXME: Consider moving this to the corresponding base node classes?    protected void low_render_xml(String.Buffer data, Node n,    function(string:string) textq, -  function(string:string) attrq, +  function(string,void|string:string) attrq,    void|mapping(string:string) namespace_lookup)    {    string tagname;    switch(n->get_node_type()) {    case XML_TEXT:    data->add(textq(n->get_text()));    break;       case XML_ELEMENT:    if (!sizeof(tagname = n->get_short_name()))    break;       data->add("<", tagname);    if (mapping attr = n->get_short_attributes()) {    foreach(sort(indices(attr)), string a) { -  data->add(" ", a, "='", attrq(attr[a]), "'"); +  if( has_value(attr[a], "'") ) +  data->add(" ", a, "=\"", attrq(attr[a], "'"), "\""); +  else +  data->add(" ", a, "='", attrq(attr[a], "\""), "'");    }    }    if (n->count_children())    data->add(">");    else    data->add("/>");    break;       case XML_HEADER:    data->add("<?xml");
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1169:    xml_header->get_attributes()->encoding = "utf-8";    }    return "utf-8";    }       void set_short_namespaces(void|mapping(string:string) forward_lookup,    void|mapping(string:string) backward_lookup)    {    if (!mTagName) return;    if (!forward_lookup) { -  forward_lookup = ([]); -  backward_lookup = ([]); +  forward_lookup = ([ +  "http://www.w3.org/XML/1998/namespace":"xml:", +  "http://www.w3.org/2000/xmlns/":"xmlns:", +  ]); +  backward_lookup = ([ +  "xml:":"http://www.w3.org/XML/1998/namespace", +  "xmlns:":"http://www.w3.org/2000/xmlns/", +  ]);    } else {    // Make sure changes aren't propagated backwards...    forward_lookup += ([]);    backward_lookup += ([]);    }    // First check if any namespaces are defined by this tag.    mapping attrs = get_attributes() || ([]);    mapping short_attrs = attrs + ([]);    foreach(indices(attrs), string attr_name) {    if (has_prefix(attr_name, "xmlns")) {
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1218:    // Check if there are any longer namespaces that might match.    foreach(forward_lookup; string long;) {    if (has_prefix(full_name, long) &&    (!found || (sizeof(found) < sizeof(long)))) {    found = long;    break;    }    }       if (found) { + #if 0 +  werror("Found: NS: %O <%s%s/>\n", +  found, forward_lookup[found], full_name[sizeof(found)..]); + #endif /* 0 */    mTagName = full_name[sizeof(found)..];    mNamespace = found;    mShortNamespace = forward_lookup[found];    } else { -  + #if 0 +  werror("No suitable namespace found for %O.\n", +  full_name); + #endif /* 0 */    // We need to allocate a short namespace symbol.    // FIXME: This is O(n²).    int i;    while(backward_lookup[mShortNamespace = ("NS"+i+":")]) {    i++;    }    backward_lookup[mShortNamespace] = mNamespace;    forward_lookup[mNamespace] = mShortNamespace;    attrs["xmlns:NS"+i] = mNamespace;    short_attrs["xmlns:NS"+i] = mNamespace;    }    }    } -  + #if 0 +  werror("attrs: %O\n" +  "short attrs: %O\n", +  attrs, short_attrs); + #endif /* 0 */    // Then set the short namespaces for any attributes.    foreach(indices(attrs), string attr_name) {    if (!has_prefix(attr_name, "xmlns:")) {    int i = -1;    int j;    while ((j = search(attr_name, ":", i + 1)) >= 0) {    i = j;    }    while ((j = search(attr_name, "/", i + 1)) >= 0) {    i = j;
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1266:    foreach (forward_lookup; string long;)    if (sizeof (long) > sizeof (ns) &&    has_prefix (attr_name, long)) {    ns = long;    i = sizeof (long) - 1;    break;    }       if (!(prefix = forward_lookup[ns])) {    // We need to allocate a short namespace symbol. + #if 0 +  werror("Namespace %O not found in %O\n", +  ns, forward_lookup); + #endif /* 0 */    // FIXME: This is O(n²).    int i;    while(backward_lookup[prefix = ("NS"+i+":")]) {    i++;    } -  backward_lookup[mShortNamespace] = ns; +  backward_lookup[prefix] = ns;    forward_lookup[mNamespace] = prefix;    attrs["xmlns:NS"+i] = ns;    short_attrs["xmlns:NS"+i] = ns; -  + #if 0 +  werror("New namespace: %O %O\n", prefix, ns); +  werror("Forward_lookup: %O\n" +  "Backward_lookup: %O\n" +  "mNamespace:%O\n", +  forward_lookup, +  backward_lookup, +  mNamespace); + #endif /* 0 */    }    m_delete(short_attrs, attr_name);    short_attrs[prefix + attr_name[i+1..]] = attrs[attr_name];    }    }    }    mShortAttributes = short_attrs;    }    // And then do it for all the children.    get_children()->set_short_namespaces(forward_lookup, backward_lookup);    }    -  +  protected Charset.Encoder get_encoder(string encoding) +  { +  return Charset.encoder(encoding)->set_replacement_callback(lambda(string c) +  { +  return sprintf("&#%x;", c[0]); +  }); +  } +     //! Creates an XML representation of the node sub tree. If the    //! flag @[preserve_roxen_entities] is set, entities on the form    //! @tt{&foo.bar;@} will not be escaped.    //!    //! @param namespace_lookup    //! Mapping from namespace prefix to namespace symbol prefix. -  +  //! +  //! @param encoding +  //! Force a specific output character encoding. By default the +  //! encoding set in the document XML processing instruction will +  //! be used, with UTF-8 as a fallback. Setting this value will +  //! change the XML processing instruction, if present.    string render_xml(void|int(0..1) preserve_roxen_entities, -  void|mapping(string:string) namespace_lookup) +  void|mapping(string:string) namespace_lookup, +  void|string encoding)    {    String.Buffer data = String.Buffer(); -  string encoding = get_encoding(); +  if( encoding ) +  { +  Node xml_header; +  if (sizeof(get_children()) && +  (xml_header = get_children()[0])->get_node_type()==XML_HEADER) +  xml_header->get_attributes()->encoding=encoding; +  } +  else +  encoding = get_encoding(); +     set_short_namespaces();    if(preserve_roxen_entities)    low_render_xml(data, this, roxen_text_quote,    roxen_attribute_quote,    namespace_lookup);    else    low_render_xml(data, this, text_quote, attribute_quote,    namespace_lookup); -  return Charset.encoder(encoding)->feed((string)data)->drain(); +  return get_encoder(encoding)->feed((string)data)->drain();    }       //! Creates an XML representation for the node sub tree and streams    //! the output to the file @[f]. If the flag @[preserve_roxen_entities]    //! is set, entities on the form @tt{&foo.bar;@} will not be escaped.    void render_to_file(Stdio.File f,    void|int(0..1) preserve_roxen_entities) {    object data = class (Stdio.File f, object encoder) {    void add(string ... args) {    encoder->feed(args[*]);    f->write(encoder->drain());    } -  } (f, Charset.encoder(get_encoding())); +  } (f, get_encoder(get_encoding()));    set_short_namespaces();    if(preserve_roxen_entities)    low_render_xml(data, this, roxen_text_quote,    roxen_attribute_quote);    else    low_render_xml(data, this, text_quote, attribute_quote);    }    -  /*static*/ void _add_to_text (string str) +  /*protected*/ void _add_to_text (string str)    // Only to be used internally from the parse callback.    {    mText += str;    }       string _sprintf(int t) {    return t=='O' && sprintf("%O(#%d:%s,%O)", this_program, mDocOrder,    get_type_name(get_node_type()), get_full_name());    }   }      //! XML node without parent pointers and attribute nodes.   class SimpleNode   {    inherit AbstractSimpleNode;    inherit VirtualNode;       // Needed for cross-overloading -  SimpleNode low_clone() +  optional SimpleNode low_clone()    {    return VirtualNode::low_clone();    }   }         //! XML node with parent pointers.   class Node   {    inherit AbstractNode;    inherit VirtualNode;       // Needed for cross-overloading -  Node low_clone() +  optional Node low_clone()    {    return VirtualNode::low_clone();    }       //! Returns the name of the element node, or the nearest element above if    //! an attribute node.    string get_tag_name()    {    // Fake ATTR nodes query their parent    return ((mNodeType == XML_ATTR) ? get_parent()->get_tag_name() : mTagName);
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1447:    pre = reverse(pre);    sscanf(post, "%s\n", post);    return "\nContext: " + pre + post + "\n";   }      // Used for debugging...   class WrappedSimple   {    inherit .Simple;    - #if (__REAL_VERSION__ > 7.65) -  // CMOD-based xml parser ==> we can overload internal functions. -  +     string lookup_entity(string entity)    {    string ret = ::lookup_entity(entity);    werror("lookup_entity(%O) ==> %O\n", entity, ret);    return ret;    }       void define_entity_raw(string entity, string raw)    {    werror("define_entity_raw(%O, %O)\n", entity, raw);    ::define_entity_raw(entity, raw);    } - #endif +    }      //! Mixin for parsing XML.   //!   //! Uses @[Parser.XML.Simple] to perform   //! the actual parsing.   class XMLParser   {    this_program add_child(this_program);    void create(int, string, mapping, string);       this_program doctype_node;    -  +  protected ADT.Stack container_stack = ADT.Stack(); +     void parse(string data,    void|mapping predefined_entities,    ParseFlags|void flags,    string|void default_namespace)    {    //.Simple xp = WrappedSimple();    .Simple xp = .Simple();       if (!(flags & PARSE_DISALLOW_RXML_ENTITIES))    xp->allow_rxml_entities(1);
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1512:    extras->force_lc = 1;    }    if (flags & PARSE_ENABLE_NAMESPACES) {    extras->xmlns = XMLNSParser();    if (default_namespace) {    // Set the default namespace.    extras->xmlns->namespace_stack->top()[0] = default_namespace;    }    }    catch( data=xp->autoconvert(data) ); +  container_stack = ADT.Stack();    foreach(xp->parse(data, parse_xml_callback,    sizeof(extras) && extras),    this_program child)    add_child(child);    };       if(err)    {    // If string msg is found we propagate the error. If error message    // contains " [Offset: 4711]" we add the input data to the string.
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1578:    //!    //! Overload this function to provide application-specific XML nodes.    //!    //! @returns    //! Returns a node object representing the XML tag,    //! or @expr{0@} (zero) if the subtree rooted in the    //! tag should be cut.    //!    //! @note    //! This function is not available in Pike 7.6 and earlier. -  protected this_program node_factory(int type, string name, +  //! +  //! @seealso +  //! @[node_factory_dispatch()], @[AbstractSimpleNode()->node_factory()] +  protected AbstractSimpleNode node_factory(int type, string name,    mapping attr, string text);    -  protected this_program|int(0..0) +  //! Dispatcher of @[node_factory()]. +  //! +  //! This function finds a suitable @[node_factory()] given the +  //! current parser context to call with the same arguments. +  protected AbstractSimpleNode node_factory_dispatch(int type, string name, +  mapping attr, string text) +  { +  foreach(reverse(values(container_stack)), AbstractNode n) { +  if (!n || !n->node_factory) continue; +  AbstractSimpleNode res = n->node_factory(type, name, attr, text); +  if (!undefinedp(res)) return res; +  } +  return node_factory(type, name, attr, text); +  } +  +  protected AbstractSimpleNode|int(0..0)    parse_xml_callback(string type, string name,    mapping attr, string|array contents,    mixed location, mixed ...extra)    { -  this_program node; +  AbstractSimpleNode node;    mapping short_attr = attr;       switch (type) {    case "":    case "<![CDATA[":    // Create text node -  return node_factory(XML_TEXT, "", 0, contents); +  return node_factory_dispatch(XML_TEXT, "", 0, contents);       case "<!--":    // Create comment node -  return node_factory(XML_COMMENT, "", 0, contents); +  return node_factory_dispatch(XML_COMMENT, "", 0, contents);       case "<?xml":    // XML header tag -  return node_factory(XML_HEADER, "", attr, ""); +  return node_factory_dispatch(XML_HEADER, "", attr, "");       case "<!ENTITY": -  return node_factory(DTD_ENTITY, name, attr, contents); +  return node_factory_dispatch(DTD_ENTITY, name, attr, contents);    case "<!ELEMENT": -  return node_factory(DTD_ELEMENT, name, 0, contents); +  return node_factory_dispatch(DTD_ELEMENT, name, 0, contents);    case "<!ATTLIST": -  return node_factory(DTD_ATTLIST, name, attr, contents); +  return node_factory_dispatch(DTD_ATTLIST, name, attr, contents);    case "<!NOTATION": -  return node_factory(DTD_NOTATION, name, attr, contents); +  return node_factory_dispatch(DTD_NOTATION, name, attr, contents);    case "<!DOCTYPE": -  return node_factory(XML_DOCTYPE, name, attr, contents); +  return node_factory_dispatch(XML_DOCTYPE, name, attr, contents);       case "<?":    // XML processing instruction -  return node_factory(XML_PI, name, attr, contents); +  return node_factory_dispatch(XML_PI, name, attr, contents);       case "<>":    // Create new tag node.    if (arrayp(extra) && sizeof(extra) && mappingp(extra[0])) {    // Convert tag and attribute names to lowercase    // if requested.    if (extra[0]->force_lc) {    name = lower_case(name);    attr = mkmapping(map(indices(attr), lower_case),    values(attr));
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1638:    }    // Parse namespace information of available.    if (extra[0]->xmlns) {    XMLNSParser xmlns = extra[0]->xmlns;    attr = xmlns->Enter(attr);    name = xmlns->Decode(name);    xmlns->Leave();    short_attr = UNDEFINED;    }    } -  node = node_factory(XML_ELEMENT, name, attr, ""); -  if (short_attr) node->set_short_attributes(short_attr); +  node = node_factory_dispatch(XML_ELEMENT, name, attr, ""); +  if (node && short_attr) node->set_short_attributes(short_attr);    return node;       case ">":    // Create tree node for this container    if (arrayp(extra) && sizeof(extra) && mappingp(extra[0])) {    // Convert tag and attribute names to lowercase    // if requested.    if (extra[0]->force_lc) {    name = lower_case(name);    attr = mkmapping(map(indices(attr), lower_case), values(attr));    short_attr = attr;    } -  // Parse namespace information of available. +  // Parse namespace information if available.    if (extra[0]->xmlns) {    XMLNSParser xmlns = extra[0]->xmlns;    name = xmlns->Decode(name);    attr = mkmapping(map(indices(attr), xmlns->Decode), values(attr));    xmlns->Leave();    short_attr = UNDEFINED;    }    } -  node = node_factory(XML_ELEMENT, name, attr, ""); +  node = container_stack->pop();       if (node) { -  +  // FIXME: Validate that the node has the expected content. +     // Add children to our tree node. We need to merge consecutive text    // children since two text elements can't be neighbors according to    // the W3 spec. This is necessary since CDATA sections are    // converted to text nodes which might need to be concatenated    // with neighboring text nodes.    Node text_node;    int(0..1) modified;       if (short_attr) node->set_short_attributes(short_attr);   
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:1712:    if (location && mappingp(location))    throw_error(contents + " [Offset: " + location->location + "]\n");    else    throw_error(contents + "\n");       case "<":    if (arrayp(extra) && sizeof(extra) && mappingp(extra[0]) &&    extra[0]->xmlns) {    XMLNSParser xmlns = extra[0]->xmlns;    attr = xmlns->Enter(attr); +  name = xmlns->Decode(name);    } -  +  container_stack->push(node_factory_dispatch(XML_ELEMENT, name, attr, ""));    return 0;       default:    // werror("Unknown XML type: %O: %O, %O\n", type, attr, contents);    return 0;    }    }   }      //
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:2049:    inherit DTDElementHelper;       protected SimpleNode low_clone()    {    return SimpleDTDElementNode(get_full_name(), get_expression());    }       //!    protected void create(string name, array expression)    { -  this_program::expression = expression; +  this::expression = expression;    ::create(DTD_ELEMENT, name, 0, "");    }   }      //!   class SimpleDTDAttlistNode   {    inherit SimpleNode;    protected SimpleNode low_clone()    {
pike.git/lib/modules/Parser.pmod/XML.pmod/Tree.pmod:2310:    inherit DTDElementHelper;       protected Node low_clone()    {    return DTDElementNode(get_full_name(), get_expression());    }       //!    protected void create(string name, array expression)    { -  this_program::expression = expression; +  this::expression = expression;    ::create(DTD_ELEMENT, name, 0, "");    }   }      //!   class DTDAttlistNode   {    inherit Node;    protected Node low_clone()    {