Melanie
Melanie

Reputation: 1208

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

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";
    }    
 }

Upvotes: 11

Views: 2653

Answers (3)

ibrahim mahrir
ibrahim mahrir

Reputation: 31712

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);

Upvotes: 5

Louis
Louis

Reputation: 151521

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>

Upvotes: 4

fingerpich
fingerpich

Reputation: 9330

this may help you

var inputXmlText='<abc xmlns="http://mydefaulns.com" xmlns:my="http://mydefaulns.com" xmlns:other="http://other.com"><node1>Value</node1><other:node2>Value2</other:node2></abc>'
var inputXml=jQuery.parseXML(inputXmlText);
var list=[];
var prefix="my";

$(inputXml).find("*").each(function(){
  if(this.tagName.indexOf(":")<0){
     inputXmlText=inputXmlText.replace(new RegExp("<" + this.tagName,'g') ,"<"+prefix+":" + this.tagName);
     inputXmlText=inputXmlText.replace(new RegExp("</" + this.tagName,'g') ,"</"+prefix+":" + this.tagName);  
  } 
});

console.log(inputXmlText);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 1

Related Questions