How To change XML namespace of certain element

16,069

Solution 1

You can just parse the whole XML as a string and insert namespaces where appropriate. This solution, however, can create lots of new strings only used within the algorithm, which is not good for the performance. However, I've written a function parsing it in this manner and it seems to run quite fast for sample XML you've posted ;). I can post it if you would like to use it.

Another solution is loading XML as XmlDocument and taking advantage of the fact it's a tree-like structure. This way, you can create a method recursively adding appropriate namespaces where appropriate. Unfortunately, XmlNode.Name attribute is read-only and that's why you have to manually copy the entire structure of the xml to change names of some nodes. I don't have time to write the code right now, so I just let you write it. If you encounter any issues with it, just let me know.

Update

I've tested your code and code suggested by Jeff Mercado and both of them seem to work correctly, at least in the sample XML you've posted in the question. Make sure the XML you are trying to parse is the same as the one you've posted.

Just to make it work and solve adding namespace issue originally asked, you can use the code, which handles the whole XML as a String and parses it manually:

private static String UpdateNodesWithDefaultNamespace(String xml, String defaultNamespace)
    {
        if (!String.IsNullOrEmpty(xml) && !String.IsNullOrEmpty(defaultNamespace))
        {
            int currentIndex = 0;

            while (currentIndex != -1)
            {
                //find index of tag opening character
                int tagOpenIndex = xml.IndexOf('<', currentIndex);

                //no more tag openings are found
                if (tagOpenIndex == -1)
                {
                    break;
                }

                //if it's a closing tag
                if (xml[tagOpenIndex + 1] == '/')
                {
                    currentIndex = tagOpenIndex + 1;
                }
                else
                {
                    currentIndex = tagOpenIndex;
                }

                //find corresponding tag closing character
                int tagCloseIndex = xml.IndexOf('>', tagOpenIndex);
                if (tagCloseIndex <= tagOpenIndex)
                {
                    throw new Exception("Invalid XML file.");
                }

                //look for a colon within currently processed tag
                String currentTagSubstring = xml.Substring(tagOpenIndex, tagCloseIndex - tagOpenIndex);
                int firstSpaceIndex = currentTagSubstring.IndexOf(' ');
                int nameSpaceColonIndex;
                //if space was found
                if (firstSpaceIndex != -1)
                {
                    //look for namespace colon between tag open character and the first space character
                    nameSpaceColonIndex = currentTagSubstring.IndexOf(':', 0, firstSpaceIndex);
                }
                else
                {
                    //look for namespace colon between tag open character and tag close character
                    nameSpaceColonIndex = currentTagSubstring.IndexOf(':');
                }

                //if there is no namespace
                if (nameSpaceColonIndex == -1)
                {
                    //insert namespace after tag opening characters '<' or '</'
                    xml = xml.Insert(currentIndex + 1, String.Format("{0}:", defaultNamespace));
                }

                //look for next tags after current tag closing character
                currentIndex = tagCloseIndex;
            }
        }

        return xml;
    }

You can check this code out in order to make you app working, however, I strongly encourage you to determine why the other solutions suggested didn't work.

Solution 2

@JeffMercado's solution didn't work for me (probably since I didn't have a default namespace).

I ended up using:

XNamespace ns = Constants.Namespace;
el.Name = (ns + el.Name.LocalName) as XName;

To change the namespace of a whole document I used:

private void rewriteNamespace(XElement el)
{
    // Change namespace
    XNamespace ns = Constants.Namespace;
    el.Name = (ns + el.Name.LocalName) as XName;

    if (!el.HasElements)
        return;

    foreach (XElement d in el.Elements())
        rewriteNamespace(d);
}

Usage:

var doc = XDocument.parse(xmlStr);
rewriteNamespace(doc.Root)

HTH

Solution 3

Since in this case you have a default namespace defined, you could just remove the default namespace declaration and add a new declaration for your new prefix using the old namespace name, effectively replacing it.

var prefix = "mailxml";
var content = XElement.Parse(xmlStr);
var defns = content.GetDefaultNamespace();
content.Attribute("xmlns").Remove();
content.Add(new XAttribute(XNamespace.Xmlns + prefix, defns.NamespaceName));
Share:
16,069
Kamran Shahid
Author by

Kamran Shahid

Expertise in performing multiple roles as .Net solutions architect, designer, technical lead and handled multiple overseas projects, also performed as analyst, technical manager and senior developer role etc., to build extensive, robust enterprise applications. Efficient experience in client management and quickly analyzing the complexity of business process, enhancements, releases with customer’s expectations leading to their satisfaction and retention. Extensive experience in designing and implementing the application development, enhancements and support tracks by following different methodologies like agile, waterfall, spring model etc. High leverage in architect and designing applications by using different and object oriented programming techniques.Experience of 9+ years with expertise in design and developing applications using C#, ASP.NET,Web api, AJAX, ADO.NET, XML, Web/WCF Services, JavaScript, e.t.c. Always ready for challenging opportunity

Updated on July 06, 2022

Comments

  • Kamran Shahid
    Kamran Shahid almost 2 years

    I have some set of xml generated via xmlserialization of some WCF messages. Now I want to make a generic method in which I will provide an xml filename and a prefix like mailxml12. Then in xml file those elements that don't have any namespace prefix in their name should be replaced with mailxml12:

    Like source file is:

    <DeliveryApptCreateRequest d2p1:ApptType="Pallet" d2p1:PickupOrDelivery="Delivery" d2p1:ShipperApptRequestID="4490660303D5" d2p1:SchedulerCRID="234234" xmlns:d2p1="http://idealliance.org/Specs/mailxml12.0a/mailxml_defs" xmlns="http://idealliance.org/Specs/mailxml12.0a/mailxml_tm">
    <SubmittingParty d2p1:MailerID6="123446" d2p1:CRID="342343" d2p1:MaildatUserLicense="A123" />
    <SubmittingSoftware d2p1:SoftwareName="asds" d2p1:Vendor="123" d2p1:Version="12" />
    <SubmitterTrackingID>2CAD3F71B4405EB16392</SubmitterTrackingID>
    <DestinationEntry>No</DestinationEntry>
    <OneTimeAppt>
      <PreferredAppt>2012-06-29T09:00:00Z</PreferredAppt>
    </OneTimeAppt>    
    <TrailerInfo>
      <Trailer>
        <TrailerNumber>A</TrailerNumber>
        <TrailerLength>20ft</TrailerLength>
      </Trailer>
      <Carrier>
        <CarrierName>N/A</CarrierName>
        <URL>http://test.com</URL>
      </Carrier>
      <BillOfLadingNumber>N/A</BillOfLadingNumber>
    </TrailerInfo>   
    </DeliveryApptCreateRequest>
    

    After the desired method it should be changed into all element name which doesn't have prefix with mailxml:. Like DeliveryApptCreateRequest should become mailxml:DeliveryApptCreateRequest while element like d2p1:CompanyName should remain as it is.

    I have tried with following code

     private void RepalceFile(string xmlfile)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(xmlfile);
            var a = doc.CreateAttribute("xmlns:mailxml12tm");
            a.Value = "http://idealliance.org/Specs/mailxml12.0a/mailxml_tm";
            doc.DocumentElement.Attributes.Append(a);
            doc.DocumentElement.Prefix = "mailxml12tm";
    
            foreach (XmlNode item in doc.SelectNodes("//*"))
            {
                if (item.Prefix.Length == 0)
                    item.Prefix = "mailxml12tm";
            }
            doc.Save(xmlfile);
        }
    

    only problem with it is that root element remain as it is while all are changed as i needed