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