Linq Xml - Find attributes by value - c#

If I have a simple XML document such as
<case id="37d3c93c-3201-4002-b24f-08e1221c3cb7">
<party id="26dad8c5-9487-48f2-8911-1d78c00095b2">...</party>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<illustration CaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">....</illustration>
<item relatedCaseId="37d3c93c-3201-4002-b24f-08e1221c3cb7">...</illustration>
</case>
I have code that changes the id attribute of the case element. I am now looking for some LINQ code that would help me search all elements that have an attribute value that matches the old value so I can replace it with the new value. I do not have a list of attribute names, and would need to search the entire document.
Any tips/ideas?
Thanks!

Something like this:
var matches = doc.Descendants()
.Where(x => x.Attributes()
.Any(attr => attr.Value == oldValue));
Or if you're just trying to replace the values, you only need the attributes themselves:
var attributes = doc.Descendants()
.Attributes()
.Where(attr => attr.Value == oldValue)
.ToList();
foreach (var attribute in attributes)
{
attribute.Value = newValue;
}
It's entirely possible that the copy to a list isn't necessary in this case, but I generally prefer to make a copy when mutating an XDocument to avoid confusing things. (It's certainly necessary when you're removing an element or something like that - just setting the value of an attribute probably doesn't affect things.)

Related

LINQ/XML: Get the attribut value if the attribut name contains a certain key word

Since I am not very familiar with LINQ and xDocument, I struggle to implement the following:
I have a XML file which looks like
<document>
<attribut1/>
<attribut2>
<house price="100" location="UK" id-ext="Id-100"/>
<house price="300" location="GB" id-int="Id-101"/>
</attribut2>
<attribut3/>
</document>
Speaking in Pseudo Code I need something like
Input: xDocument
Output: List containing strings with all values, i.e. "Id-100" in this example, of those attributes where "id-ext" was included in the attribut name. Hence, I try to get the values of those attributes which contain some certain letters in their name.
I already searched for similar suggestions like the one stated here:
How to search entire XML file for keyword?
But the point is that here the whole XML-Node is returned and I don't manage to break it down to the name of an attribute.
I would appreciate any suggestions of how to move one after applying "Descendants" to get the values of those attributs where some keywords are contained in the attribut name.
Use a dictionary
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, List<XElement>> dict = doc.Descendants("house")
.Where(x => x.Attribute("id-ext") != null)
.GroupBy(x => (string)x.Attribute("id-ext"))
.ToDictionary(x => x.Key, y => y.ToList());
Assuming "keywords are contained in the attribut name" means that the attribute name as a string contains a specific substring, and that this attribute may occur on any element in the document:
var doc = XDocument.Parse(#"<document>
<attribut1/>
<attribut2>
<house price='100' location='UK' id-ext='Id-100'/>
<house price='300' location='GB' id-int='Id-101'/>
</attribut2>
<attribut3/>
</document>");
foreach (var s in doc
.Descendants()
.SelectMany(e => e.Attributes())
.Where(a => a.Name.LocalName.Contains("id-ext"))
.Select(a => a.Value))
{
Console.WriteLine(s);
}

Getting tag with specific name in XML using C#

I have XML file and I have parse it into XDocument.
I need to get all tags with name <Entity>, but there is a one problem.
Tag <Entity> contains two more Entity tags as his children. (among with many others tag as well).
When I do this:
var entity= xmlDoc.Descendants().Where(x => x.Name.LocalName == "Entity");
I get them all of course.
Is there a way to tell: Get me all Entity tags, but not Entity tag that is child of an Entity tag?
Structure looks like this:
<Entity> --> I need to get this one
<SomeTags>Value</SomeTags>
<First>
<Entity>Value</Entity> --> Skip this one
</First>
<Entity>Value<Entity> --> Skip this one as well
</Entity>
You could use the following:
private String path= #"C:\Temp\xml.xml"; //YOur XML path
public string getValue(string Name)
{
try
{
doc = XDocument.Load(path);
var dada = (from c in doc.Descendants(Name)
where c.Parent.Name!=Name
select c).First();
return dada.Value;
}
catch (Exception)
{
global::System.Windows.Forms.MessageBox.Show("There was a problem with the XML");
return "";
}
}
Descendants gets all child elements recursively. Assuming the elements you want are all at the same depth, you need to find their parent element and query using Elements instead - this will only get the immediate children.
doc.Descendants("parent")
.Elements("Entity");
If that doesn't work for your structure, you could also literally query as you've suggested - find all those Entity elements that don't have any parent Entity elements:
doc.Descendants("Entity")
.Where(x => !x.Ancestors("Entity").Any());
Building on #CharlesMager's second example, this should be the correct syntax:
doc.Descendants("Entity").Where(x => !x.Ancestors("Entity").Any());
btw: one of your Entities isn't closed
Example on dotNetFiddle

Getting attribute name from xml in c#

I get a XML - File like this:
<list>
<sublist id1="a" id2="b" id3="b" id4="b">
<item name="name1">
<property1>a</property1>
</item>
<item2 name="name2">
<property1>c</property1>
</item2>
</sublist>
<sublist id1="b" id2="b" id3="a" id4="b">
[...more XML here...]
</sublist>
How can I find out the attribute name of the element "sublist" with the value "a" ?
If you have the XML string in a variable called xml:
XDocument
.Parse(xml) // parse the XML
.Descendants() // get all descendant elements
.SelectMany(e => e.Attributes()) // get all attributes from all elements
.Where(a => a.Value == "a") // leave only attributes whose value is "a"
.Select(a => a.Name); // select the name of those attributes
The result:
id1
id3
Note that this code uses XElement and LINQ to accomplish the goal. There are many other ways, some of which may be better suited to your needs.
Update
I noticed now that you're only looking for attributes on sublist elements. The code can be modified to handle that:
XDocument
.Parse(xml) // parse the XML
.Descendants("sublist") // get all descendant elements named "sublist"
.SelectMany(e => e.Attributes()) // get all attributes from all elements
.Where(a => a.Value == "a") // leave only attributes whose value is "a"
.Select(a => a.Name); // select the name of those attributes
The difference is in the call to Descendants, which now filters out any elements that is not called sublist.
In a comment you also asked how to handle the case when there is just a single sublist element. The code snippets above work fine for that too, since they're not making any assumptions about the number of elements.
You might be tempted to handle that case differently, such as with this code:
XDocument
.Parse(xml) // parse the XML
.Descendants("sublist") // get all descendant elements named sublist
.Single() // get the one and only element
.Attributes() // get all attributes from all elements
.Where(a => a.Value == "a") // leave only attributes whose value is "a"
.Select(a => a.Name) // select the name of those attributes
The difference between this and the previous examples is that we use Single here to extract the one and only sublist element from the result. The item type in the code at that point becomes XElement, and Single will throw an exception if there are no ("Sequence contains no elements") or more than one ("Sequence contains more than one element") sublist elements. In the next line we can then get rid of the SelectMany call and just access the Attributes of the XElement straight away.
But in my opinion the change in the code is not worth the loss in robustness you'll have.

Querying value from a specific xml tag with a specific attribute using Linq C#

I have this xml file where I am trying to get some tags from.
Here is the thing... I want to extract the tags that has a specific attribute with a specific value...
Here is an example
<root>
<input class="x">Data</input>
<input>Data2</input>
<input name="y">Data3</input>
<input class="z">Data4</input>
</root>
I want to get all the "input" tags that has the attribute "class".
List<XElement> selected = xmlDoc.Descendants("input").Where(t => t.Element("input").Attributes("class") != null).ToList();
but it gives me a null reference exception in the lambda expression... Would you please help me?
t.Attributes("class") will never return null. Use t.Attribute("class") instead (or .Where(t => t.Attributes("class").Any())).
You don't also need t.Element("input")
var selected = xmlDoc.Descendants("input")
.Where(t => t.Attribute("class") != null)
.ToList();
or simply
var selected = xmlDoc.XPathSelectElements("//input[#class]").ToList();
with the help of XPATH
You'll want this
List<XElement> selected = xmlDoc.Descendants("input").Where(t => t.Attribute("class") != null).ToList();
You were trying to look at input nodes inside of input nodes.

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