Select subset of XML - c#

I know this is a very basic question but I'm new to XML and while it seems simple, I can't find a simple answer anywhere. I have an XML document that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<root version="1">
<targets>
<target type="email">
<property name="to">b1q23#email.com</property>
</target>
<target type="fob"/>
</targets>
<observation uniqueID="00A60D" deviceID="308610ea23">
<field name="field1">test1</field>
<field name="field2">test2</field>
</observation>
and I'm trying to either select a subset of that xml, or remove nodes, to get it pared down to:
<observation uniqueID="00A60D" deviceID="308610ea23">
<field name="attachments">
<string>1910.jpg</string>
</field>
<field name="field1">test1</field>
<field name="field2">test2</field>
</observation>
So that I can deserialize it into an object. Any help is greatly appreciated.

You can use XPath:
string xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root version=\"1\">" +
"<targets>" +
"<target type=\"email\">" +
"<property name=\"to\">b1q23#email.com</property>" +
"</target>" +
"<target type=\"fob\"/>" +
"</targets>" +
"<observation uniqueID=\"00A60D\" deviceID=\"308610ea23\">" +
"<field name=\"field1\">test1</field>" +
"<field name=\"field2\">test2</field>" +
"</observation>" +
"</root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlElement root = doc.DocumentElement;
var observationNode = root.SelectSingleNode("/root/observation");
var observationXml = observationNode.OuterXml;

Here is a XML to LINQ Version:
dynamic root= XElement.Load(dataStream).Descendants("root")
.Select(element => element.Value).ToArray();
This will give all the root element from the document.And you can access root

