Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I assign a prefix to an XML node in default namespace in javascript?

Tags:

javascript

xml

I have an XML fragment that I parse using jQuery parseXML. Most nodes don't have prefixes, they are in the default namespace and some have a prefixes.

I need all the nodes that are in the default namespaces to be associated with a prefix instead. I've made sure that this prefix is already declared in the string version of the XML with a magical string replace (i.e. xmlns:my="http://mydefaulns.com" is declared at the root level when I load the XML.)

I tried the following:

var defaultNs="http://mydefaulns.com";
var xmlDoc = $.parseXML(stringXML);
$(xmlDoc).find("*").each(function() {
    if (this.namespaceURI=== defaultNs) {
        this.prefix = "my";
    }
}

But it has no impact, when I write the XML back there's still no prefix.

I've also tried to just load the XML and call:

xmlDoc.firstChild.removeAttribute("xmlns")

but the attribute wasn't removed so the prefixes were not magically updated.

At that point, I think the only way to get the result that I want would be to recreate all the nodes with the new prefixed name, copying all the attributes.

This seem really extreme, is there another way?

Input (string):

<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com"xmlns:other="http://other.com">
    <node1>Value</node1>
    <other:node2>Value2</other:node2>
</abc>

Desired output:

<my:abc xmlns:my="http://mydefaulns.com"xmlns:other="http://other.com">
    <my:node1>Value</my:node1>
    <other:node2>Value2</other:node2>
</my:abc>

The actual XML is more complex, but this gives you an idea.

I parse the XML with jQuery.parse and and get back the string version by using

function XMLDocumentToString(oXML) {
    if (typeof oXML.xml != "undefined") {
       return oXML.xml;
    } else if (XMLSerializer) {
        return (new XMLSerializer().serializeToString(oXML));
    } else {
        throw "Unable to serialize the XML";
    }    
 }
like image 326
Melanie Avatar asked Jan 09 '17 23:01

Melanie


People also ask

How do I use the namespace prefix in XML?

After you declare the prefix, you can use it to qualify elements and attributes in an XML document and associate them with the namespace URI. Because the namespace prefix is used throughout a document, it should be short in length. This example defines two BOOK elements.

How do I use prefixes in LINQ to XML?

If you declare the namespaces with specific prefixes, LINQ to XML will attempt to honor the namespace prefixes when serializing. To create an attribute that declares a namespace with a prefix, you create an attribute where the namespace of the name of the attribute is Xmlns, and the name of the attribute is the namespace prefix.

What is default namespace scope in XML?

Declaration scope. When you use multiple namespaces in an XML document, you can define one namespace as the default namespace to create a cleaner looking document. The default namespace is declared in the root element and applies to all unqualified elements in the document. Default namespaces apply to elements only, not to attributes.

How do I add a namespace prefix to a schema?

Conventions for schemas suggest that you use either xs or xsd as the prefix for the schema namespace. To control namespace prefixes, you insert attributes that declare namespaces. If you declare the namespaces with specific prefixes, LINQ to XML will attempt to honor the namespace prefixes when serializing.


2 Answers

No need to parse the xml string just use replace with regular expressions like:

var prefix = "my:";
stringXML = stringXML.replace(/(<\/?)(\w+)(?!:)(\b)/g, "$1" + prefix + "$2$3");

this will match any sequence of letters (valid tag name) just after a < or </ (< is necessary, / is optional) and not followed by a : but followed by \b.

var stringXML = '<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com"><node1>Value</node1><other:node2>Value2</other:node2></abc>';

console.log("BEFORE: ", stringXML);

var prefix = "my:";
stringXML = stringXML.replace(/(<\/?)(\w+)(?!:)(\b)/g, "$1" + prefix + "$2$3");

console.log("AFTER: ", stringXML);
like image 110
ibrahim mahrir Avatar answered Oct 14 '22 11:10

ibrahim mahrir


At that point, I think the only way to get the result that I want would be to recreate all the nodes with the new prefixed name, copying all the attributes.

Yes, that's exactly what you have to do if you want do do it cleanly.

The solutions using regular expressions are brittle. This is the example you gave:

<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com">
    <node1>Value</node1>
    <other:node2>Value2</other:node2>
</abc>

Now consider the following document, which equivalent to your original one:

<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com">
    <node1>Value</node1>
    <node2 xmlns="http://other.com">Value2</node2>
</abc>

The only thing that changed is how the element node2 which is in namespace http://other.com was assigned a namespace. In your original document it was through the other prefix, which was defined on the root node. Here it is by redefining the default namespace on node2. From the standpoint of XML, the two documents are the same. It does not matter how node2's namespace is defined. The problem though is that neither of the two regexp-based answers you got will convert this document properly.

Here is an implementation that manipulates the DOM tree to produce the final result:

var stringXML = '<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com">\n\
    <node1>Value</node1>\n\
    <other:node2>Value2</other:node2>\n\
</abc>';

var defaultNS = "http://mydefaulns.com";
var xmlDoc = $.parseXML(stringXML);

xmlDoc.firstChild.removeAttribute("xmlns");

// Make sure we do have xmlns:my defined.
xmlDoc.firstChild.setAttribute("xmlns:my",defaultNS);

function transformChildren(parent) {
  // We take a copy of childNodes before clearing it.
  var childNodes = Array.prototype.slice.call(parent.childNodes);

  while (parent.firstChild) {
    parent.removeChild(parent.firstChild);
  }

  var newChild;
  var limit = childNodes.length;
  for (var childIx = 0; childIx < limit; ++childIx) {
    newChild = undefined;
    var node = childNodes[childIx];
    if (node.nodeType === Node.ELEMENT_NODE && node.namespaceURI === defaultNS) {
      newChild = xmlDoc.createElementNS(defaultNS, "my:" + node.tagName);

      // Copy the attributes.
      var attributes = node.attributes;
      for (var attrIx = 0; attrIx < attributes.length; ++attrIx) {
        var attr = attributes[attrIx];
        newChild.setAttributeNS(attr.namespaceURI, attr.name, attr.value)
      }

      // Move the children.
      while (node.firstChild) {
        newChild.appendChild(node.firstChild);
      }
      
      transformChildren(newChild);
    }
    
    parent.appendChild(newChild || node);
  }
}

transformChildren(xmlDoc);

// This is just reused from the question.
function XMLDocumentToString(oXML) {
  if (typeof oXML.xml != "undefined") {
    return oXML.xml;
  } else if (XMLSerializer) {
    return (new XMLSerializer().serializeToString(oXML));
  } else {
    throw "Unable to serialize the XML";
  }
}

console.log(XMLDocumentToString(xmlDoc));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
like image 39
Louis Avatar answered Oct 14 '22 11:10

Louis