Using linq to find parent with attribute - c#

I have the following XML
<Configuration>
<Organisation Count="2">
<ID>1234</ID>
<UKPRN Count = "2">
<NAME>
<FIRST>abcd</FIRST>
<LAST>efgh</LAST>
</NAME>
</UKPRN>
</Organisation>
</Configuration>
I've tried this
var test = root.Elements().Where(p => p.Attribute("Count") != null).Select(p => p.Descendants("FIRST"));
but that's the wrong way round. Can't quite get my head around how to get hold of the parent.
How do I use linq to identify the closest parent element with the attribute Count, e.g. element FIRST should return UKPRN and element ID should return Organisation?

You can use the Ancestors() method to get all the parents and choose the first one that has the attribute you require:
var pairs = root.Descendants()
.Select(e => new
{
Element = e,
CountElement = e.Ancestors().FirstOrDefault(a => a.Attribute("Count") != null)
});

Related

Find element with specific attribute in xml?

I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<layout>
<menu name="Employees" url="Employees.aspx" admin="0">
</menu>
<menu name="Projects" url="Projects.aspx" admin="1">
</menu>
<menu name="Cases" url="Cases.aspx" admin="1">
</menu>
<menu name="CaseView" url="CaseView.aspx" admin="1" hidden="1" parent="Projects">
</menu>
<menu name="Management" url="" admin="1">
<item name="Groups" url="Groups.aspx" admin="1" parent="Management"/>
<item name="Statuses" url="Statuses.aspx" admin="1"/>
</menu>
</layout>
Here I have CaseView and Groups that both have a 'parent' attribute.
Currently I iterate like this:
IEnumerable<XElement> menus =
doc.Element("layout").Elements();
foreach (var menu in menus)
{
string name = menu.Attribute("name").Value;
string active = "";
string url = menu.Attribute("url").Value;
if(activePage == url)
{
active = "class=\"active\"";
}
...
What I want is:
if(activePage == url || ActiveIsChildOf(name, activePage))
{
active = "class=\"active\"";
}
Essentially this method needs to find if an element with activePage as its url attribute exists. If it does, see if it has a parent attribute; if it does, check if the parent == name.
Is there some way to find an element by attribute or something?
ex:
XElement e = doc.GetByAttribute("url",activePage)
Thanks
Since you are using Linq to XML, you can use Descendants method - it returns all child elements, not just the direct children. After that, you can use LINQ to filter the results.
XDocument doc;
string activePage;
var activeMenu = doc.Descendants("menu")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
You might need to check if o.Attribute("url") does not return null (it does when the attribute does not exist) if you cannot guarantee that the source XML does not have such attribute for all menu elements.
You can also skip the argument to Descendants() to check all elements - in your sample data that would allow you to check both menu and item elements. For example:
var activeMenu = doc.Descendants()
.Where(o => o.Name == "menu" || o.Name == "item")
.FirstOrDefault(o => o.Attribute("url").Value == activePage);
If xpath is too cryptic, you can use LINQ:
IEnumerable<XElement> hits =
(from el in XMLDoc.root.Elements("item")
where (string)el.Attribute("url") == activePage
select el);
or like this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => ((string)e.Attribute("url")) == activePage);
if(null != xele )
{
// do something with it
}
And you probably want it case-insensitive:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements("item").FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
If you want both menu and item, use this:
XElement xml = XElement.Load(file);
XElement xele = xml.Elements().FirstOrDefault(e => StringComparer.OrdinalIgnoreCase.Equals((string)e.Attribute("url") , activePage));
if(null != xele )
{
// do something with it
}
You can simply use xPath. It's a query language for XML.
You can formulate something like this :
var xDoc = new XmlDocument();
xDoc.Load("XmlFile.xml");
//Fetch your node here
XmlNode = xDoc.SelectSingleNode(/layout/menu[#url='activepage'][1]);
It returns a set of node and the index 1 is to get the first node of the given set.
You can always use xDoc.SelectNodes if you want all the matching nodes.
Since you are using LINQ you can simply include System.Xml.XPath and select nodes with XPathSelectElement or XPathSelectElements.
You can do that with XPath:
doc.SelectNodes("//*[#url='" + activePage + "']")
It will return all document items that have activePage as url attribute.
A case insensitive search example, converting xml to a dictionary:
Dim expandos = XDocument.Parse(Request("Xml")).Root.Elements.Select(
Function(e)
Dim expando As Object = New ExpandoObject,
dic = e.Attributes.ToDictionary(Function(a) a.Name.LocalName, Function(a) a.Value,
StringComparer.InvariantCultureIgnoreCase)
expando.PedidoId = dic("PedidoId")
expando.FichaTecnicaModeloId = dic("FichaTecnicaModeloId")
expando.Comodo = dic("Comodo")
expando.Cliente = dic("Cliente")
Return expando
End Function)

C# Linq XML, check for specific value and parse to array

I have the following XML:
<?xml version="1.0" ?>
<NewDataSet>
<Data>
<ElementDefinition>
<ID>1</ID>
<QUANTITY>0</QUANTITY>
</ElementDefinition>
<ElementDefinition>
<ID>2</ID>
<QUANTITY>1</QUANTITY>
</ElementDefinition>
</Data>
</NewDataSet>
I need to create an array which contains all ElementDefinitions which contain a QUANTITY element with a value other then 0.
I tried:
var f = XDocument.Load(path);
var xe = f.Root.Elements("QUANTITY").Where(x => x.Value != "0").ToArray();
But that doesn't seem to work. With the above XML the array should contain 1 item, but it stays 0.
After that I need to create a string for each ElementDefinition in the array, the string must contain the value of the corresponding ID element.
For that I tried:
foreach (string x in xe)
{
string ID = //not sure what to do here
}
You want something like this:
var ids = f.Root.Descendants("ElementDefinition")
.Where(x => x.Element("QUANTITY").Value != "0")
.Select(x => x.Element("ID").Value);
As you want the ID, it is not very helpful to select all QUANTITY nodes. Instead, select exactly what you specified in your question:
All ElementDefinitions (Descendants("ElementDefinition")), that have a QUANTITY with a Value other than 0 (Where(x => x.Element("QUANTITY").Value != "0"). From the resulting nodes, select the ID (Select(x => x.Element("ID").Value)).
Yo can replace with
var xe = f.Root.Elements("Data/ElementDefinition/QUANTITY").Where(x => x.Value != "0").ToArray();

Linq to XML - Find an element

I am sure that this is basic and probably was asked before, but I am only starting using Linq to XML.
I have a simple XML that i need to read and write to.
<Documents>
...
<Document>
<GUID>09a1f55f-c248-44cd-9460-c0aab7c017c9-0</GUID>
<ArchiveTime>2012-05-15T14:27:58.5270023+02:00</ArchiveTime>
<ArchiveTimeUtc>2012-05-15T12:27:58.5270023Z</ArchiveTimeUtc>
<IndexDatas>
<IndexData>
<Name>Name1</Name>
<Value>Some value</Value>
<DataType>1</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
<IndexData>
<Name>Name2</Name>
<Value>Some value</Value>
<DataType>3</DataType>
<CreationTime>2012-05-15T14:27:39.6427753+02:00</CreationTime>
<CreationTimeUtc>2012-05-15T12:27:39.6427753Z</CreationTimeUtc>
</IndexData>
...
</IndexDatas>
</Document>
...
</Documents>
I have a "Documents" node that contains bunch of "Document" nodes.
I have GUID of the document and a "IndexData" name.
I need to find the document by GUID and check if it has "IndexData" with some name.
If it does not have it i need to add it.
Any help would be apreciated, as i have problem with reading and searching trough elements.
Currently I am trying to use (in C#):
IEnumerable<XElement> xmlDocuments = from c in XElement
.Load(filePath)
.Elements("Documents")
select c;
// fetch document
XElement documentElementToEdit = (from c in xmlDocuments where
(string)c.Element("GUID").Value == GUID select c).Single();
EDIT
xmlDocuments.Element("Documents").Elements("Document")
This returns no result, even tho xmlDocuments.Element("Documents") does. It looks like i cant get Document nodes from Documents node.
You can find those docs (docs without related name in index data) with below code, after that you could add your elements to the end of IndexData elements.
var relatedDocs = doc.Elements("Document")
.Where(x=>x.Element("GUID").Value == givenValue)
.Where(x=>!x.Element("IndexDatas")
.Elements("IndexData")
.Any(x=>x.Element("Name") == someValue);
This should work:
var x = XDocument.Load(filePath);
// guid in your sample xml is not a valid guid, so I changed it to a random valid one
var requiredGuid = new Guid("E61D174C-9048-438D-A532-17311F57ED9B");
var requiredName = "Name1";
var doc = x.Root
.Elements("Document")
.Where(d => (Guid)d.Element("GUID") == requiredGuid)
.FirstOrDefault();
if(doc != null)
{
var data = doc.Element("IndexDatas")
.Elements("IndexData")
.Where(d => (string)d.Element("Name") == requiredName)
.FirstOrDefault();
if(data != null)
{
// index data found
}
else
{
// index data not found
}
}
else
{
// document not found
}

How to access the properties on a XML Node via linq?

I read through this post.
I have this XML:
<?xml version="1.0" encoding="utf-8" ?>
<Export version="" srcSys="" dstSys="" srcDatabase="" timeStamp="">
</Export>
This is what i tried, but with no luck:
var xml = XElement.Parse(BuyingModule.Properties.Resources.Export);
Func<XElement, string, string> GetAttribute = (e, property) => e.Elements("property").Where(p => p.Attribute("name").Value == property).Single().Value;
var query = from record in xml.Elements("Export")
select record;
var prop = GetAttribute(query.FirstOrDefault(), "version");
How do i access to properties of the "Export" Node?
I need to set those properties
The Export element doesn't have a properties element, which is what your GetAttribute method is trying to find.
My guess is you actually want:
var element = xml.Element("Export"); // Just get the first element
var version = (string) element.Attribute("version");
It's not clear to me why you've used a query expression and a delegate here - it's just things more complicated than you need. But Attribute(XName) is probably what you were missing...

Search all child nodes of XML node for a value and remove the grandparent node

Trying to use
exportDoc.Root.Elements("string").Where(node => !(node.Element("product").HasElements) || node.Element("product").Element("type").Value != product).Remove();
to remove the nodes in my XML document where the product string I'm searching for doesn't occur. Here is a sample of my XML structure:
<root>
<string id = "Hithere">
<product>
<type>orange</type>
<type>yellow</type>
<type>green</type>
<product>
<element2/>
<element3/>
</string>
<string id ="...">
...
...
</root>
So I need to look under the product element of each string element AND at each of the type elements therein to see if the value of string product (input to the method where this is contained) occurs. At present, it looks like my code only removes the node if the product string I'm searching for matches the value of just the first type element.
The whole point is to remove all string nodes from this xdoc that don't have the product I'm looking for listed under their product element.
You need to change your search condition slightly:
var nodesToRemove = xDoc.Root
.Elements("string")
.Where(node =>
!(node.Element("product").HasElements) ||
node.Element("product").Elements("type").All(x => x.Value != product))
.ToList();
This should match elements which all string:product:types differ from product value (or in other words - if at least one <type> will match your product, it won't be marked for removal).
You can't Remove() while you're still enumerating (deferred execution).
You need something more like:
// untested
var toRemove = exportDoc.Root.Elements("string")
.Where(node => !(node.Element("product").HasElements) ||
node.Element("product").Element("type").Value != product).ToList();
toRemove.Remove();

Categories

Resources