XElement root = XElement.Parse("<root version ..." // etc. to parse a string.
// Use XElement.Load to load a file.
var observations = root.Elements("observation");
It assumes one root (by definition) and possibly multiple observation elements.

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'

Why xmlns appears at the parents' node in DGML file writing?

When I save a DGML file, unnecessary XNamespace appears.
This is a code of saving DGML file.
public void saveDGMLFile()
{
Debug.Log("Save DGML file!");
XNamespace xNamespace = "http://schemas.microsoft.com/vs/2009/dgml";
xDoc = new XDocument();
XElement root = new XElement(
xNamespace + "DirectedGraph",
new XAttribute("name", "root"));
xDoc.Add(root);
XElement parent = xDoc.Root;
XElement nodes = new XElement("Nodes");
foreach (var e in exploded_positions)
{
XElement item = new XElement("Node");
item.SetAttributeValue("Id", e.Item1.name);
item.SetAttributeValue("Category", 0);
item.SetAttributeValue(XmlConvert.EncodeName("start_position"), (e.Item2.x + " " + e.Item2.y + " " + e.Item2.z));
item.SetAttributeValue(XmlConvert.EncodeName("end_position"), (e.Item3.x + " " + e.Item3.y + " " + e.Item3.z));
nodes.Add(item);
}
parent.Add(nodes);
XElement links = new XElement("Links");
XElement link = new XElement("Link");
links.Add(link);
parent.Add(links);
XElement categories = new XElement("Categories");
XElement category = new XElement("category");
categories.Add(category);
parent.Add(categories);
xDoc.Save(outputFileName);
}
And this is an output DGML file.
<?xml version="1.0" encoding="utf-8"?>
<DirectedGraph name="root" xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes xmlns="">
<Node Id="PTC_EXP_Blasensensor-Adapter-+-" Category="0" start_position="0 0 0" end_position="0 0 -0.7573751" />
<Node Id="PTC_EXP_BML_Mutter_UNF-2B_1-14-" Category="0" start_position="0 0 0" end_position="0 0.7573751 0" />
<Node Id="PTC_EXP_BUSAKSHAMBAN_OR1501300N" Category="0" start_position="0 0 0" end_position="0.7573751 0 0" />
</Nodes>
<Links xmlns="">
<Link />
</Links>
<Categories xmlns="">
<category />
</Categories>
</DirectedGraph>
As you can see, xmlns="" of XNameSpace appears after the parents' node, Nodes, Links, and Categories.
How can I remove it?
It's because when you set the namespace on the root element to http://schemas.microsoft.com/vs/2009/dgml, it's only affecting that element, it doesn't become the default for the whole document - child elements you add to it will still have a default/empty namespace.
That's why, when the XML is output those elements have the xmlns attribute to distinguish that they are not in the same namespace.
To change that, when creating the child elements you can add the namespace as you have done for the DirectedGraph root element - for example:
XElement nodes = new XElement(xNamespace + "Nodes");
Once they have the same element as the root node, they will no longer be output with an empty xmlns attribute.
Alternatively, see here for a method to do that for all child nodes in a document.

System.InvalidOperationException when modifiying value in an xml file in C#

So i... Have this snippet of code what writes to an existing xml file... the code to me is VERY simple...
XElement element;
XDocument xdoc = XDocument.Load(FileLoc);
element = xdoc.Elements(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).Single();
element.Value = Gold.Text;
Good Right? good! but why does it give out that error which means that it can't find the thing? it's a very valid thing....
here is the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Save xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/DumaLegend">
<saveInfo>
<energyPieces>0</energyPieces>
<fullEnergyCells>4</fullEnergyCells>
<fullHearts>4</fullHearts>
<globalSwitches xmlns:d3p1="a">
<d3p1:switchList xmlns:d4p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</globalSwitches>
<gold>0</gold>
<hasBigFireball>false</hasBigFireball>
<hasCombo>false</hasCombo>
<hasCrossbow>false</hasCrossbow>
<hasDash>false</hasDash>
<hasDashUpgrade>false</hasDashUpgrade>
<hasDoubleJump>false</hasDoubleJump>
<hasFireball>false</hasFireball>
<hasHookshot>false</hasHookshot>
<hasInvisPot>false</hasInvisPot>
<hasSecondCombo>false</hasSecondCombo>
<hasShieldUpgrade>false</hasShieldUpgrade>
<hasSmallFireball>false</hasSmallFireball>
<heartPieces>0</heartPieces>
<heroPosOnMap>0</heroPosOnMap>
<heroTokens>0</heroTokens>
<itemSlot1 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<itemSlot2 xmlns:d3p1="http://schemas.datacontract.org/2004/07/DumaLegend.Objects.Consumables" i:nil="true" />
<lives>3</lives>
<worldsUnlocked>0</worldsUnlocked>
<worldsUnlockedOnMap>0</worldsUnlockedOnMap>
</saveInfo>
<saveSlot>0</saveSlot>
</Save>
Use xdoc.Descendants(XName.Get("gold", "http://schemas.datacontract.org/2004/07/DumaLegend")).
From the docs for Elements
Returns a filtered collection of the child elements of this element or document, in document order. Only elements that have a matching XName are included in the collection.
There is only one child elements of your document, and that is the Save element.
What you are looking for is at the path Save/saveInfo/gold. So you can either use Elements like this:
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Elements(ns + "Save")
.Elements(ns + "saveInfo")
.Elements(ns + "gold")
.Single();
Or you can use Descendants, which will search all child elements recursively.
XNamespace ns = "http://schemas.datacontract.org/2004/07/DumaLegend";
var gold = doc.Descendants(ns + "gold").Single();

delete child node with specific value

Am trying to delete a list name containing the value "dfdfdfd" from an XPath
XmlNode names = LoadDocument(xml).DocumentElement.SelectSingleNode("//Class[#Name='" + getCurClass() + "']/Property[#Id='" + i + "']/Lists[contains(ListName,'ws_Users')]");
after I execute this statement:
names.RemoveChild(names.FirstChild);
but I nothing happens.
My XML :
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Class Name="ECMInstruction" Style="Top">
<Entity Id="1" Name="DocumentInformation" />
<Property Id="1">
</Property>
<Property Id="2">
<Lists>
<ListName>ws_Users</ListName>
<ListName>dfdfdfd</ListName>
</Lists>
</Property>
</Class>
</Root>
Thanks for the help
You need to save the modified XmlDocument object back to file :
XmlDocument doc = LoadDocument(xml);
XmlNode names = doc.DocumentElement.SelectSingleNode("//Class[#Name='" + getCurClass() + "']/Property[#Id='" + i + "']/Lists[contains(ListName,'ws_Users')]");
names.RemoveChild(names.FirstChild);
//save `doc` back to file :
doc.Save("path_to_the_xml_file.xml");
or this way :
XmlNode names = LoadDocument(xml).DocumentElement.SelectSingleNode("//Class[#Name='" + getCurClass() + "']/Property[#Id='" + i + "']/Lists[contains(ListName,'ws_Users')]");
names.RemoveChild(names.FirstChild);
//save owner `XmlDocument` back to file :
names.OwnerDocument.Save("path_to_the_xml_file.xml");

How to find the name of the root node of a given xml file

Im using c#.net windows form application. I have a xml file whose name is hello.xml and it goes like this
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language>
<key>abc</key>
<value>hello how ru</value>
</language>
<language>
<key>def</key>
<value>i m fine</value>
</language>
<language>
<key>ghi</key>
<value>how abt u</value>
</language>
</languages>
How can i get the root node i.e <languages> into a text box. At this time I will have the xml file name. i.e "hello.xml". Using this I should get the root node.
Using LINQ to XML you can do this:
XDocument doc = XDocument.Load("input.xml");
string rootLocalName = doc.Root.Name.LocalName;
textBox1.Text = '<' + rootLocalName + '>';
With XmlDocument you can use this:
XmlDocument doc = new XmlDocument();
doc.Load("input.xml");
string rootName = doc.SelectSingleNode("/*").Name;
Or use the XmlDocument DocumentElement property as shown here:
XmlDocument doc = new XmlDocument();
doc.Load("hello.xml");
string root = doc.DocumentElement.Name;
textBox1.Text = "<" + root + ">";

Categories