The situation
I'm using XDocument to load XML files from a lot of different sources. They all contain pretty much the same type of information, but the XML structure is very different.
This is a simplified example of what I'm doing today for a single source:
string path = #"some_file.xml";
var doc = XDocument.Load(path);
var root = doc.Root;
foreach (var node in root)
{
User u = new User();
u.Name = node.Element("User").Element("Information").Attribute("Name").Value;
u.Description = node.Element("User").Element("Description").Value;
u.Edited = node.Element("User").Element("Description").Attribute("Edited").Value;
/* ...and so on. */
listOfUsers.add(u);
}
This works, but I would like to make it more dynamic. As it happens, I already have strings containing exactly where to look for my properties formated like this:
"Element:User->Element:Information->Attribute:Name"
"Element:User->Element:Description"
"Element:User->Element:Description->Attribute:Edited"
This for example, tells me where in the structure I would find Name, Description and Edited in this particular XML file.
My question
Is there any way I can use these string to tell XDocument where to look? In other words, is there any way I can dynamically convert the above strings to this?
u.Name = node.Element("User").Element("Information").Attribute("Name").Value;
u.Description = node.Element("User").Element("Description").Value;
u.Edited = node.Element("User").Element("Description").Attribute("Edited").Value;
What I have tried
I found something called Reflection, that seems to do something similar to what I'm trying to do, but I can't figure out how to apply it to my problem above.
Maybe there are other ways as well?
Remember that you also have the option of XPath
So all you need to do is translate your string path from this: (After navigating root)
`Element:User->Element:Information->Attribute:Name`
to: (skip your root)
`/*/User/Information/#Name`
Which will then allow you to do like this:
var name = document.XPathSelectElement("/Root/User/Information/#Name").Value;
If Namespaces pose a problem, you also have the option of agnostic paths, with local-name(), e.g.
/*[local-name()='Root']/*[local-name()='User']/*[local-name()='Information']/#Name
Related
I am looking for a way to query CIM XML files with LINQ, in order to speed up my analysis of data and verify the exports from a database containing the elements of electrical network.
Since I am a newbie in C#, I guessed that it will be easy to load the CIM XML in console application and based on tag values filter some elements. However, all tutorials that I found so far are straight-forward where elements are named like "Student", "Purchase", etc.
My elements are named like "cim:LoadBreakSwitch", where "cim" is actual address defined in root node.
This way, when I try to select all elements named like "cim:LoadBreakSwitch", I get an exception thrown on Run Time because the name of a element cannot contain colon.
Example of element that I want to select from the CIM XML file:
<cim:LoadBreak rdf:ID="101">
<cim:ConductingEquipment.phases>A</cim:ConductingEquipment.phases>
<cim:IdentifiedObject.description>PoleMounted</cim:IdentifiedObject.description>
</cim:LoadBreak>
When I print in the console the names of all elements with
IEnumerable<XElement> elements = xmlDoc.Elements();
foreach (var item in elements)
{
Console.WriteLine(item.Name);
}
I get something like
"{http://[address from the root node]}LoadBreak".
I don't know if there is possibility to do it, but I am just curious did anyone who is experienced developer had need to do something similar.
Your XML is missing a root element with namespace declarations.
Here is a conceptual example for you. It shows how to handle namespaces and query XML with them.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<rdf:RDF xmlns:cim='http://iec.ch/TC57/2008/CIM-schema-cim13#'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<cim:LoadBreak rdf:ID='101'>
<cim:ConductingEquipment.phases>A</cim:ConductingEquipment.phases>
<cim:IdentifiedObject.description>PoleMounted</cim:IdentifiedObject.description>
</cim:LoadBreak>
</rdf:RDF>");
XNamespace cim = xdoc.Root.GetNamespaceOfPrefix("cim");
XNamespace rdf = xdoc.Root.GetNamespaceOfPrefix("rdf");
foreach (XElement xelem in xdoc.Descendants(cim + "LoadBreak").Elements())
{
Console.WriteLine("{0}: {1}"
, xelem.Name.LocalName
, xelem.Value);
}
}
Output
ConductingEquipment.phases: A
IdentifiedObject.description: PoleMounted
I've been playing with LINQ in VB.Net and some other things in an attempt to delete XML nodes based on attribute values. Basically, if any node in my XML documents has an attribute of a particular value, "cats" for example, I want to delete it.
The catch is I won't really know exactly what the XML structures will look like, so I can't give a path. Also, I know some of the attributes that may contain "cats", but I don't want to hard code them if possible.
So, in other words, I don't have a set XML structure, and I want to delete ANY node that has "cats" as an attribute value, like Caption = "cats" or Title = "cats", anywhere in the node. If it has "cats", nuke it.
Is this at all possible? Or do I just need to give up on this project?
BTW, I'm trying to write the solution in VB.Net, but I am quite capable of reading and converting C# if someone happens to know how to accomplish this but can only give C# code.
Thanks a ton for any help!
You can do this using:
XDocument.Descendants() to iterate through all elements in your document.
XElement.Attributes() to loop through all attributes of an element, to see if any have a value of "cats".
Extensions.Remove() to remove all elements that have an attribute value that matches.
In c# this becomes:
var doc = XDocument.Parse(xmlString);
var attributeValue = "cats";
doc.Descendants().Where(e => e.Attributes().Any(a => (string)a == attributeValue)).Remove();
And in VB.NET:
Dim doc = XDocument.Parse(xmlString)
Dim attributeValue = "cats"
doc.Descendants().Where(Function(e) e.Attributes().Any(Function(a) CStr(a) = attributeValue)).Remove()
Example fiddle.
I'm using an anonymous type to grab some XML data. All was going well until I ran across a section of XML where there can be 2 or 3 similar nodes. Like in the XML sample below there are 3 separate "Phones". My code was working fine when there was only ONE element that was possible to grab after following the "element path" I led it to. How can i grab a specific one? Or all 3 for that matter? Handling XML is still new to me and there seems to be soo many ways of handling it Searching the web for my exact need here didn't prove successful. Thanks.
var nodes = from node in doc.Elements("ClaimsSvcRs").Elements("ClaimDownloadRs")
select new
{
Phone1 = (string)node.Elements("Communications").Elements("PhoneInfo").Elements("PhoneNumber").FirstOrDefault(),
Phone2 = (string)node.Elements("Communications").Elements("PhoneInfo").Elements("PhoneNumber").FirstOrDefault(),
};
The XML Code is
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<ClaimsSvcRs>
<ClaimDownloadRs>
<Communications>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Home</CommunicationUseCd>
<PhoneNumber>+1-715-5553944</PhoneNumber>
</PhoneInfo>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Business</CommunicationUseCd>
<PhoneNumber>+1-715-5552519</PhoneNumber>
</PhoneInfo>
<PhoneInfo>
<PhoneTypeCd>Phone</PhoneTypeCd>
<CommunicationUseCd>Cell</CommunicationUseCd>
<PhoneNumber>+1-715-5551212</PhoneNumber>
</PhoneInfo>
</Communications>
</ClaimDownloadRs>
</ClaimsSvcRs>
</TEST>
I haven't used xpath in a while so i'll let someone else stand in there... but there's a way to select a particular PhoneInfo object based upon its subelements. So if you knew whether you wanted Home or Business or Cell or whatever, you'd be able to select that particular PhoneInfo object. Otherwise if you wanted simple Phone1,2,3 and nulls where ok, use the Skip linq function. Phone2 = query.Skip(1).FirstOrDefault()
lol no worries ;) xpath can be intermixed in here, was my thought, and might be more elegant if your CommunicationUseCd fields were deterministic. Then you could have Home = ... and Work = ..., etc, instead of Phone1 & Phone2
The same could be accomplished by slipping a where clause into each your query lines
If you're up for LINQ you can get all your elements in one go:
foreach(XElement phone in XDocument.Parse(xmlString).Descendants("PhoneInfo"))
{
Console.WriteLine(phone.Element("PhoneNumber").Value);
//etc
}
I find XDocument & LINQ a lot easier than XmlDocument & XPath, if you're okay with the alternative. There's more info on them here
This is driving me a little crazy. I am pulling an XML string from a database column and successfully creating an XDocument using XDocument.Parse. I've used linq to xml before to query xml trees but for some reason on this everything I am doing is returning null. Is it something to do with the namespace?
Here is a sampling of the text visualizer for my XDocument object:
// removed XML for privacy reasons
An example of the query I am trying:
XElement algorithmId = (from algoId in reportLogXml.Descendants(ALGORITHM_ID)
select algoId).FirstOrDefault();
I am using a constant for the string value and I have quadruple checked that the spelling matches as well as trying several different elements that are clearly in the document but they all return null. What am I doing wrong here?
Yes, it probably has to do with the namespace but also the <AlgorithmId> element has no descendants.
You can fix the ns problem like this:
//untested
XNameSpace ns0 = "http://schemas.datacontract.org/2004/07/Adapters.Adapter";
var ns1 = reportLogXml.Root.GetDefaultNamespace();
// check: ns0 and ns1 should be equal here
... in reportLogXml.Descendants(ns1 + ALGORITHM_ID)
Note that this is a special + operator, follow the format exactly.
I have a simple XML file that looks something like:
<Institutions>
<FI name = "NameOne">
<longname>some text</longname>
<APIKey>some text</APIKey>
<connectstring>some text</connectstring>
</FI>
<FI name = "NameTwo">
<longname>some text</longname>
<APIKey>some text</APIKey>
<connectstring>some text </connectstring>
</FI>
</Institutions>
Using LINQ to XML I can grab the entire file, find all values for "longname", "APIKey" and "connectstring" but I can not figure out how to find all the "name" values or how to grab only the three pieces of information underneath each FI name value. Just to be clear, I will have NO IDEA what the name= values are in advance.
I'm using:
XElement root = XElement.Load("c:\\directory\\Data_Config.xml");
and
IEnumerable<XElement> Fis =
from el in root.Elements("Institutions")
select el;
to load the file, as per the MSDN documentation. All of it's references seem to imply knowledge of what the name value is that I would be querying.
I've googled, tried different Attribute/Element queries, all with no luck. I'm pretty sure it's something simple but it's evading me.
How do I get this data?
Thanks,
Jason
var xml = XElement.Load (#"c:\directory\Data_Config.xml");
var query =
from e in xml.Descendants("FI")
select e.Attribute("name").Value;