Unable to select XElements with LINQ - c#

I'm trying to select nodes using LINQ but I cannot understand why this simple query does not work.
My xml is like this:
<config>
<func_list current_app="GSCC" flag="0">
<func id="GSCC">
<BLOCKS>
<BLOCK id="1" type="ACTIONS">
<ITEMS>
<ITEM id="1" type="UINT32" size="1" value="1" />
<ITEM id="2" type="UINT32" size="1" value="5" />
<ITEM id="3" type="UINT32" size="1" value="0" />
</ITEMS>
</BLOCK>
</BLOCKS>
</func>
</func_list>
</config>
Now, I have an XElement (_funcNode) which points to 'func' node:
IEnumerable<XElement> xBlocks = from el in _funcNode.Descendants("BLOCK")
where el.Attribute("type").Value == "ACTIONS"
select el;
if (!xBlocks.Any()) return false;
Also the xBlocks.Any() throws an System.NullReferenceException exception.
Any idea?

I solved changing the query to:
IEnumerable<XElement> xBlocks = from el in _funcNode.Descendants("BLOCK")
where (string)el.Attribute("type") == "ACTIONS"
select el;
Regards.

You main problem is your Query does not add up to your xml.
IEnumerable<XElement> xBlocks = from el in _funcNode.Descendants("BLOCK")
What you want to use is
_funcNode.Descendants().Elements("BLOCK")
Try to do this
var doc = _funcNode.Descendants("BLOCK")
See what doc looks like.

Related

inserting a node after a specific node in xml file

