Overloading namespaces in xml with envelope - c#

I have a program that iterates through XML Documents to search for values.
I want to extract the version number of the document I found and add it to the search result.
The problem I encounter is, that the documents have a lot of different namespaces, and most of them have an envelope. The envelope has the same namespaceprefix like the divergent namespace in the payload of the document.
I am struggeling to extract values from the payload, because I am not able to get the namespace of the inner document.
if (myX != null)
{
XNamespace myNs;
if (myX.Root.FirstAttribute == null)
myNs = "";
else
{
myNs = myX.Root.FirstAttribute.Value;
var dummy2 = myX.Root.Descendants("Message").First().FirstAttribute;
var dummy1 = myX.Root.Descendants(myNs + "Message").First().FirstAttribute;
}
foreach (XNode xN in myX.Root.DescendantNodes())
{
if (xN is XElement)
{
if (!wildcard)
{
if (((XElement)xN).Value == value)
{
myResultSet[0].Add(new ResultSet(file, myX.Root.Descendants(myNs + "MessageVersion").First().Value));
}
}
else if (wildcard)
{
if (((XElement)xN).Value.Contains(value))
{
myResultSet[0].Add(new ResultSet(file, myX.Root.Descendants(myNs + "MessageVersion").First().Value));
}
}
}
}
}
I can get the ns0:Body Element, but I fail to get anything with the "http://dummy.schema.com/ss2/schemas/2.5/DESADV" namespace.
The XML is something like this:
<ns0:Envelope xmlns:ns0="http://dummy.schema.com/ss2/schemas/2.3">
<ns0:SenderILN>123456789123</ns0:SenderILN>
<ns0:ReceiverILN>987654321987</ns0:ReceiverILN>
<ns0:EnvelopeNumber>12345</ns0:EnvelopeNumber>
<ns0:Body>
<ns0:Message xmlns:ns0="http://dummy.schema.com/ss2/schemas/2.5/DESADV">
<ns0:Header>
<ns0:MessageType>DESADV</ns0:MessageType>
<ns0:SenderILN>123456789123</ns0:SenderILN>1
<ns0:ReceiverILN>987654321987</ns0:ReceiverILN>
<ns0:MessageVersion>2.5</ns0:MessageVersion>
</ns0:Header>
<ns0:DESADV>
<ns0:Payload />
</ns0:DESADV>
</ns0:Message>
</ns0:Body>
</ns0:Envelope>
Is there a possibility to get to the inner message without removing the envelope first?

I'm not sure why you're trying to 'extract' the namespace. The namespace is part of the element name - just as you know the local name MessageVersion you should also know its namespace.
You should be doing something like this:
XNamespace ns = "http://dummy.schema.com/ss2/schemas/2.5/DESADV"
var version = (string) doc
.Descendants(ns + "MessageVersion")
.Single();
If for some reason you don't know or don't care about the namespace, you can just search by local name:
var version = (string) doc
.Descendants()
.Single(x => x.Name.LocalName == "MessageVersion");

if (((XElement)xN).Value == value)
{
XNamespace myNs = ((XElement)xN).Name.Namespace;
myResultSet[0].Add(new ResultSet(file, myX.Root.Descendants(myNs + "MessageVersion").First().Value));
}
After starting from the result and not the document itself it was quite easy to extract the namespace and the value.
But this works only, because I am brute forcing the document. I found no solution to do it in a sober way... Still looking for a better solution.

Related

How to extract xml child element

