After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 737682 - Adding node with default namespace results in bad namespace association when root node defines different default namespace
Adding node with default namespace results in bad namespace association when ...
Status: RESOLVED FIXED
Product: libxml++
Classification: Bindings
Component: General
git master
Other Linux
: Normal normal
: ---
Assigned To: Christophe de Vienne
Christophe de Vienne
Depends on: 738112
Blocks:
 
 
Reported: 2014-09-30 20:27 UTC by mathias_lorente
Modified: 2014-10-10 15:18 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Tests and patch (6.41 KB, patch)
2014-09-30 20:53 UTC, mathias_lorente
none Details | Review
Second patch + tests (9.96 KB, patch)
2014-10-04 23:37 UTC, mathias_lorente
none Details | Review
patch: Element::set_namespace_declaration(): Update associated node (2.32 KB, patch)
2014-10-09 12:11 UTC, Kjell Ahlstedt
none Details | Review
patch: Add dom_update_namespace example (9.88 KB, patch)
2014-10-09 12:12 UTC, Kjell Ahlstedt
none Details | Review

Description mathias_lorente 2014-09-30 20:27:47 UTC
Steps to reproduce:

- Parse following XML file:

<?xml version="1.0"?>
<root xmlns="http://default.namespace/root">
<child/>
</root>

- Apply following XPath on the parsed document:

