c# - Get values from XML Nodes with more than one value - c#

First of all I would like to clarify that I'm noob programming.
Here is my question.
I'm having troubles getting the values of a node with more than one value.
I'm using Xml.Linq.
Example of my XML:
<root>
<ManufactureID>test</ManufactureID>
<Part>21034015</Part>
<Fixture>Erowa</Fixture>
<Material>CrCo</Material>
<ImplantIndex IMP="IMP1">
<Position x="26,61927" y="3,666112" z="-13,54083"/>
<Direction x="0,7169617301164524" y="0,41536091911417444" z="-0,5598581824185941"/>
<Xaxis x="0,7169617301164524" y="0,41536091911417444" z="-0,5598581824185941"/>
<Yaxis x="0,4630894965759858" y="0,31652069765969354" z="0,8278663938788802"/>
<Zaxis x="0,52107004875489" y="-0,8528129659108433" z="0,034583948081838636"/>
</ImplantIndex>
<ImplantIndex IMP="IMP2">
<Position x="27,20444" y="3,832021" z="-5,81747"/>
<Direction x="0,5516120001302346" y="0,2908829003330433" z="-0,7817361061164817"/>
<Xaxis x="0,5516120001302346" y="0,2908829003330433" z="-0,7817361061164817"/>
<Yaxis x="0,7202426402494431" y="0,30658331713284814" z="0,6222999347760941"/>
<Zaxis x="0,420683658440441" y="-0,9063077887504092" z="-0,04039123136907434"/>
</ImplantIndex>
</root>
For getting the nodes value of Part,Fixture or Material I have not problem.
But for getting the x/y/z values of position and direction actually I'm using:
string position = doc.Root.Element("ImplantIndex").Element("Position").ToString();
string[] posTokens = position.Split('"');
Console.WriteLine(double.Parse(posTokens[1]));
Console.WriteLine(double.Parse(posTokens[3]));
Console.WriteLine(double.Parse(posTokens[5]));
Anyone can help me getting a better way for doing that last part?
Thank you in advance.

This is how you normally access attribute value at node:
XmlDocument doc = new XmlDocument();
doc.LoadXml("<book genre='novel' ISBN='1-861001-57-5'>" +
"<title>Pride And Prejudice</title>" +
"</book>");
XmlElement root = doc.DocumentElement;
// Check to see if the element has a genre attribute.
if (root.HasAttribute("genre")){
String genre = root.GetAttribute("genre");
Console.WriteLine(genre);
}
or using XElement
XElement root = XElement.Load("PurchaseOrder.xml");
IEnumerable<XElement> address =
from el in root.Elements("Address")
where (string)el.Attribute("Type") == "Billing"
select el;
foreach (XElement el in address)
Console.WriteLine(el);

Related

How to get an xml value depending on node attribute in C#?

