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";
}
}
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.
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.
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.
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.
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);
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With