All Child Nodes in XML are linked to a Parent Node - c#

I am trying to fix a piece of code that is not doing what I want it to do. The code was left by an engineer who claimed it was working perfectly. He has been using XPATH instead of Linq, so for the short term I'm looking for a solution based on XPATH.
I've searched the web, but am unable to convert what I've found into a working solution.
The XML looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Orders>
<Order>
<OrderId>6175</OrderId>
<OrderNumber>6175</OrderNumber>
<OrderDate>2016-08-19 13:17:41</OrderDate>
<OrderLineItems>
<ItemName>Name of Item</ItemName>
<Quantity>1</Quantity>
<Meta/>
</OrderLineItems>
</Order>
</Orders>
The script he created with Visual Studio 2008 that concerns reading the XML looks like:
public override void CreateNewOutputRows()
{
string filename = Variables.strFileInLoop;
XmlDocument doc = new XmlDocument();
doc.Load(filename);
// loop orders
foreach (XmlNode shipmentNode in doc.DocumentElement.SelectNodes("/Orders/Order"))
{
OutputOrdersBuffer.AddRow();
String OrderId = GetNodeText("OrderId", shipmentNode).Trim();
OutputOrdersBuffer.OrderId = OrderId;
OutputOrdersBuffer.OrderNumber = GetNodeText("OrderNumber", shipmentNode).Trim();
OutputOrdersBuffer.OrderDate = GetNodeText("OrderDate", shipmentNode).Trim();
String replace = #"C:\Projects\Customername\IN\";
OutputOrdersBuffer.FileName = filename.Replace(replace, "");
foreach (XmlNode OrderLineItemNode in shipmentNode.SelectNodes("/Orders/Order/OrderLineItems"))
{
OutputOrderLinesBuffer.AddRow();
OutputOrderLinesBuffer.OrderId = OrderId;
OutputOrderLinesBuffer.ItemName = GetNodeText("ItemName", OrderLineItemNode).Trim();
OutputOrderLinesBuffer.Quantity = GetNodeText("Quantity", OrderLineItemNode).Trim();
OutputOrderLinesBuffer.Meta = GetNodeText("Meta", OrderLineItemNode).Trim();
}
}
}
What happens when we try to import an XML with multiple orders is that the code attaches all OrderLineItems to all Orders.
So if there are 10 unique orders in the file with 2 OrderLineItems it put out 10 Orders with 20 OrderLineItems each.
As I said, I've looked everywhere (I think) but am unable to convert what I've found to a solution where the OrderLineItems are only linked to the order they belong to.

The iteration within an order uses an XPath expression
/Orders/Order/OrderLineItems
that goes through the entire document every time, as the starting / in indicates. This is why all order line items are included in all orders.
Changing the XPath expression to take shipmentNode as the context item, which happens implicitly if the XPath expression does not begin with a /, and navigating from there to its OrderLineItems children should help:
foreach (XmlNode OrderLineItemNode in shipmentNode.SelectNodes("OrderLineItems"))
An analogy with a file system on the command line usually helps when starting with XPath: the context item behaves like a working directory, and one can either change the directory with an absolute path (starting with /), or with a relative path to the current directory (starting with ./, which also works in XPath). Of course XPath is different as it is set-oriented, but this analogy helps getting into the right mindset for navigation.

Related

Why I can't access to these node content using XPath?