I am trying to figure out the code to extract xml child (I think this is worded correctly) elements. I have searched and tried many samples but cannot find how to drill down to pick out the section I want and return the information I need. Maybe I all I need is someone to define the data I am trying to pull so I can read up on the issue, of course any code would be very helpful and I will figure it out from there. Thanks in advanced for any help!
Here is the xml file. I am trying to run an if statement to find the section named <STATISTICTYPE>PVCAP_CharactersSaved</STATISTICTYPE> and return the <JOBNAME>,<TIMEDELTA>,<VALUESUM>.
<?xml version="1.0" encoding="utf-8"?>
<PVCAPTURESTATISTICCONTAINTER xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PVCAPTUREJOBSTATISTICS>
<PVCAPTURESTATISTICSUMMARY>
<STATISTICTYPE>PVCAP_CharactersSaved</STATISTICTYPE>
<STATISTICNAME>Characters saved</STATISTICNAME>
<JOBID>24</JOBID>
<JOBNAME>HEAT FILES</JOBNAME>
<TIMEDELTA>422</TIMEDELTA>
<VALUESUM>25432</VALUESUM>
</PVCAPTURESTATISTICSUMMARY>
<PVCAPTURESTATISTICSUMMARY>
<STATISTICTYPE>PVCAP_CharactersSaved_NoMM</STATISTICTYPE>
<STATISTICNAME>Characters saved (no match and merge)</STATISTICNAME>
<JOBID>24</JOBID>
<JOBNAME>HEAT FILES</JOBNAME>
<TIMEDELTA>422</TIMEDELTA>
<VALUESUM>25432</VALUESUM>
</PVCAPTURESTATISTICSUMMARY>
</PVCAPTUREJOBSTATISTICS>
<DOCUMENTCOUNT>762</DOCUMENTCOUNT>
<PAGECOUNT>3194</PAGECOUNT>
<IMAGECOUNT>3194</IMAGECOUNT>
<VERSION>2.0</VERSION>
</PVCAPTURESTATISTICCONTAINTER>
You can use LINQ to XML, particularly the XElement class.
var element = XElement.Parse(xmlStr).Element("PVCAPTUREJOBSTATISTICS")
.Elements("PVCAPTURESTATISTICSUMMARY")
.First(c => c.Element("STATISTICTYPE").Value == "PVCAP_CharactersSaved")
var jobName = element.Element("JOBNAME").Value;
var timeDelta = element.Element("TIMEDELTA").Value;
var valueSum = element.Element("VALUESUM").Value;
You'll want to add in some error handling and whatnot here, but this should get you going in the right direction.
You can do something like this:
XElement res = XElement.Parse(xmlResult);
foreach(var elem in res.Element("PVCAPTUREJOBSTATISTICS").Elements("PVCAPTURESTATISTICSUMMARY"))
{
if (elem.Element("STATISTICTYPE").Value.Equals("PVCAP_CharactersSaved", StringComparison.Ordinal))
{
string jobName = elem.Element("JOBNAME").Value;
string timeDelta = elem.Element("TIMEDELTA").Value;
string valueSum = elem.Element("VALUESUM").Value;
}
}
You can use XDocument and LINQ-to-XML to do that quite easily, for example :
string xml = "your xml content here";
XDocument doc = XDocument.Parse(xml);
//or if you have the xml file instead :
//XDocument doc = XDocument.Load("path_to_xml_file.xml");
var result = doc.Descendants("PVCAPTURESTATISTICSUMMARY")
.Where(o => (string) o.Element("STATISTICTYPE") == "PVCAP_CharactersSaved")
.Select(o => new
{
jobname = (string) o.Element("JOBNAME"),
timedelta = (string) o.Element("TIMEDELTA"),
valuesum = (string) o.Element("VALUESUM")
});
foreach (var r in result)
{
Console.WriteLine(r);
}

Change XML node with same name?