/ns0:root/ns0:child  (ns0 <-> http://default.namespace/root)

- Resulting nodeSet is composed of one node.

- Add new child node to root node:

xmlpp::Document* document = parser.get_document();
xmlpp::Element* root = document->get_root_node();
xmlpp::Element* child = root->add_child("child");
child->set_namespace_declaration("http://alternate.default.namespace/child", "");

- Apply previous XPath on updated document:

/ns0:root/ns0:child  (ns0 <-> http://default.namespace/root)

- Resulting nodeSet is composed of TWO nodes!

- If you serialize the document, parse it again and apply the same XPath, resulting nodeSet is composed of only one node!
Comment 1 mathias_lorente 2014-09-30 20:53:38 UTC
Created attachment 287481 [details] [review]
Tests and patch
Comment 2 Kjell Ahlstedt 2014-10-02 09:31:10 UTC
There is a bug here, but your patch does not completely fix it, and it
introduces another bug.

Assume that we start by parsing this file:

<?xml version="1.0" encoding="UTF-8"?>
<myns:root xmlns:myns="http://default.namespace/root">
  <myns:child/>
</myns:root>

Then execute:
  xmlpp::Element* child = root->add_child("child", "myns");
  child->set_namespace_declaration(nsmap["ns1"], "");

With your patch,
  child->set_namespace("");
would now be executed as part of the call to set_namespace_declaration(),
changing the namespace of child from myns to the default namespace.

Another problem is that in a larger XML document, child might have children.
If the added namespace declaration overrides a namespace declaration in
an ancestor node, some of the child's children should perhaps be affected.

This looks like a libxml2 bug. xmlNewNs() should fix this, don't you think?
Comment 3 mathias_lorente 2014-10-04 23:17:48 UTC
You're absolutely right!

In fact I would really prefer API upgrade:

Currently when you create a node you can only provide its name and an already defined namespace prefix.

So if you want to create new node with new namespace you have to:
- create the node,
- set new namespace declaration (prefix + uri) to the new node,
- set appropriate namespace prefix to the node.

I think this is really bad.
Libxml++ should provide a better interface for libxml2.
I suggest to add a method allowing to do the job in one call, for example:

add_child(const Glib::ustring& node_name, const Glib:ustring& namespace_uri, const Glib::ustring& namespace_prefix)

With such method, you can add any new node in any new or existing namespace with any namespace prefix.

Probably, it would also be interesting to add method allowing to create new node using current context (ie same namespace as its parent).
Comment 4 mathias_lorente 2014-10-04 23:31:54 UTC
Regarding my patch, I think it would be easy to check whether current namespace prefix is empty or not. If so, child->set_namespace("") could be called safely...

This sounds good for node creation but not for node update. But this is also the case for namespace with prefix declared:

- Let's suppose following XML file is parsed:

<?xml version="1.0" encoding="UTF-8"?>
<ns0:root xmlns:ns0="http://default.namespace/root">
  <ns0:child>
    <ns0:sub_child/>
  </ns0:child>
</ns0:root>

- If, child node is retrieved and its namespace is modified:

child->set_namespace_declaration("http://other.namespace", "ns0")

- Then following XML can be dumped:

<?xml version="1.0" encoding="UTF-8"?>
<ns0:root xmlns:ns0="http://default.namespace/root">
  <ns0:child xmlns:ns0="http://other.namespace">
    <ns0:sub_child/>
  </ns0:child>
</ns0:root>

- So by modifying namespace of the child node, namespace of the sub-child is also modified. Well, in fact you need to dump it and parse it again. In fact, sub_child node namespace is still "http://default.namespace/root".
I suppose this can be considered as a bug in libxml2.
Comment 5 mathias_lorente 2014-10-04 23:36:07 UTC
I upload an updated version of my patch with new tests.

This patch doesn't solve problem for sub-nodes which can be impacted by namespace modification on one of its ancestor.
Comment 6 mathias_lorente 2014-10-04 23:37:04 UTC
Created attachment 287735 [details] [review]
Second patch + tests
Comment 7 Kjell Ahlstedt 2014-10-06 08:08:42 UTC
(In reply to comment #3)
> Libxml++ should provide a better interface for libxml2.
> I suggest to add a method allowing to do the job in one call, for example:
>
> add_child(const Glib::ustring& node_name, const Glib:ustring& namespace_uri,
> const Glib::ustring& namespace_prefix)

That's a good idea. I can make a patch. Or would you like to do it?

> Probably, it would also be interesting to add method allowing to create new
> node using current context (ie same namespace as its parent).

That's reasonably easy to do with the existing add_child().

  parent->add_child("child_a", parent->get_namespace_prefix());

Or, if you add many children:

  Glib::ustring parent_ns = parent->get_namespace_prefix();
  parent->add_child("child_a", parent_ns);
  parent->add_child("child_b", parent_ns);
  ...

Is it really worth 3 more methods to get rid of the parent_ns parameter?
(There are two add_child() and one add_child_before().)

(In reply to comment #6)
> Second patch + tests

I'm sure this patch works as intended, but I hesitate to push it.
It looks like a work-around for a bug in xmlNewNs(). I can't find an existing
relevant libxml2 bug. We should file a libxml2 bug, and see if we get a
reaction.
Comment 8 Kjell Ahlstedt 2014-10-07 19:44:50 UTC
I have filed libxml2 bug 738112.
Comment 9 Kjell Ahlstedt 2014-10-09 12:11:11 UTC
Created attachment 288115 [details] [review]
patch: Element::set_namespace_declaration(): Update associated node

xmlNodeNs() will probably not be changed. I'll add your patch, but I want to
change it as shown in the attached patch.
Comment 10 Kjell Ahlstedt 2014-10-09 12:12:19 UTC
Created attachment 288116 [details] [review]
patch: Add dom_update_namespace example

I'd also like to change your test case.
I've removed much of the emphasis on default namespaces. It can be used for
testing other namespaces too, after more add_child() functions have been added.
Comment 11 Kjell Ahlstedt 2014-10-10 15:18:58 UTC
I have pushed the patches in comments 9 and 10 with minor changes.
I have also pushed a patch that adds the methods

  Element* Node::add_child_with_new_ns(const Glib::ustring& name,
    const Glib::ustring& ns_uri,
    const Glib::ustring& ns_prefix = Glib::ustring());
  Element* Node::add_child_with_new_ns(xmlpp::Node* previous_sibling,
    const Glib::ustring& name,
    const Glib::ustring& ns_uri,
    const Glib::ustring& ns_prefix = Glib::ustring());
  Element* Node::add_child_before_with_new_ns(xmlpp::Node* next_sibling,
    const Glib::ustring& name,
    const Glib::ustring& ns_uri,
    const Glib::ustring& ns_prefix = Glib::ustring());

and a patch where the first one of these is called in the dom_update_namespace
example program.