lestrrat-go / libxml2

Interface to libxml2, with DOM interface
MIT License
230 stars 56 forks source link

SetNamespace does not work for default namespace #43

Closed bergner closed 6 years ago

bergner commented 6 years ago

Lets say I want to build the following xml:

<pfx:root xmlns:pfx="http://some.uri">
  <elem xmlns="http://other.uri"/>
</pfx:root>

With go-libxml2 I would do this:

package main
import "github.com/lestrrat/go-libxml2/dom"

func main() {
    d := dom.CreateDocument()
    r, _ := d.CreateElement("root")
    r.SetNamespace("http://some.uri", "pfx", true)
    d.SetDocumentElement(r)
    e, _ := d.CreateElement("elem")
    e.SetNamespace("http://other.uri", "", true)
    r.AddChild(e)
    println(d.ToString(1, true))
}   

But that does not produce the expected xml, instead I get the following which is missing the xmlns declaration on <elem>

<pfx:root xmlns:pfx="http://some.uri">
  <elem/>
</pfx:root>

If I write the equivalent code in Perl with XML::LibXML that go-libxml2 is inspired by I get the correct result with a xmlns declaration on <elem>

#!/usr/bin/perl
use XML::LibXML;

$d = XML::LibXML::Document->new();
$r = $d->createElement('root');
$r->setNamespace('http://some.uri', 'pfx', 1);
$d->setDocumentElement($r);
$e = $d->createElement('elem');
$e->setNamespace('http://other.uri', '', 1);
$r->addChild($e);
print $d->toString(1, 1);
bergner commented 6 years ago

This patch shows promising results. Calling node.SetNamespace(uri, "", true) now adds an explicit xmlns="..." declaration.

diff --git a/clib/clib.go b/clib/clib.go
index 29c0159..478df6a 100644
--- a/clib/clib.go
+++ b/clib/clib.go
@@ -730,15 +730,18 @@ func XMLNewDocNode(doc PtrSource, ns PtrSource, local, content string) (uintptr,
 }

 func XMLNewNs(n PtrSource, nsuri, prefix string) (uintptr, error) {
+       var cprefix *C.xmlChar = nil
        nptr, err := validNodePtr(n)
        if err != nil {
                return 0, err
        }

        cnsuri := stringToXMLChar(nsuri)
-       cprefix := stringToXMLChar(prefix)
+       if prefix != "" {
+               cprefix = stringToXMLChar(prefix)
+               defer C.free(unsafe.Pointer(cprefix))
+       }
        defer C.free(unsafe.Pointer(cnsuri))
-       defer C.free(unsafe.Pointer(cprefix))

        nsptr := C.xmlNewNs(nptr, cnsuri, cprefix)
        if nsptr == nil {
diff --git a/dom/node_element.go b/dom/node_element.go
index aed7823..55c9387 100644
--- a/dom/node_element.go
+++ b/dom/node_element.go
@@ -44,9 +44,6 @@ func (n *Element) SetNamespace(uri, prefix string, activate ...bool) error {
        if uri == "" {
                return errors.New("missing uri for SetNamespace")
        }
-       if prefix == "" {
-               return errors.New("missing prefix for SetNamespace")
-       }

        ns, err := clib.XMLNewNs(n, uri, prefix)
        if err != nil {
lestrrat commented 6 years ago

@bergner Sorry for the non-responsiveness. For whatever reason I missed the notifications :/