everyone!
I have an XML file and need to change the value of a node, specifically the indicated line. The problem i have is that as you can see, there are many nodes.
How can i change this line? This XML file could be much larger, so i am looking for a solution that would take different amounts of 'launch.file' nodes into account.
The node that will need to be set to True will be identified by the corresponding NAME tag. So if i typed in ULTII, the DISABLED node for that block will be set to True. If i typed in Catl, then the DISABLED node for that block would be changed.
<?xml version="1.0" encoding="windows-1252"?>
<SBase.Doc Type="Launch" version="1,0">
<Descr>Launch</Descr>
<Filename>run.xml</Filename>
<Disabled>False</Disabled>
<Launch.ManualLoad>False</Launch.ManualLoad>
<Launch.File>
<Name>Catl</Name>
<Disabled>False</Disabled>
<ManualLoad>False</ManualLoad>
<Path>ft\catl\catl.exe</Path>
</Launch.File>
<Launch.File>
<Disabled>False</Disabled> <!-- change to True -->
<ManualLoad>False</ManualLoad>
<Name>ULTII</Name>
<Path>F:\ULTII.exe</Path>
<NewConsole>True</NewConsole>
</Launch.File>
<Launch.File>
<Name>ECA</Name>
<Disabled>False</Disabled>
<Path>C:\ECA.exe</Path>
</Launch.File>
</SBase.Doc>
I am using Visual Studio 2012, should you need to know.
Thank you to anyone who can help me out on this, i really appreciate it.
Heres my method to do what you want
private void DisableLaunchFile(string xmlfile, string launchFileName){
XDocument doc = XDocument.Load(xmlfile);
var launchFileElement = doc.Descendants("Launch.File").Where (d => d.Element("Name").Value == lauchFileName);
launchFileElement.Elements("Disabled").First().Value = true.ToString();
doc.Save(xmlfile);
}
Use it like:
string pathToXmlFile = //assign ;
DisableLaunchFile(pathToXmlFile, "Catl");
DisableLaunchFile(pathToXmlFile, "ULTII");
This can be achieved by using LINQ to XML (see XDocument Class).
Assuming that there is the single Launch.File element with Name element with value "ULTII":
var document = XDocument.Load(...);
var ultiiElement = document
.Descendants("Launch.File")
.Single(fileElement => fileElement.Element("Name").Value == "ULTII");
ultiiElement.Element("Disabled").Value = "True"; // or true.ToString()
document.Save(...);
This method will do the trick:
public void ChangeNode(string name, string filePath)
{
XDocument xDocument;
using (var streamReader = new StreamReader(filePath))
{
xDocument = XDocument.Parse(streamReader.ReadToEnd());
}
var nodes = xDocument.Descendants("Launch.File");
foreach (var node in nodes)
{
var nameNode = node.Descendants("Name").FirstOrDefault();
if (nameNode != null && nameNode.Value == name)
{
var disabledNode = node.Descendants("Disabled").FirstOrDefault();
if (disabledNode != null)
{
disabledNode.SetValue("True");
}
}
}
using (var streamWriter = new StreamWriter(filePath))
{
xDocument.Save(streamWriter);
}
}
The name you want to pass in is the name of the node that you want to change and the path is the file path to the xml file. So you might call it like:
ChangeNode("ULTII", "C:\\output.xml");
You may need to tidy this up a bit like matching the node name invariant of case or culture but it should get you started.

XDocument Descendants and Element always return null values

Hey all i have looked thoroughly through all the questions containing XDocument and while they are all giving an answer to what I'm looking for (mostly namespaces issues) it seems it just won't work for me.
The problem I'm having is that I'm unable to select any value, be it an attribute or element.
Using this XML
I'm trying to retrieve the speaker's fullname.
public void GetEvent()
{
var xdocument = XDocument.Load(#"Shared\techdays2013.xml");
XNamespace xmlns = "http://www.w3.org/2001/XMLSchema-instance";
var data = from c in xdocument.Descendants(xmlns + "speaker")
select c.Element(xmlns + "fullname").Value;
}
You can omit the namespace declaration in your linq statement.
public void GetEvent()
{
var xdocument = XDocument.Load(#"Shared\techdays2013.xml");
//XNamespace xmlns = "http://www.w3.org/2001/XMLSchema-instance";
var data = from c in xdocument.Descendants("speaker")
select c.Element("fullname").Value;
}
You can omit WebClient because you have direct local access to a file. I'm just showing a way to process your file on my machine.
void Main()
{
string p = #"http://events.feed.comportal.be/agenda.aspx?event=TechDays&year=2013&speakerlist=c%7CExperts";
using (var client = new WebClient())
{
string str = client.DownloadString(p);
var xml = XDocument.Parse(str);
var result = xml.Descendants("speaker")
.Select(speaker => GetNameOrDefault(speaker));
//LinqPad specific call
result.Dump();
}
}
public static string GetNameOrDefault(XElement element)
{
var name = element.Element("fullname");
return name != null ? name.Value : "no name";
}
prints:
Bart De Smet
Daniel Pearson
Scott Schnoll
Ilse Van Criekinge
John Craddock
Corey Hynes
Bryon Surace
Jeff Prosise
1) You have to drop the namespace
2) You'll have to query more precisely. All your <speaker> elements inside <speakers> have a fullname but in the next section I spotted <speaker id="94" />
A simple fix (maybe not the best) :
//untested
var data = from c in xdocument.Root.Descendants("speakers").Descendants("speaker")
select c.Element("fullname").Value;
You may want to specify the path more precise:
xdocument.Element("details").Element("tracks").Element("speakers").

