11

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";
    }    
 }
1
  • Can you post a small sample input, output, and what you mean by writing the xml back? Commented Jan 10, 2017 at 2:25

3 Answers 3

5

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

Sign up to request clarification or add additional context in comments.

1 Comment

Not sure how much it is used, but XML node can have more caracters than the regex word class (for example, été is a valid name for an element)
4
+50

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>

Comments

1

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>

4 Comments

I agree it is a bit hacky, but from my testing yet there is no elegant solution! :/ We'll see if someone comes up with something else! Since we use a global replace, we could cache the tagname that have been processed. I usually use nodeName on the xml node. What is the difference with tagName?
@Melanie tagName has same value as nodeName, tagName exists only on Element, nodeName on any Node developer.mozilla.org/en-US/docs/Web/API/Element/tagName
i think its simple and flexible for more complex xml
This has the advanges over ibrahim mahir solution that it will workd for any elements name.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.