Linq - getting root descendants - c#

The xml file is this one:
<settings y="1" x="0">
<prospect aksdj="sdf">
<image path="images/1.jpg"/>
</prospect>
<prospect aksdfasdj="safafdf">
<image path="images/2.jpg"/>
</prospect>
</settings>
I want to get both rows with the image tags.
My code is this:
XElement doc = XElement.Load(#"C:\Users\John\Desktop\File.xml");
var result = (from c in doc.Descendants("settings")
select new
{
name = c.Element("prospect").Value
}).ToList();
But, doc.Descendants("settings") is null. Why is it null?

You've loaded an element which is already the <settings> element - that element doesn't have any <settings> descendants. (Descendants isn't returning you null, by the way - it's returning you an empty sequence. There's a big difference.)
If you change it to
XDocument doc = XDocument.Load("...");
then it should be okay - or just load it as an XElement and find the <prospect> descendants, given that you've only got one <settings> element anyway...

Related

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();

Iterating through XML file with XDocument returns element with no attributes

I'm iterating through all the child elements of this XML file:
<?xml version="1.0" encoding="utf-8"?>
<users>
<user name="SemiViral" access="2" />
</users>
with this code:
XDocument doc = XDocument.Load("Users.xml");
Console.WriteLine(doc.Descendants("users").Count());
foreach (XElement u in doc.Descendants("users")) {
Console.WriteLine(u.Attributes().Count());
}
but the output from the WriteLine is 0, and similarly empty if I try referencing the attributes directly. Counting the descendants returns 1 and when I added inner contents to the single child element, it was able to output those. So I know that it's the correct element, it's simply not accessing the attributes for some reason.
Here is a code to do what you are trying to do. You were not getting results because you were only looking for users elements (doc.Descendants("users")). The element that you are looking for is at the next level of the xml. If you debugged your code you would have spotted it.
XDocument doc = XDocument.Load("Users.xml");
Console.WriteLine(doc.Descendants("users").Descendants().Count());
foreach (XElement u in doc.Descendants("users").Descendants())
{
Console.WriteLine("value of the attribute is " + u.Attributes("access").First().Value);
}

Getting an XElement with a namespace via XPathSelectElements

I have an XML e.g.
<?xml version="1.0" encoding="utf-8"?>
<A1>
<B2>
<C3 id="1">
<D7>
<E5 id="abc" />
</D7>
<D4 id="1">
<E5 id="abc" />
</D4>
<D4 id="2">
<E5 id="abc" />
</D4>
</C3>
</B2>
</A1>
This is may sample code:
var xDoc = XDocument.Load("Test.xml");
string xPath = "//B2/C3/D4";
//or string xPath = "//B2/C3/D4[#id='1']";
var eleList = xDoc.XPathSelectElements(xPath).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
It works perfectly, but if I add a namespace to the root node A1, this code doesn't work.
Upon searching for solutions, I found this one, but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for <E5> because the same tag exists for <D7>, <D4 id="1"> and <D4 id="2">
My requirement is to search if a node exists at a particular XPath. If there is a way of doing this using Descendants, I'd be delighted to use it. If not, please guide me on how to search using the name space.
My apologies in case this is a duplicate.
To keep using XPath, you can use something link this:
var xDoc = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8'?>
<A1 xmlns='urn:sample'>
<B2>
<C3 id='1'>
<D7><E5 id='abc' /></D7>
<D4 id='1'><E5 id='abc' /></D4>
<D4 id='2'><E5 id='abc' /></D4>
</C3>
</B2>
</A1>");
// Notice this
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("sample", "urn:sample");
string xPath = "//sample:B2/sample:C3/sample:D4";
var eleList = xDoc.XPathSelectElements(xPath, nsmgr).ToList();
foreach (var xElement in eleList)
{
Console.WriteLine(xElement);
}
but it uses the Descendants() method to query the XML. From my understanding, this solution would fail if I was searching for because the same tag exists for , and
I'm pretty sure you're not quite understanding how that works. From the MSDN documentation:
Returns a filtered collection of the descendant elements for this document or element, in document order. Only elements that have a matching XName are included in the collection.
So in your case, just do this:
xDoc.RootNode
.Descendants("E5")
.Where(n => n.Parent.Name.LocalName == "B4");
Try this
var xDoc = XDocument.Parse("<A1><B2><C3 id=\"1\"><D7><E5 id=\"abc\" /></D7><D4 id=\"1\"><E5 id=\"abc\" /></D4><D4 id=\"2\"><E5 id=\"abc\" /></D4></C3></B2></A1>");
foreach (XElement item in xDoc.Element("A1").Elements("B2").Elements("C3").Elements("D4"))
{
Console.WriteLine(item.Element("E5").Value);//to get the value of E5
Console.WriteLine(item.Element("E5").Attribute("id").Value);//to get the value of attribute
}

Inserting and saving xml using Linq to XML

If i have an XML file settings.xml like below
<Root>
<First>
</First>
</Root>
I Load the XML first using XDocument settings = XDocument.Load("settings.xml")
How should I insert a XML node inside the node First and save it using LINQ-to-XML?
First you need to find the First element. Then you can add other elements and attributes to it.
There are more than one way to find an element in the xml: Elements, Descendants, XPathSelectElement, etc.
var firstElement = settings.Descendants("First").Single();
firstElement.Add(new XElement("NewElement"));
settings.Save(fileName);
// or
var newXml = settings.ToString();
Output:
<Root>
<First>
<NewElement />
</First>
</Root>
Or element with attribute:
firstElement.Add(
new XElement("NewElement", new XAttribute("NewAttribute", "TestValue")));
Output:
<Root>
<First>
<NewElement NewAttribute="TestValue" />
</First>
</Root>
[Edit] The answer to the bonus question. What to do if the first element does not exist and I want to create it:
var root = settings.Element("Root");
var firstElement = root.Element("First");
if (firstElement == null)
{
firstElement = new XElement("First");
root.Add(firstElement);
}
firstElement.Add(new XElement("NewElement"));

C# XPath Not Finding Anything

I'm trying to use XPath to select the items which have a facet with Location values, but currently my attempts even to just select all items fail: The system happily reports that it found 0 items, then returns (instead the nodes should be processed by a foreach loop). I'd appreciate help either making my original query or just getting XPath to work at all.
XML
<?xml version="1.0" encoding="UTF-8" ?>
<Collection Name="My Collection" SchemaVersion="1.0" xmlns="http://schemas.microsoft.com/collection/metadata/2009" xmlns:p="http://schemas.microsoft.com/livelabs/pivot/collection/2009" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FacetCategories>
<FacetCategory Name="Current Address" Type="Location"/>
<FacetCategory Name="Previous Addresses" Type="Location" />
</FacetCategories>
<Items>
<Item Id="1" Name="John Doe">
<Facets>
<Facet Name="Current Address">
<Location Value="101 America Rd, A Dorm Rm 000, Chapel Hill, NC 27514" />
</Facet>
<Facet Name="Previous Addresses">
<Location Value="123 Anywhere Ln, Darien, CT 06820" />
<Location Value="000 Foobar Rd, Cary, NC 27519" />
</Facet>
</Facets>
</Item>
</Items>
</Collection>
C#
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
XmlNodeList xnl = root.SelectNodes("//Item");
Console.WriteLine(String.Format("Found {0} items" , xnl.Count));
}
There's more to the method than this, but since this is all that gets run I'm assuming the problem lies here. Calling root.ChildNodes accurately returns FacetCategories and Items, so I am completely at a loss.
Thanks for your help!
Your root element has a namespace. You'll need to add a namespace resolver and prefix the elements in your query.
This article explains the solution. I've modified your code so that it gets 1 result.
public void countItems(string fileName)
{
XmlDocument document = new XmlDocument();
document.Load(fileName);
XmlNode root = document.DocumentElement;
// create ns manager
XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(document.NameTable);
xmlnsManager.AddNamespace("def", "http://schemas.microsoft.com/collection/metadata/2009");
// use ns manager
XmlNodeList xnl = root.SelectNodes("//def:Item", xmlnsManager);
Response.Write(String.Format("Found {0} items" , xnl.Count));
}
Because you have an XML namespace on your root node, there is no such thing as "Item" in your XML document, only "[namespace]:Item", so when searching for a node with XPath, you need to specify the namespace.
If you don't like that, you can use the local-name() function to match all elements whose local name (the name part other than the prefix) is the value you're looking for. It's a bit ugly syntax, but it works.
XmlNodeList xnl = root.SelectNodes("//*[local-name()='Item']");

Categories