How To change XML namespace of certain element

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

Using LINQ to XML to Process XML in Multiple Namespaces

I'm trying to parse results from the YouTube API. I'm getting the results correctly as a string, but am unable to parse it correctly.
I followed suggestions on a previous thread, but am not getting any results.
My sample code is:
string response = youtubeService.GetSearchResults(search.Term, "published", 1, 50);
XDocument xDoc = XDocument.Parse(response, LoadOptions.SetLineInfo);
var list = xDoc.Descendants("entry").ToList();
var entries = from entry in xDoc.Descendants("entry")
select new
{
Id = entry.Element("id").Value,
Categories = entry.Elements("category").Select(c => c.Value)
//Published = entry.Element("published").Value,
//Title = entry.Element("title").Value,
//AuthorName = entry.Element("author").Element("name").Value,
//Thumnail = entry.Element("media:group").Elements("media:thumnail").ToList().ElementAt(0)
};
foreach (var entry in entries)
{
// entry.Id and entry.Categories available here
}
The problem is that entries has a count of 0 even though the XDocument clearly has the valid values.
The value of the response variable (Sample XML) can be seen here: http://snipt.org/lWm
(FYI: The youTube schema is listed here: http://code.google.com/apis/youtube/2.0/developers_guide_protocol_understanding_video_feeds.html)
Can anyone tell me what I'm doing wrong here?
All the data is in the "http://www.w3.org/2005/Atom" namespace; you need to use this throughout:
XNamespace ns = XNamespace.Get("http://www.w3.org/2005/Atom");
...
from entry in xDoc.Descendants(ns + "entry")
select new
{
Id = entry.Element(ns + "id").Value,
Categories = entry.Elements(ns + "category").Select(c => c.Value)
...
};
etc (untested)
When you see prefix:name, it means that name is in the namespace whose prefix has been declared as prefix. If you look at the top of the document, you'll see an xmlns:media=something. The something is the namespace used for anything with the prefix media.
This means you need to create an XNamespace for each of the namespaces you need to reference:
XNamespace media = XNamespace.Get("http://search.yahoo.com/mrss/");
and then use media for the names in that namespace:
media + "group"
The namespaces in this document are:
xmlns="http://www.w3.org/2005/Atom"
xmlns:app="http://www.w3.org/2007/app"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/"
xmlns:gd="http://schemas.google.com/g/2005"
xmlns:gml="http://www.opengis.net/gml"
xmlns:yt="http://gdata.youtube.com/schemas/2007"
xmlns:georss="http://www.georss.org/georss"
You need to set the namespace.
Creating an XName in a Namespace
As with XML, an XName can be in a namespace, or it can be in no namespace.
For C#, the recommended approach for creating an XName in a namespace is to declare the XNamespace object, then use the override of the addition operator.
http://msdn.microsoft.com/en-us/library/system.xml.linq.xname.aspx

Categories