Let's say we have the following xml :
<?xml version="1.0" encoding="UTF-16"?>
<OrderExchangeMessage version="9.0" type="AddOrder"
xmlns:xs="xxx"
xmlns:xsi="xxx"
xmlns="xxx">
<Command>
<AddOrderRequest>
<Order>
<OrderID>xxx</OrderID>
<InnerOrder>
<RestorationOrder version="5.0">
<ModelElements>
<ModelElement displayName="red car">
<files>
<file path="pathtofile"/>
</files>
</ModelElement>
<ModelElement displayName="red truck">
<files>
<file path="pathtofile"/>
</files>
</ModelElement>
<ModelElement displayName="green car">
<files>
<file path="pathtofile"/>
</files>
</ModelElement>
</ModelElements>
</RestorationOrder>
</InnerOrder>
</Order>
</AddOrderRequest>
</Command>
</OrderExchangeMessage>
How can I retrieve the value of file path only if the attribute of ModelElement contains "red" ?
So I need to know which file goes for a red car and which file for a red truck.
I also tried to get a parent in order to look for child, but got no luck so far.
XmlNodeList? nodeListItems = xmldoc.SelectNodes("/OrderExchangeMessage[#version='9.0']/" +
"Command/AddOrderRequest/Order/InnerOrder/" +
"RestorationOrder[#version='5.0']/" +
"ModelElements");
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNodeList modelElements = doc.GetElementsByTagName("ModelElement");
foreach (XmlNode modelElement in modelElements)
{
XmlNode displayName = modelElement.Attributes.GetNamedItem("displayName");
if (displayName != null && displayName.Value.Contains("red"))
{
XmlNode path = modelElement["files"]["file"].Attributes.GetNamedItem("path");
Console.WriteLine(path.Value);
}
}
This should do the trick.
xml is the xml document
This code iterates all ModelElement and Checks displayName for the value of red
If you want to use your SelectNodes code, you'll have to have a NameSpaceManager, and the xpath has to have "xxx:" before each node path.
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldoc.NameTable);
nsmgr.AddNamespace("xxx", xmldoc.DocumentElement.NamespaceURI);
XmlNodeList? nodeListItems = xmldoc.SelectNodes("/xxx:OrderExchangeMessage[#version='9.0']/xxx:" +
"Command/xxx:AddOrderRequest/xxx:Order/xxx:InnerOrder/xxx:" +
"RestorationOrder[#version='5.0']/xxx:" +
"ModelElements", nsmgr);
Please try the following solution.
It is using LINQ to XML API of the .Net Framework. It is available since 2007.
The input XML has a default namespace. It should be taken care of.
c#
void Main()
{
const string filePath = #"e:\Temp\LockedInside.xml";
XDocument xdoc = XDocument.Load(filePath);
XNamespace ns = xdoc.Root.GetDefaultNamespace();
var ModelElements = xdoc.Descendants(ns + "ModelElement")?
.Where(d => d.Attributes("displayName").FirstOrDefault().Value.StartsWith("red"));
foreach (var ModelElement in ModelElements)
{
Console.WriteLine("displayName='{0}', file_path='{1}'"
, ModelElement.Attribute("displayName").Value
, ModelElement.Element(ns + "files").Element(ns + "file").Attribute("path").Value);
}
}
Output
displayName='red car', file_path='pathtofile'
displayName='red truck', file_path='pathtofile'

Getting a whole XML node with its markups in a string

Given the following XML example
<aaa>
<bbb id="1">
<ccc att="123"/>
<ccc att="456"/>
<ccc att="789"/>
</bbb>
<bbb id="2">
<ccc att="321"/>
<ccc att="654"/>
<ccc att="987"/>
</bbb>
</aaa>
as an XmlDocument object called xDoc1, I managed to remove the first bbb node thanks to its ID and an XPath instruction, leaving the second bbb node alone in the aaa one.
But now I want to get this removed node and its markups in a single string, as the InnerText value of this node is equal to
<ccc att="123"/><ccc att="456"/><ccc att="789"/>
but I want my string to be equal to
<bbb id='1'><ccc att="123"/><ccc att="456"/><ccc att="789"/></bbb>
How can I do so ? Using XmlDocument is mandatory.
I tried to use the ParentNode method, but then it includes the other bbb node.
My C# code for the moment :
xDoc1 = new XmlDocument();
xDoc1.Load("file.xml"); // Containing the given example above.
XmlNodeList nodes = xDoc1.SelectSingleNodes("//bbb[#id='1']");
foreach (XmlNode n in nodes)
{
XmlNode parent = n.ParentNode;
parent.RemoveChild(n);
}
// At this point, xDoc1 does not contain the first bbb node (id='1') anymore.
Use OuterXml property of XmlNode
xDoc1 = new XmlDocument();
xDoc1.Load("file.xml"); // Containing the given example above.
XmlNodeList nodes = xDoc1.SelectSingleNodes("//bbb[#id='1']");
foreach (XmlNode n in nodes)
{
XmlNode parent = n.ParentNode;
parent.RemoveChild(n);
Console.WriteLine(n.OuterXml);
}
I would firstly suggest not using XmlDocument. It's old tech and has been superseded by XDocument, which gives you Linq2Xml and lots of explicit casting goodness when dealing with attributes etc.
Using the XDocument approach with Linq instead of XPath, it's quite a lot easier to work this problem:
var doc=XDocument.Load("file.xml");
var elToRemove = doc.Root.Elements("bbb").Single(el => (int)el.Attribute("id") == 1);
elToRemove.Remove();
Console.WriteLine(doc.ToString()); //no <bbb id="1">
Console.WriteLine(elToRemove.ToString()); //the full outer text of the removed <bbb>

Insert node in xml document using c#