i'm re posting my question in a simpler way.
i need to search for a specific node in a XML file, and once i see it, i need to create a new node and insert it after that. the problem is that there are 2 nodes with the same value. and i need to insert the new node twice after each instance. with the code below: it's inserting the new nodes twice but in the same place after the first instance only.
original XML:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
</eventlist>
intended XML:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
</eventlist>
but the current output is:
<eventlist>
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd1" />
<media mediaType="Audio" />
</properties>
</event>
<event type="VIZ" />
<event type="VIZ" />
<event type="AUDIOPLAYER">
<properties>
<schedule startType="-ParentEnd2" />
<media mediaType="Audio" />
</properties>
</event>
</eventlist>
the code is below here:
XmlDocument xdoc = new XmlDocument();
xdoc.Load(#"C:\Users\namokhtar\Desktop\newxml\testxml.xml");
foreach (XmlNode node in xdoc.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']"))
{
XmlNode srcNode = node.SelectSingleNode("/eventlist/event[#type='AUDIOPLAYER']");
XmlNode newElem = xdoc.CreateElement("event");
XmlAttribute newAttr = xdoc.CreateAttribute("type");
newAttr.Value = "VIZ";
newElem.Attributes.Append(newAttr);
srcNode.ParentNode.InsertAfter(newElem, srcNode);
}
xdoc.Save(#"C:\Users\namokhtar\Desktop\newxml\newxml1.xml");
please advise me...
I haven´t fully tested this, but I´m almost sure this should do the trick:
foreach (XmlNode node in xdoc.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']"))
{
XmlNodeList srcNodes = node.SelectNodes("/eventlist/event[#type='AUDIOPLAYER']");
foreach (XmlNode srcNode in srcNodes)
{
XmlNode newElem = xdoc.CreateElement("event");
XmlAttribute newAttr = xdoc.CreateAttribute("type");
newAttr.Value = "VIZ";
newElem.Attributes.Append(newAttr);
srcNode.ParentNode.InsertAfter(newElem, srcNode);
}
}
The problem is you were selecting single node from matching expression, and you need to select all the nodes that match that and insert the new node after each of them.
Hope this helps!
Here is a solution using LINQ:
var xml = XDocument.Parse(File.ReadAllText(#"C:\Users\namokhtar\Desktop\newxml\testxml.xml"));
var elems = xml.Root.Elements()
.Where(e => e.Name == "event" && e.Attribute("type")?.Value == "AUDIOPLAYER");
foreach (var elem in elems)
{
elem.AddAfterSelf(new XElement("event", new XAttribute("type", "VIZ")));
}
xml.Save(#"C:\Users\namokhtar\Desktop\newxml\newxml1.xml");

Remove element from XML based on condition [duplicate]

XML
<WorkTable>
<Days>
<Day id="0" name="Monday"/>
<Day id="1" name="Tuesday"/>
<Day id="2" name="Wednesday"/>
<Day id="3" name="Thursday" />
<Day id="4" name="Friday"/>
<Day id="5" name="Saturday"/>
<Day id="6" name="Sunday"/>
</Days>
<SpecialDays>
<Day date="22.07.2015"/>
<Day date="24.07.2015"/>
</SpecialDays>
</WorkTable>
This code doesn't remove the node from xml. Could you help me to find problem?
XmlDocument doc = new XmlDocument();
doc.Load(localXMLpath + xmlFileName);
XmlNode delNode= doc.SelectSingleNode("/WorkTable/SpecialDays/Day[#date='24.07.2015']");
delNode.ParentNode.RemoveChild(delNode);
doc.Save(localXMLpath + xmlFileName);
This should works:
XDocument xdoc = XDocument.Load(filename);
xdoc.Element("WorkTable").Element("SpecialDays").Elements("Day")
.Where(x => (string)x.Attribute("date") == "24.07.2015")
.Remove();
xdoc.Save(filename);
Your code works OK, the problem is that you're trying to overwrite the file you've read the data from.
See this answer C# : the close method of Xml.Load(file)

C# returning an XML element with ancestors and self, but no other children

I have an xml doc layout like so
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 1">
<Data value="1">
<Data value="2">
<Data value="3">
</Metric>
<Metric name="metric 2">
<Data value="4">
<Data value="5">
<Data value="6">
</Metric>
</Dimension>
<Dimension name="dimension 2">
<Metric name="metric 3">
<Data value="6">
<Data value="7">
<Data value="8">
</Metric>
<Metric name="metric 4">
<Data value="9">
<Data value="10">
<Data value="11">
</Metric>
</Dimension>
</DataSet>
</Document>
I am attempting to split the Metrics out with their ancestors but not with their siblings. For example I want a file to look like...
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 1">
<Data value="1">
<Data value="2">
<Data value="3">
</Metric>
</Dimension>
</DataSet>
</Document>
and file 2 to look like ...
<?xml version="1.0" encoding="utf-8" ?>
<Document name="document name">
<DataSet name="dataset 1">
<Dimension name="dimension 1">
<Metric name="metric 2">
<Data value="4">
<Data value="5">
<Data value="6">
</Metric>
</Dimension>
</DataSet>
</Document>
My attempt to solve this was to create a method that would accept an xmlfile, outputDirectory, elementName, attributeName, and attributeValue.
This would allow me to search the document for a specific metric and pull it out into its own file with its entire tree, but not with it's siblings.
at the top of my method i have...
XDocument doc = XDocument.Load(xmlFile);
IEnumerable<XElement> elementsInPath = doc.Descendants()
.Elements(elementName)
.Where(p => p.Attribute(attributeName).Value.Contains(attributeValue))
.AncestorsAndSelf()
.InDocumentOrder()
.ToList();
However when I iterate through the elementsInPath The output gives all of the parents of the matched "Metric" with each parent return all of its children, and the only child i want present is the one that was matched by the input params.
Any help would be appreciated. Just for reference I save the files using the following snippet
int i = 1;
foreach (XElement element in elementsInPath)
{
XDocument tmpDoc = new XDocument();
tmpDoc.Add(element);
tmpDoc.Save(outputDirectory + elementName + "_" + i + ".xml");
i++;
}
Also to note, If I use the following code I get the exact metrics I am looking for, but i need to encapsulate them within their parents.
IEnumerable<XElement> elementsInPath = doc.Descendants(elementName)
.Where(p => p.Attribute(attributeName).Value.Contains(attributeValue))
.InDocumentOrder()
.ToList();
So you're really building a new document for each Metric element. The most straightforward way to do this would be:
foreach (XElement el in doc.Root.Descendants("Metric"))
{
XElement newDoc =
new XElement("Document",
new XAttribute(doc.Root.Attribute("name")),
new XElement("DataSet",
new XAttribute(el.Parent.Parent.Attribute("name")),
new XElement("Dimension",
new XAttribute(el.Parent.Attribute("name")),
el)
)
)
);
newDoc.Save(el.Attribute("name").Value.Replace(" ", "_") + ".xml");
}
This hopefully illustrates how the dynamic version of this would work - iterate through the ancestors and create new elements based on them:
foreach (XElement el in doc.Root.Descendants("Metric"))
{
XElement newDoc = el;
foreach(XElement nextParent in el.Ancestors()) // iterate through ancestors
{
//rewrap in ancestor node name/attributes
newDoc = new XElement(nextParent.Name, nextParent.Attributes(), newDoc);
}
newDoc.Save(el.Attribute("name").Value.Replace(" ", "_") + ".xml");
}
Just using the Ancestors() functionality won't work, because when you tried to add them it would also add their children (the siblings of the element you're trying to split on).

How to remove XML node by attribute

XML
<WorkTable>
<Days>
<Day id="0" name="Monday"/>
<Day id="1" name="Tuesday"/>
<Day id="2" name="Wednesday"/>
<Day id="3" name="Thursday" />
<Day id="4" name="Friday"/>
<Day id="5" name="Saturday"/>
<Day id="6" name="Sunday"/>
</Days>
<SpecialDays>
<Day date="22.07.2015"/>
<Day date="24.07.2015"/>
</SpecialDays>
</WorkTable>
This code doesn't remove the node from xml. Could you help me to find problem?
XmlDocument doc = new XmlDocument();
doc.Load(localXMLpath + xmlFileName);
XmlNode delNode= doc.SelectSingleNode("/WorkTable/SpecialDays/Day[#date='24.07.2015']");
delNode.ParentNode.RemoveChild(delNode);
doc.Save(localXMLpath + xmlFileName);
This should works:
XDocument xdoc = XDocument.Load(filename);
xdoc.Element("WorkTable").Element("SpecialDays").Elements("Day")
.Where(x => (string)x.Attribute("date") == "24.07.2015")
.Remove();
xdoc.Save(filename);
Your code works OK, the problem is that you're trying to overwrite the file you've read the data from.
See this answer C# : the close method of Xml.Load(file)

Parsing XML Element

i am trying to parse an xml element (DItem >> Title)
below is my code but somehow i am not getting hold of it.... any help?
XDocument xdoc1 = XDocument.Load(url);
XNamespace ns = "http://sitename/items.xsd";
string topic = xdoc1.Descendants(ns + "DItem")
.Select(x => (string)x.Attribute("Title"))
.FirstOrDefault();
<?xml version='1.0'?>
<root xmlns="http://www.w3.org/2005/Atom">
<title type="text">title</title>
<entry>
<id>da7d3189-fd89-4d3f-901c-30eab7a3baa5</id>
<title type="text">Swimming Pools</title>
<summary type="text"></summary>
<updated>2011-08-19T19:02:21Z</updated>
<link rel="alternate" href="link" />
<link href="link" />
<content type="application/xml">
<Items xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.namespace.xsd">
<CatalogSource Acronym="ABC" OrganizationName="organization name" />
<Item Id="28466" CatalogUrl="url">
<DItem xmlns:content="http://namespace.xsd" TargetUrl="http://index.html" Title="my title">
<content:Source Acronym="ABC" OrganizationName="ABC" />
</DItem>
</Item>
</Items>
</content>
</entry>
</root>
Using the namespace "http://www.namespace.xsd" should (and does) work:
XNamespace ns = "http://www.namespace.xsd";
string topic = xdoc1.Descendants(ns + "DItem")
.Select(x => (string)x.Attribute("Title"))
.FirstOrDefault();
Since DItem is not qualified with a namespace itself, it will use the default namespace specified asxmlns="http://www.namespace.xsd" on its parent element.

Categories

Resources