I am pretty new in XPath and in C# and I have the following problem:
I have to parse this file: http://static.nvd.nist.gov/feeds/xml/cpe/dictionary/official-cpe-dictionary_v2.3.xml
As you can see opening it in the browser this file have the following structure:
<?xml version='1.0' encoding='UTF-8'?>
<cpe-list xmlns:meta="http://scap.nist.gov/schema/cpe-dictionary-metadata/0.2" xmlns:config="http://scap.nist.gov/schema/configuration/0.1" xmlns:ns6="http://scap.nist.gov/schema/scap-core/0.1" xmlns:scap-core="http://scap.nist.gov/schema/scap-core/0.3" xmlns="http://cpe.mitre.org/dictionary/2.0" xmlns:cpe-23="http://scap.nist.gov/schema/cpe-extension/2.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://scap.nist.gov/schema/configuration/0.1 http://nvd.nist.gov/schema/configuration_0.1.xsd http://cpe.mitre.org/dictionary/2.0 http://scap.nist.gov/schema/cpe/2.3/cpe-dictionary_2.3.xsd http://scap.nist.gov/schema/scap-core/0.3 http://nvd.nist.gov/schema/scap-core_0.3.xsd http://scap.nist.gov/schema/scap-core/0.1 http://nvd.nist.gov/schema/scap-core_0.1.xsd http://scap.nist.gov/schema/cpe-dictionary-metadata/0.2 http://nvd.nist.gov/schema/cpe-dictionary-metadata_0.2.xsd http://scap.nist.gov/schema/cpe-extension/2.3 http://scap.nist.gov/schema/cpe/2.3/cpe-dictionary-extension_2.3.xsd">
<generator>
<product_name>National Vulnerability Database (NVD)</product_name>
<product_version>2.22.0-SNAPSHOT (PRODUCTION)</product_version>
<schema_version>2.3</schema_version>
<timestamp>2014-03-05T05:13:33.550Z</timestamp>
</generator>
<cpe-item name="cpe:/a:1024cms:1024_cms:0.7">
<title xml:lang="en-US">1024cms.org 1024 CMS 0.7</title>
<cpe-23:cpe23-item name="cpe:2.3:a:1024cms:1024_cms:0.7:*:*:*:*:*:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:1024cms:1024_cms:1.2.5">
<title xml:lang="en-US">1024cms.org 1024 CMS 1.2.5</title>
<cpe-23:cpe23-item name="cpe:2.3:a:1024cms:1024_cms:1.2.5:*:*:*:*:*:*:*"/>
</cpe-item>
<cpe-item name="cpe:/a:1024cms:1024_cms:1.3.1">
<title xml:lang="en-US">1024cms.org 1024 CMS 1.3.1</title>
<cpe-23:cpe23-item name="cpe:2.3:a:1024cms:1024_cms:1.3.1:*:*:*:*:*:*:*"/>
</cpe-item>
.............................................................
.............................................................
.............................................................
<cpe-item name="cpe:/h:zyxel:p-660hw_t3:v2">
<title xml:lang="en-US">ZyXEL P-660HW T3 Model v2</title>
<cpe-23:cpe23-item name="cpe:2.3:h:zyxel:p-660hw_t3:v2:*:*:*:*:*:*:*"/>
</cpe-item>
</cpe-list>
So now, using XPath, I have to obtain the list of all tag (excluding the first tag situated as first tag into my tag
In my code I have something like it:
XmlDocument document = new XmlDocument(); // Represent an XML document
document.Load(sourceXML.FullName); // Loads the XML document from the specified stream
// Add the namespaces:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("ns6", "http://scap.nist.gov/schema/scap-core/0.1");
nsmgr.AddNamespace("cpe-23", "http://scap.nist.gov/schema/cpe-extension/2.3");
nsmgr.AddNamespace("ns", "http://cpe.mitre.org/dictionary/2.0");
nsmgr.AddNamespace("meta", "http://scap.nist.gov/schema/cpe-dictionary-metadata/0.2");
nsmgr.AddNamespace("scap-core", "http://scap.nist.gov/schema/scap-core/0.3");
nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
nsmgr.AddNamespace("config", "http://scap.nist.gov/schema/configuration/0.1");
/* nodeList is the collection that contains all the <cpe-item> tag that are
* inside the root <cpe-list> tag in the XML document:
*/
XmlNodeList nodeList;
nodeList = document.DocumentElement.SelectNodes("//ns:cpe-list/ns:cpe-item", nsmgr);
long conta = 0;
So I am using this line to select all the tag that are into the tag:
nodeList = document.DocumentElement.SelectNodes("//ns:cpe-list/ns:cpe-item", nsmgr);
It seems to work but I am not sure if it is correct because when I look into using the Visual Studio Debugger it say to me that my XmlNodeList nodeList contains: 80588 element (the file is very big but it seems to me to much element !!!)
Another doubt is related to the use of the ns namespace that is into my previouse code (this is not my code, I have to work on it).
Why in the previous code there is the ns namepace ahead the cpe-list and cpe-item if in the XML code to parse I smply have something like:
<cpe-item name="cpe:/a:1024cms:1024_cms:1.3.1">
<title xml:lang="en-US">1024cms.org 1024 CMS 1.3.1</title>
<cpe-23:cpe23-item name="cpe:2.3:a:1024cms:1024_cms:1.3.1:*:*:*:*:*:*:*"/>
</cpe-item>
that don't begin with ns namespace? Why is it used?
The last question is about how can I access to the title inner text content?
I am trying to do something like this but in this way can't work:
XmlNodeList nodeList;
nodeList = document.DocumentElement.SelectNodes("//ns:cpe-list/ns:cpe-item", nsmgr);
long conta = 0;
DataModel.Vulnerability.CPE currentCPE;
foreach (XmlNode node in nodeList)
{
// Access to the name ATTRIBUTE of the <cpe-item> tag:
Debug.WriteLine(String.Format("[{0:N0}] CPE: {1} Title: {2}", conta, node.Attributes["name"].Value, node.FirstChild.FirstChild.Value));
// Access to the <title> tag content:
//Debug.WriteLine(String.Format("[{0:N0}] Title: {1} Title: {2}", conta, node.SelectSingleNode("./title", nsmgr)));
XmlNode titleNode = node.SelectSingleNode("./title", nsmgr);
conta++;
}
When this code is executed I have no problem to access to the name attributes of the current cpe element into my list but I can't access to the content of the tag because when execute this line:
XmlNode titleNode = node.SelectSingleNode("./title", nsmgr);
it return that the value is null
What is the problem? What am I missing? How can I solve?
Tnx
Andrea
Your XPath looks fine given XML snippet posted in this question. It should return correct number of elements as far as I can see. Can't tell more than that, you should check further yourself.
Your XML has default namespace (xmlns="....."). All elements in XML without prefix considered in default namespace. But in XPath, all element without prefix considered has no namespace. In the end, that different paradigm of both platform requires you to define ns prefix that point to default namespace url for use in XPath statement.
Related to point 2. Remember that all element without prefix is in default namespace. So is <title> element. Hence you need to add ns prefix in the XPath statement : ./ns:title
Ideally, one post has to contains no more than one specific question. Answering a bunch of questions in one post is rarely useful for future visitors, it is tend to confuse them instead. Remember that we are not only solving your problem here, but also trying to build knowledge-base that hopefully useful for others having similar problem.

XDocument Add multiple XElements

In my Windows Phone 8 C#/XAML .NET 4.5 Project, I'm trying to create an XDocument with similar structure:
<element1>
<subelement1>
</subelement1>
<subelement2>
...etc...
</subelement2>
</element1>
<element2>
<subelement1>
</subelement1>
<subelement2>
...etc...
</subelement2>
</element2>
The method creating the document looks like (simplified for the question purposes):
... createXML()
{
XDocument doc = new XDocument();
XElement elem1 = new XElement("element1");
elem1.Add(new XElement("subelement1"));
XElement elem2 = new XElement("element2");
doc.Add(elem1);
doc.Add(elem2);
}
But I keep getting InvalidOperationException saying that it would create a invalid document structure.
I know why - it would cause the document to have multiple "root nodes" - but I effectively need it that way.
This structure is needed for webservice done by third party, which recieves the document as a string.
So the question is "How to achieve this structure? Should I use some other XObject instead?"
(I know that probably the most simple solution would be to use collection of XElements...just askin' if there is another way out of curiosity)
The structure that you specified at the top of the post is illegal, because valid XML documents must have a single root element; your document has two elements at the top level, which is not allowed.
You can solve this problem by adding a root element at creation time, and then discarding it when reading the document;
document = new XDocument(new XElement("root", elem1, elem2));

How to change the data within elements in a XML file using C#?

I'm kind of new to XML files in C# ASP.NET. I have a XML in the below format:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Installation>
<ServerIP>192.168.20.110</ServerIP>
<DB_Name>USTCKT1</DB_Name>
<Username>jorame</Username>
<Password>Cru$%e20</Password>
<Table_PreFix>TCK</Table_PreFix>
</Installation>
I need to change the values within each element. For example, when an user clicks I should be able to replace 192.168.20.110 with 192.168.1.12.
How can I accomplish this? Any help will be really appreciated.
You should look at using the methods in the XDocument class. http://msdn.microsoft.com/en-us/library/bb301598.aspx
Specifically look at the methods: Load(string) - to load an XML file, Element() - to access a specific element and Save(string) - to save the XML document. The page on Element() has some sample code which can help.
http://msdn.microsoft.com/en-us/library/system.xml.linq.xcontainer.element.aspx
You can do something like this using the XDocument class:
XDocument doc = XDocument.Load(file.xml);
doc.Element("Installation").Element("ServerIP").Value = "192.168.1.12";
//Update the rest of the elements
doc.Save(file.xml);
More Details
If you run into namespace issues when selecting your elements you will need to include the xml namespace in the XElement selectors eg doc.Element(namspace + "Installation")
In general, you can do it in the following steps:
Create a new XmlDocument object and load the content. The content might be a file or string.
Find the element that you want to modify. If the structure of your xml file is too complex, you can use xpath you find what you want.
Apply your modification to that element.
Update your xml file.
Here is a simple demo:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("file.xml"); // use LoadXml(string xml) to load xml string
string path = "/Installation/ServerIP";
XmlNode node = xmlDoc.SelectSingleNode(path); // use xpath to find a node
node.InnerText = "192.168.1.12"; // update node, replace the inner text
xmlDoc.Save("file.xml"); // save updated content
Hope it's helpful.

How to Retrieve value of xml node?

I have a xml file like this:
<Contacts>
<CommandID>
ShowInstalledProducts
</CommandID>
</Contacts>
I need to iterate through a list of xml files and retrieve the value of CommandId ( in this case ShowInstalledProducts), for each ...
I am very new to xml. can someone please help me with the same. I am trying to achieve this using Linq. (other solutions are also welcome though)
DirectoryInfo directoryInfo = new DirectoryInfo(#"T:\Commands");
FileInfo[] fileInfo = directoryInfo.GetFiles();
foreach (FileInfo loop in fileInfo)
{
string doc = File.ReadAllText(loop.FullName);
XmlDocument XMLDoc = new XmlDocument();
XMLDoc.Load(doc);
XMLDoc= stripDocumentNamespace(XMLDoc);
//id = XMLDoc.Descendants("CommandID").First().Value;
}
This is what i have done till now , im reading the files , and trying to ger the descendants. however there are multiple in each xml file , and i need to retrieve the value of each . stuck here :(
Step 1: Go to linqpad.net and download the Linqpad application. It is a simple editor allowing you to write, run, and play around with Linq expression. Also, it has a lot of built in examples to learn from. (You might have to select Help --> Veiw samples to open this):
Step 2: Paste the following code into the editor, and press F5 to run it (make sure C# Statement(s) is selected for "Language" above it though!). Play around and tweak it as you like / need.
var bench = XElement.Parse(#"<Contacts>
<Node>
Something
</Node>
<Node>
Something else
</Node>
</Contacts>");
var listOfNodes = bench.Elements();
listOfNodes.Dump();
var content = listOfNodes.Select(x => x.Value);
content.Dump();
This should be enough to get you started. Enjoy! :)
fileInfo.SelectMany(fi => XDocument
.Load(fi.FullName)
.Descendants("CommandID")
.Select(e=>e.Value))
This LINQ code should return the list of all CommandId values of all files.

XDocument.Descendants(itemName) - Problems finding qualified name

I'm trying to read a XML-RSS-Feed from a website. Therefore I use a async download and create a XDocument with the XDocument.Parse() Method.
The Document intends to be very simple, like this:
<root>
<someAttribute></SomeAttribute>
<item>...</item>
<item>...</item>
</root>
Now I want to read out all the items. Therefore I tried:
foreach (XElement NewsEntry in xDocument.Descendants("item"))
but this doesn't work. So I found a post in this board to use the qualified name, because there are some namespaces defined in the root element:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns="http://purl.org/rss/1.0/">
well, I tried all 3 available namespaces - nothing worked for me:
XName itemName = XName.Get("item", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
XName itemName2 = XName.Get("item", "http://purl.org/dc/elements/1.1/");
XName itemName3 = XName.Get("item", "http://purl.org/rss/1.0/modules/syndication/");
Any help would be appreciated.
(Usually I'm doing the XML-Analysis with Regex - but this time I'm developing for a mobile device, and therefore need to care about performance.)
You have not tried the default namespace at the end of the rdf declaration:
xmlns="http://purl.org/rss/1.0/"
This makes sense, as any element in the default namespace will not need to have the namespace prepended to the element name.
Not directly a solution to the XDocument RSS read problem. But why aren't you using the provided SyncdicationFeed class to load the feed? http://msdn.microsoft.com/en-us/library/system.servicemodel.syndication.syndicationfeed.aspx
Try this
var elements = from p in xDocument.Root.Elements()
where p.Name.LocalName == "item"
select p;
foreach(var element in elements)
{
//Do stuff
}

Categories