I was trying to insert an XML node in XML document at a specific position.
This is my xml:
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<readContract xmlns="http://implementation.company.schema.reference">
<ContactNumbers>10158</ContactNumbers>
<productGroups>0085</productGroups>
<indicationBalanceInfo>false</indicationBalanceInfo>
<indicationBlocked>true</indicationBlocked>
</readContract>
</Body>
</Envelope>
And am trying to insert another tag <productGroups>0093</productGroups> below to the tag <productGroups>0085</productGroups>
Expecting like the below:
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<readContract xmlns="http://implementation.company.schema.reference">
<ContactNumbers>10158</ContactNumbers>
<productGroups>0085</productGroups>
<productGroups>0093</productGroups>
<indicationBalanceInfo>false</indicationBalanceInfo>
<indicationBlocked>true</indicationBlocked>
</readContract>
</Body>
</Envelope>
Used the below C# code to achieve it.
XmlDocument doc = new XmlDocument();
string inputxml = this.StServiceCallActivity5.InputEnvelope.InnerXml.ToString();
//Here inputxml contains whole xml document.
string addxml = "<productGroups>0093</productGroups>";
doc.LoadXml(inputxml);
XmlDocumentFragment xmlDocFrag = doc.CreateDocumentFragment();
xmlDocFrag.InnerXml = addxml;
XmlElement parentEle = doc.DocumentElement;
parentEle.AppendChild(xmlDocFrag);
And it returns value like
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<readContract xmlns="http://implementation.company.schema.reference">
<ContactNumbers>10158</ContactNumbers>
<productGroups>0085</productGroups>
<productGroups>0093</productGroups>
<indicationBalanceInfo>false</indicationBalanceInfo>
<indicationBlocked>true</indicationBlocked>
</readContract>
</Body>
<productGroups xmlns="">0093</productGroups>
</Envelope>
Am a newbie to C# code, kindly help me to get the XML doc as expected.
your help is much appreciated.
When you do this:
XmlElement parentEle = doc.DocumentElement;
parentEle.AppendChild(xmlDocFrag);
You're appending the node to the root of the document. You probably wanted to select the actual readContract node that the item is supposed to be appended into. As an example:
XmlNode newNode = doc.CreateNode(XmlNodeType.Element, "productGroup", "");
newNode.InnerText = "something";
XmlNode readContractNode = doc["Envelope"]["Body"]["readContract"];
XmlElement groups = readContractNode["productGroups"];
readContractNode.InsertAfter(newNode, groups);
Of course you'd probably want to handle the case where there are already multiple child productGroup elements, but the idea is the same.
Looks like namespaces are causing the problem. This worked for me:
XmlDocument doc = new XmlDocument();
doc.LoadXml(File.ReadAllText("XMLFile1.xml"));
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("ns1", "http://schemas.xmlsoap.org/soap/envelope/");
ns.AddNamespace("ns2", "http://implementation.company.schema.reference");
var rootNode = doc.SelectSingleNode("//ns1:Envelope", ns);
var readContractNode = rootNode.FirstChild.FirstChild;
var newNode = doc.CreateNode(XmlNodeType.Element, "productGroups", "http://implementation.company.schema.reference");
newNode.InnerText = "0093";
readContractNode.InsertAfter(newNode, readContractNode.SelectSingleNode("//ns2:productGroups", ns));
Or if you don't fancy namespaces like I me, you can try a bit more "brute-forcy" approach:
XmlDocument doc = new XmlDocument();
doc.LoadXml(File.ReadAllText("XMLFile1.xml"));
var newNode = doc.CreateNode(XmlNodeType.Element, "productGroups", "http://implementation.company.schema.reference");
newNode.InnerText = "0093";
doc.FirstChild.FirstChild.FirstChild.InsertAfter(newNode, doc.FirstChild.FirstChild.FirstChild.FirstChild.NextSibling);
It can be optimized but I think it helps to make the point that root cause is the different namespaces in the document.
You may want to use XmlNode.InsertAfter Method.
public virtual XmlNode InsertAfter(
XmlNode newChild,
XmlNode refChild
)
Where
newChild = The XmlNode to insert
and
refChild = The XmlNode that is the reference node. The newNode is placed after the refNode
Please check this link for information.
And check this link with answer on SO.
P.S.
Always check other answers before posting new question.

Working With Specific XML structure

