I have XML Code Block as below (it is part of similar lines of hundreds..):
<specs>
<spec name='fald' value = '100'>
<name></name>
<value></value>
</spec>
</specs>
I need to convert code as seen below:
<specs>
<spec name ='fald' value = '100'/>
</specs>
Using following code I am able to delete child nodes:
foreach (XElement child in doc.Descendants().Reverse())
{
if (child.HasAttributes)
{
foreach (var attribute in child.Attributes())
{
if (string.IsNullOrEmpty(attribute.Value) && string.IsNullOrEmpty(child.Value))
child.Remove();
}
}
}
But this process also deletes parent node ('spec') which is expected to take place there. Any help is appreciated, thanks...
It's a little unclear what the criteria for deleting an element is, but to get you started, perhaps this is somewhere along the lines of what you are looking for?
var xml =
#"<specs>
<spec name='fald' value='100'>
<name></name>
<value></value>
</spec>
</specs>";
var doc = XElement.Parse(xml);
var childrenToDelete = doc.XPathSelectElements("//spec/*")
.Where(elem => string.IsNullOrEmpty(elem.Value)
&& (!elem.HasAttributes
|| elem.Attributes().All(attr => string.IsNullOrEmpty(attr.Value))))
.ToList();
foreach (var child in childrenToDelete)
{
child.Remove();
}
// Produces:
// <specs>
// <spec name="fald" value="100" />
// </specs>
Check this fiddle for a test run.
Related
I want to change the attribute value (in this case "51") of the "NextMemberId" in an xml file that looks like this:
<File>
<MemberList>
<NextMemberId Value="51" />
<Member Id="1" ..... />
<Member Id="2" ..... />
</MemberList>
</File>
The following code works, but I would like to know if it can be done in a more direct way without having to run a foreach loop:
var memberId = 1;
var memberlist = Doc.DocumentElement.SelectSingleNode("MemberList");
foreach (XmlNode node in memberlist.ChildNodes)
{
var nodeElement = node as XmlElement;
if (nodeElement != null && nodeElement.Name == "NextMemberId")
{
nodeElement.SetAttribute("Value", memberId.ToString());
}
}
Thanks for any inspiration!
The correct path to get NextMemberId from File according to your sample XML would be :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId");
nodeElement.SetAttribute("Value", memberId.ToString());
If there are multiple NextMemberId in your actual XML, and you need to filter by Value attribute, then you can add an XPath predicate similar to what the other answer suggested :
var nodeElement = Doc.DocumentElement.SelectSingleNode("MemberList/NextMemberId[#Value=51");
Notice that you can choose to keep or leave single-quotes around 51 depending on whether you want compare the Value as a string or a number, respectively.
You can select single node with specified attribute like this:
var nextMemberIdNode = Doc.DocumentElement.SelectSingleNode("NextMemberId[#Value='51']")
I'm trying to read through .xml -file and get information out of there. Here is a sample of the .xml -file I have:
<?xml version="1.0" encoding="UTF-8"?>
<XmlFile>
<xmlsource>
<Name>TestXml</Name>
<filename>MyXmlFile.xml</filename>
<Information Key="GeneralInfo"/>
<Products>
<Product>
<ProductName>Product1</ProductName>
<Name Key="SomeName"/>
<Usages>
<Usage>
<Specs>
<Spec1 Key="Moving"/>
<Spec2 Key="Lifting"/>
</Specs>
<Info1>
<MovingInfo1>yes</MovingInfo1>
</Info1>
<Info2>Noup</Info2>
<MoreSpecs>
<ProductModel1>
<DetInfo1>DetInfo1</DetInfo1>
<DetInfo2>DetInfo2</DetInfo2>
</ProductModel11>
</MoreSpecs>
</Usage>
</Usages>
</Product>
<Product>
<ProductName>Product2</ProductName>
<Name Key="SomeName2"/>
<Usages>
<Usage>
<Specs>
<Spec1 Key="Moving"/>
<Spec2 Key="Lifting"/>
</Specs>
<Info1>
<MovingInfo1>not</MovingInfo1>
</Info1>
<Info2>Yes</Info2>
<MoreSpecs>
<ProductModel1>
<DetInfo1>DetInfo1</DetInfo1>
</ProductModel1>
</MoreSpecs>
</Usage>
<Usage>
<Specs>
<Spec1 Key="Turning"/>
</Specs>
<Info1>
<TurningInfo1>Infoooo</TurningInfo1>
</Info1>
<Info2>No</Info2>
<MoreSpecs>
<ProductType1>
<DetInfo1>DetInfo1</DetInfo1>
</ProductType1>
</MoreSpecs>
</Usage>
</Usages>
</Product>
</Products>
</xmlsource>
(This is just a sample, original file has a lot more data in it.)
I want to know only the values of ProductName and Spec1. As you can see from the sample, 'Product2' has two different values of Spec1: 'Moving' and 'Turning'.
What I'm trying to achieve:
Read ProductName ("Product1") from the first <Product> and then the Spec1 ("Moving"), then do something with the information. After that, move to next <Product>, read ProductName ("Product2"), Spec1 ("Moving") and the other Spec1 ("Turning"), and skipping all the other possible Spec values - meaning, that I want only Spec1 value. And so on go through the hole file.
Here is what I have tried to do:
public void getNodes(string filepath)
{
xmlFilePath = filepath;
XmlDocument xDoc = new XmlDocument();
xDoc.Load(xmlFilePath);
XmlNodeList products = xDoc.SelectNodes("//Product");
XmlNodeList productnames = xDoc.SelectNodes("//Product/ProductName");
XmlNodeList specs = xDoc.SelectNodes("//Product//Spec1");
AllocConsole();
Console.WriteLine(products.Count);
Console.WriteLine(specs.Count);
foreach (XmlNode xn in specs)
{
XmlAttributeCollection spec1Atts = xn.Attributes;
Console.WriteLine(spec1Atts["Key"].Value.ToString());
}
for (int i = 0; i < products.Count; i++)
{
Console.WriteLine(products.Item(i).InnerText);
Console.ReadLine();
}
}
This is the closest I have got (closest to what I'm trying to do).
There, first I have load the .xml -file.
Then, in lines containing XmlNodeList etc. I'm filtering with those requirements.
Here (below), is being checked the amount of products specs:
Console.WriteLine(products.Count);
Console.WriteLine(specs.Count);
Finally I'm printing out the values which has been read. With this, the print-out is obviously:
First comes out the amounts
Second comes the specs
And finally the productnames
As said above, I want ProductNames and Spec1's to be "linked" together.
I tried many methods e.g. shown in here: Reading multiple child nodes of xml file
Somehow I couldn't make any example work in my situation. Maybe it's because in my case, there is so deep parent-child pairs?
I can't change the structure of the .xml -file. If I could, I would have changed it already...
So, my question is: Could someone show me a hint/way how to achieve my goal? Thanks in advance.
What you need to do is traverse the hierarchy. In the revised code below, I find the ProductName, then within that, I look for the next node and so on until I find the Specs that correspond to that product.
private static void getNodes(string filePath)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filePath);
var productNodes = xmlDoc.SelectNodes("//Product");
if (productNodes != null)
{
foreach (XmlNode product in productNodes)
{
var childNodes = product.ChildNodes;
foreach (XmlNode child in childNodes)
{
if (child.Name == "ProductName")
{
Console.WriteLine(child.InnerText);
}
else if (child.Name == "Usages")
{
var childNodes2 = child.ChildNodes;
foreach (XmlNode child2 in childNodes2)
{
if (child2.Name == "Usage")
{
var childNodes3 = child2.ChildNodes;
foreach (XmlNode child3 in childNodes3)
{
if (child3.Name == "Specs")
{
var childNodes4 = child3.ChildNodes;
foreach (XmlNode child4 in childNodes4)
{
foreach (XmlNode a in child4.Attributes)
{
Console.WriteLine($" {a.InnerText}");
}
}
}
}
}
}
}
}
}
}
Console.ReadLine();
}
Hope that helps. If so, please vote for my answer because I need the reputation. Thank you.
If I understand your question correctly, you are trying to retrieve the specs for each product and you want to use them together. (Apologies if I don't fully get it). if that is the case you can try probing the product element directly in your example loop. like
for (int i = 0; i < products.Count; i++) {
var specs = products[i].SelectNodes("Usages/Usage/Specs")[0].ChildNodes;
for (int j = 0; j < specs.Count; j++)
Console.WriteLine("{0}->{1}", products[i].FirstChild.InnerText, specs[j].Attributes["Key"].Value);
}
I hope this helps
I have an XML file with subelements of elements:
<Root>
<Location>
<DepartureBoundary>
<DepartureBoundaryRadius>600</DepartureBoundaryRadius>
</DepartureBoundary>
</Location>
<Location>
<DepartureBoundary>
<DepartureBoundaryRadius>600</DepartureBoundaryRadius>
</DepartureBoundary>
</Location>
</Root>
Currently, I am doing the following to access the value for DepartureBoundaryRadius:
XDocument locationsDoc = XDocument.Load("file.xml");
DepartureLocationBoundaryRadius = null;
List<DepartureBoundaryRadius> radiusList = new List<DepartureBoundaryRadius>();
foreach (XElement locationElement in locationsDoc.Descendants("Root"))
{
foreach (XElement locationSubElement in locationsDoc.Descendants("Location"))
{
foreach (XElement departureElement in locationsDoc.Descendants("DepartureBoundary"))
{
DepartureLocationBoundaryRadius = departureElement.Element("DepartureRadius").Value));
radiusList.Add(DepartureLocationBoundaryRadius);
}
}
}
Is there an easier way to do this? I would rather assign the value of DepartureLocationBoundaryRadius in one line or one statement -- especially since each Location has only one DepartureBoundaryRadius value. Any thoughts? Thanks!
I think you mean Elements in your question . Descendants already gives what you want
var values = locationsDoc.Descendants("DepartureBoundaryRadius")
.Select(x => x.Value)
.ToList();
var list = xdoc.Document.Descendants("DepartureBoundaryRadius").Select(x=>x.Value);
I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?
You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();
You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.
How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;
If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();
I have an XML file in the following format
<?xml version="1.0" ?>
<AA someattrib="xyz">
<BB someOtherAttrib="xyz">
<Title></Title>
<CC>
<myNode rowid="">
<subNode1></subNode1>
<subNode2></subNode2>
<nodeOfInterest></nodeOfInterest>
</myNode >
<myNode rowid="">
<subNode1> </subNode1>
</myNode>
</CC>
</BB>
</AA>
I want to use Linq to pick out one node by the name 'MyNode' where the rowid is a particular number that I will be getting from a collection in an object. Once I get myNode I want to update the value of the child nodeOfInterest if it is present. If not present, then I would like to add it. Once done I want to save the file.
This is what I have at the moment but it may not be the right approach.
foreach (User employee in Users)
{
XPathNavigator node = xNav.SelectSingleNode("/AA/BB/CC/myNode[#rowid = '"+employee.ID.ToString()+"']");
XPathNodeIterator nodeIterator= node.SelectChildren("nodeOfInterest", "");
if (nodeIterator.Count == 1)
{
}
else
{
}
}
Is there a way this can be done using a direct join between the List and the xmldoc in memory? This will be a large list and an equally large xml file. I dont think running a loop and calling selectSingleNode is the most efficient way.
Thanks for your inputs
Well one starting point would be to create a Dictionary<string, XElement> mapping the row ID to the element:
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
Then:
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
// Use myNode
}
else
{
// Employee not found
}
}
Personally I prefer using the selection methods provided by LINQ to XML (Elements, Element, Descendants etc) rather than SelectSingleNode, SelectChildren etc.
The full answer, with help from Jon's replies...
var doc = XDocument.Load("thefile.xml");
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
XElement nodeOfInterest = myNode.Elements("nodeOfInterest").FirstOrDefault();
if (nodeOfInterest != null)
{
nodeOfInterest.Value = "update with this value";
}
else
{
XElement nodeOfInterest = new XElement("nodeOfInterest", "Add nodeOfInterest with this value");
myNode.Add(newElement);
}
}
}
doc.Save("TheFile.xml");