I am trying to get some data from an XML document. I have no control over the schema. If it were up to me I would have chosen another schema. I am using C#'s XPATH library to get the data.
XML DOC
<Journals>
<name>Title of Journal</name>
<totalvolume>2</totalvolume>
<JournalList>
<Volume no="1">
<Journal>
<issue>01</issue>
<Title>Title 1</Title>
<date>1997-03-10</date>
<link>www.somelink.com</link>
</Journal>
<Journal>
<issue>02</issue>
<Title>Title 3</Title>
<date>1997-03-17</date>
<link>www.somelink.com</link>
</Journal>
</Volume>
<Volume no="2">
<Journal>
<issue>01</issue>
<Title>Title 1</Title>
<date>1999-01-01</date>
<link>www.somelink.com</link>
</Journal>
<Journal>
<issue>01</issue>
<Title>Title 2</Title>
<date>1999-01-08</date>
<link>www.somelink.com</link>
</Journal>
</Volume>
</JournalList>
</Journals>
I am trying to get all the data in the Volume 2 node. Here is what I tried so far:
C# Code:
protected void loadXML(string url)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(url);
string strQuery = "Volume[#no='2']";
XmlElement nodeList = xmlDoc.DocumentElement;
XmlNodeList JournalList = nodeList.SelectNodes(strQuery);
foreach (XmlElement Journal in JournalList)
{
XmlElement temp = Journal;
}
}
It seems there are no nodes in JournalList. Anyone? Thanks in advance/
Your code is looking for "Volume" nodes directly under the "Journals" node
Change this:
string strQuery = "Volume[#no='2']";
To this, in order to look for "Volume" nodes under the "JournalList" node:
string strQuery = "JournalList/Volume[#no='2']";
Also, there's a couple typos in your XML:
</Volume no="2"> -> <Volume no="2"> // no open tag found
</Journal> -> </Journals> // expecting end tag </Journals>
From your comment below:
how would I go about access each journal? for example. I want irrate through each "journal" and get the title of the journal?
In order to do that, you could modify your code slightly:
var nodeList = xmlDoc.DocumentElement;
var volume = nodeList.SelectSingleNode(strQuery);
foreach (XmlElement journal in volume.SelectNodes("Journal"))
{
var title = journal.GetElementsByTagName("Title")[0].InnerText;
}
Also you can use Linq to XML:
using System.Xml.Linq;
//...
string path="Path of your xml file"
XDocument doc = XDocument.Load(path);
var volume2= doc.Descendants("Volume").FirstOrDefault(e => e.Attribute("no").Value == "2");

How to add child node to the root element in an XML file using C#

The xml file is having the following structure
<RootElement>
</RootElement>
i need to append the "Child" element to both RootElement and TestChild .
For this I'm using the following code.
List<string> Str = new List<string> {"a","b"};
XmlDocument XDOC = new XmlDocument();
XDOC.Load(Application.StartupPath + "\\Sample.xml");
XmlNode RootNode = XDOC.SelectSingleNode("//RootElement");
XmlNode TestChild = XDOC.CreateNode(XmlNodeType.Element, "TestChild", null);
for (int Index = 0; Index < Str.Count; Index++)
{
XmlElement XEle = XDOC.CreateElement("Child");
XEle.SetAttribute("Name", Str[Index]);
TestChild.AppendChild(XEle);
RootNode.AppendChild(XEle);
}
RootNode.AppendChild(TestChild);
XDOC.Save(Application.StartupPath + "\\Sample.xml");
But with this i can append the child node to only the RootElement
The result should come like
<RootElement>
<Child Name="a"/>
<Child Name="b"/>
<TestChild>
<Child Name="a"/>
<Child Name="b"/>
</TestChild>
</RootElement>
But now i'm getting like
<RootElement>
<Child Name="a" />
<Child Name="b" />
<TestChild>
</TestChild>
</RootElement>
please give me a solution to do this
Thanks in advance
I think problem is coming because you are using same node element in both root and test so create clone and than add it
XmlElement XEle = XDOC.CreateElement("Child");
XEle.SetAttribute("Name", Str[Index]);
TestChild.AppendChild(XEle);
RootNode.AppendChild(XEle.Clone());
try this:
XmlElement cpyXEle = XEle.Clone() as XmlElement;
TestChild.AppendChild(XEle);
RootNode.AppendChild(cpyXELe);
It is as others said that you need to clone the node. The reason for this in case it is not clear is that the node can only appear in one place in your dom tree. Originally you were putting it on the child and then in the following line when you put it on the root it moved it from the child to the root. It makes sense if you think about what that node would tell you when you asked it what its current parent was - it could only give one answer...

Categories