Obtaining values out of XML using Where clause - c#

Good Evening! I have a multi-level XML file that I need to extract some data out of and then spit it back out in a CSV file. I have been struggling with the extracting the specific data that I need. I have gotten my XML query to the point of where I get the data returned that I need, but I am unsure of how to get the specific pieces of data I need out of the data in question.
What I need out of the below as my end result is the following...
For each person node, I need to identify the following pieces of data and record them to create the export file. This would run in a foreach loop over a large number of person records
var emailAddress - This is user + ‘#’ + registry where association == type “ 2”
I.E. testuser#domain.tld
var alias - The next part is the alias – That comes from within the person node and is user where association == type “1”
I.E. test1
var servername - The last part to be extracted is the servername which is inside that same person tag and is registry in association where type == “1”
I.E. servername.fqdn
I will then be using a StringBuilder to join all of these on every foreach to give the desired output.
I will not know the domain name or server name prior to the execution of the tools on the file. It could also vary within the file, hence why these are not simply hardcoded.
My current code returns the full association information below.
I greatly appreciate ANY assistance. I have learned a ton from here, and greatly appreciate all of you.
My XML file looks like
<eim server="servername" timestamp="1489506332830">
<domain name="domainname" description="domainanme">
<registry name="servername" type="2342.52342.123-caseIgnore" kind="1" description="Created by wizard.">
<alias type="Other">passwordMap=false</alias>
<alias type="DnsHostName">servername</alias>
</registry>
<registry name="domainanme" type="2342.52342.123-caseIgnore" kind="1" description="Created by wizard.">
<alias type="KerberosRealm">domainnamee</alias>
<alias type="Other">passwordMap=false</alias>
</registry>
<person name="tuser" description="Test User">
<association type="1" registry="servername.fqdn" user="test1"/>
<association type="2" registry="domainname.tld" user="testuser"/>
</person>
<person name="tuser2" description="Test User2">
<association type="1" registry="servername.fqdn" user="test2"/>
<association type="2" registry="domainname.tld" user="testuser2"/>
</person>
My current code is
XmlDocument doc = new XmlDocument();
doc.Load("c:\\temp\\eimexport.eiml");
List<string> testing = new List<string>();
XmlElement eimElement = doc.DocumentElement;
XmlNodeList eimNodes = eimElement.SelectNodes("/eim/domain/person");
foreach (XmlNode parseEimNode in eimNodes)
{
testing.Add(parseEimNode.InnerXml);
}
foreach (var s in testing)
{
Console.WriteLine(s);
}
Console.ReadLine();

Since you're using XmlDocument, filtering can be done using XPath predicate expression (expression in []) :
foreach (XmlNode parseEimNode in eimNodes)
{
var email = parseEimNode.SelectSingleNode("association[#type=2]");
var user = email.GetAttribute("user");
var domain = email.GetAttribute("registry");
var server = parseEimNode.SelectSingleNode("association[#type=1]");
var _alias = server.GetAttribute("user");
var servername = server.GetAttribute("registry");
// process all information gathered so far accordingly
}

Related

Writing data to existing Xml

I am developing a universal windows app on windows 10 with Visual Studio 2015 and have a pretty large Xml structured like this:
<header id = "1">
<title>
some text
</title>
<question>
a question
</question>
<user_input>
<input1>
</input1>
<input2>
</input2>
</user_input>
</header>
<header id = "2">
<title>
some text
</title>
<question>
a question
</question>
<user_input>
<input1>
</input1>
<input2>
</input2>
</user_input>
</header>
...
This is repeating many times. There are parts that should never be changed (e.g. title, question). Now i want to write new elements into "ui", so it can be read again and shows the new content in texbox.
I use a FileStream and XmlDocument and XmlNodeList to read the Xml and show the content on textblocks:
path = "test.xml";
FileStream stream = new Filestream(path, FileMode.Open, FileAcces.Read);
XmlDocument xdoc = new XmlDocument();
xdoc.Load(reader);
XmlNodeList node = xdoc.GetElementsByTagName("header");
textblock1.Text = node[0].Attributes["id"].Value;
textblock2.Text = node[i].ChildNode[1].InnerText;
....
I tried this to write into the Xml:
XDocument xdoc = XDocument.Load(path);
XElement ele = xdoc.Element("header");
ele.Add(new XElement("user_input",
new XElement("input1", newtext)));
xdoc.Save(path); <---- at this point there is an error
"Argument 1: cannot convert from 'string' to 'System.IO.Stream'"
My question is: how can i write the user input (some string) to the place I want it to be? The first input shall be written into header with id = 1 into user_input, the second into header id = "2" and so on. I already tried to load the xml with XDocument and write a new element with XElement, but it work at all.Is there something wrong with my xml? Or is it the function? Thank you in advance.
Firstly, the xml file cannot contain same roots, here you have two headers nodes but don't see a root node. So I add a root node for testing your xml file as follows
<?xml version="1.0" encoding="utf-8"?>
<Topics>
<header id = "1">
...
</header>
</Topics>
Secondly, this error
"Argument 1: cannot convert from 'string' to 'System.IO.Stream'"
xdoc.save(string) is not available in uwp, details you can see the version information of XDocument.Save method.
Thirdly, for this question
how can i write the user input (some string) to the place I want it to be?
we can insert value to special element by xpath or GetElementsByTagName method. In uwp, I recommend you use Windows.Data.Xml.Dom namespace instead of System.xml.Ling.
Here I wrote a demo for insert value to special place . And upload the demo to GitHub, you can download CXml for testing.
Mainly Code
private async void BtnXmlWrite_Click(object sender, RoutedEventArgs e)
{
String input1value = TxtInput.Text;
if (null != input1value && "" != input1value)
{
var value = doc.CreateTextNode(input1value);
//find input1 tag in header where id=1
var xpath = "//header[#id='1']/user_input/input1";
var input1nodes = doc.SelectNodes(xpath);
for (uint index = 0; index < input1nodes.Length; index++)
{
input1nodes.Item(index).AppendChild(value);
}
RichEditBoxSetMsg(ShowXMLResult, doc.GetXml(), true);
}
else
{
await new Windows.UI.Popups.MessageDialog("Please type in content in the box firstly.").ShowAsync();
}
}
More details you can reference XML dom Sample, XML and XPath.

Issues Querying XML with Namespace

I am attempting to consume a Rest service that returns an XML response. I have successfully made the get request, my problem is processing the response. The response includes a namespace that seems to be messing up my linq query. I have tried almost everything I can think of userNames always comes up empty. Any help would be greatly appreciated and could possibly save my sanity.
<?xml version="1.0" encoding="UTF-8"?>
<tsResponse xmlns="http://tableausoftware.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableausoftware.com/api http://tableausoftware.com/api/ts-api-2.0.xsd">
<users>
<user id="c9274ce9-0daa-4aad-9bd2-3b1d6d402119" name="_DevITTemplate" role="Unlicensed" publish="false" contentAdmin="false" lastLogin="" externalAuthUserId=""/>
string usersList =
request.DownloadString("http://bshagena-sandbox/api/2.0/sites/b4126fe9-d7ee-4083-88f9- a5eea1f40416/users/");
request.Dispose();
XDocument xd;
XElement users;
XNamespace ns = "http://tableausoftware.com/api";
xd = XDocument.Parse(usersList);
users = xd.Root.Elements().First();
var userNames = from a in users.Descendants(ns +"users")
select (string)a.Attribute("name").Value;
It is your user element which contains attribute name, not the names wrapper element. Adjust your xpath accordingly: (Your use of the XNamespace is fine)
var userNames = from a in users.Descendants(ns + "user")
select a.Attribute("name").Value;
Minor - Attribute.Value is already a string - no need to cast it :)
Here is what I did and it seemed to work. Sorry for all the comments. I am sure this is not the most efficient way to do this. I hope this helps someone else losing their mind over the same issue.
// Sends get request and stores response as a string
string usersList =
request.DownloadString("http://<serverName>/api/2.0/sites/b4126fe9-d7ee-4083-88f9-a5eea1f40416/users/");
// declares an XML document object
XDocument xd;
// Declares and XML element object
XElement users;
// Declares a stupid XML namespace object
XNamespace ns = "http://tableausoftware.com/api";
// Sets document to value of string
xd = XDocument.Parse(usersList);
// Sets the element to value of the first node of the xml document
users = xd.Root.Elements().First();
// Creates an array and queries elements based on attribute of name
var userNames = from a in users.Elements(ns + "user")
select (string)a.Attribute("name").Value;

Reading all root node names in an XML document

I'm sure all the pros out there would find this very trivial but I need a quick solution for this in C#
I'm retrieving an xml schema of a view in share point which looks like this:
<FieldRef Name="LinkTitle"/><FieldRef Name="Author0"/><FieldRef Name="ID"/>
I want to parse this and only retrieve the Name's of each root element in this schema.
currently this is the code I'm working on , need some help with it
String fieldvals = view.ViewFields.SchemaXml.ToString();
XmlDocument reader = new XmlDocument(); ;
reader.LoadXml(fieldvals);
String xpath = "/";
var nodes = reader.SelectNodes(xpath);
foreach (XmlNode childrenNode in nodes)
{
Console.WriteLine(childrenNode.SelectSingleNode("//field1").Value);
}
Apparently, when this piece of code executes, I get an exception saying that more than one root node is present which is true of course .. but I'm not able to figure out the correct code to access every root node and extract it's name!
Wrap your xml fragment within a root node and then you can use linq to xml to retrieve a string array of those names like this:
var xml = XElement.Parse(xmlString);
var names=xml.Elements().Attributes(#"Name").Select(attrib => attrib.Value);
You should wrap your xml in some root node as an XML can have only one Root Node as below :
<FieldRefs>
<FieldRef Name="LinkTitle"/>
<FieldRef Name="Author0"/>
<FieldRef Name="ID"/>
</FieldRefs>
And then your code will execute fine.
String fieldvals = view.ViewFields.SchemaXml.ToString();
XmlDocument reader = new XmlDocument(); ;
reader.LoadXml(fieldvals);
String xpath = "/FieldRefs/FieldRef";
var nodes = reader.SelectNodes(xpath);
foreach (XmlNode childrenNode in nodes)
{
/*Process here*/
}

Issue parsing XML document in c#

I am trying to get the innertext from specific elements in an XML document, passed into via a string and I can't work out why it's not finding any nodes.
This code runs fine, but never enters either of the FOREACH loops as the ocNodesCompany and ocNodesOrgs both have xero elements. Why does the GetElementsByTagName not find the nodes?
BTW I've also tried:
XmlNodeList ocNodesOrgs = thisXmlDoc.SelectNodes("//OpenCalaisSimple/CalaisSimpleOutputFormat/Company")
Code:
public static ArrayList getTwitterHandles(String ocXML)
{
ArrayList thisList = new ArrayList();
XmlDocument thisXmlDoc = new XmlDocument();
thisXmlDoc.LoadXml(ocXML);
//get Companies
XmlNodeList ocNodesCompany = thisXmlDoc.GetElementsByTagName("Company");
foreach (XmlElement element in ocNodesCompany)
{
thisList.Add(element.InnerText);
}
//Get Organisations
XmlNodeList ocNodesOrgs = thisXmlDoc.GetElementsByTagName("Organization");
foreach (XmlElement element in ocNodesOrgs)
{
thisList.Add(element.InnerText);
}
//Get Organisations
return thisList;
}
My XML String is:
<!--Use of the Calais Web Service is governed by the Terms of Service located at http://www.opencalais.com. By using this service or the results of the service you agree to these terms of service.--><!-- Company: BBC,T-mobile,Vodafone,GE, IndustryTerm: open calais services, Organization: Federal Bureau of Investigation,Red Cross,Greenpeace,Royal Navy,-->
<OpenCalaisSimple>
<Description>
<calaisRequestID>38cb8898-48ba-85ff-12e9-f8d629568428</calaisRequestID>
<id>http://id.opencalais.com/lt0Hf8XWIr2DNIJzNlaXlA</id>
<about>http://d.opencalais.com/dochash-1/ff929eb2-de43-3ed1-8ee4-6109abf6bf77</about>
<docTitle/>
<docDate>2011-03-10 06:36:08.646</docDate>
<externalMetadata/>
</Description>
<CalaisSimpleOutputFormat>
<Company count="1" relevance="0.603" normalized="British Broadcasting Corporation">BBC</Company>
<Company count="1" relevance="0.603" normalized="T-MOBILE NETHERLANDS HOLDING B.V.">T-mobile</Company>
<Company count="1" relevance="0.603" normalized="Vodafone Group Plc">Vodafone</Company>
<Company count="1" relevance="0.603" normalized="General Electric Company">GE</Company>
<IndustryTerm count="1" relevance="0.603">open calais services</IndustryTerm>
<Organization count="1" relevance="0.603">Red Cross</Organization>
<Organization count="1" relevance="0.603">Greenpeace</Organization>
<Organization count="1" relevance="0.603">Royal Navy</Organization>
<Topics>
<Topic Taxonomy="Calais" Score="0.899">Human Interest</Topic>
<Topic Taxonomy="Calais" Score="0.694">Technology_Internet</Topic>
</Topics>
</CalaisSimpleOutputFormat>
</OpenCalaisSimple>
Note that Microsoft recommend you use XPath also, here is their help page for the GetElementsByTag method, and note the comment towards the middle recommending the use of SelectNodes instead (which is XPath).
http://msdn.microsoft.com/en-us/library/dc0c9ekk.aspx
A variation of your method, written with XPath, would be:
public static ArrayList getTwitterHandles(String ocXML)
{
ArrayList thisList = new ArrayList();
XmlDocument thisXmlDoc = new XmlDocument();
thisXmlDoc.LoadXml(ocXML);
//get Companies
XmlNodeList ocNodesCompany = thisXmlDoc.SelectNodes("//Company");
foreach (XmlElement element in ocNodesCompany)
{
thisList.Add(element.InnerText);
}
//Get Organisations
XmlNodeList ocNodesOrgs = thisXmlDoc.SelectNodes("//Organization");
foreach (XmlElement element in ocNodesOrgs)
{
thisList.Add(element.InnerText);
}
//Get Organisations
return thisList;
}
Note that the above implements what I believe is the functionality you have in your example - which is not quite the same as the xpath you've tried. Essentially in XPath "//" means any parent nodes, so "//Company" will pick up ANY subnode of the root you pass in that has a name of Company.
If you only want specific Company nodes, then you can be more specific:
XmlNodeList ocNodesCompany = thisXmlDoc.SelectNodes("//Company");
becomes
XmlNodeList ocNodesCompany = thisXmlDoc.SelectNodes("/OpenCalaisSimple/CalaisSimpleOutputFormat/Company");
Note the key difference is that there is only ONE forward slash at the beginning.
I've just tested both variations and they work great.
If you're handling XML files then I would strongly recommend you read up on, and become a guru, of XPath, it's exceptionally handy for allowing you to rapidly write code to parse through XML files and pick out precisely what you need (though I should add it's not the only way to do it and it is certainly not appropriate for all circumstances of course :) )
Hope this helps.
Seems like you should use XPath query to get elements you wanna recieve. You can read about it here
You could also use XDocument from System.Xml.Linq namespace. The following snippet is almost equivalent to your code. The return type is List<string> instead of ArrayList.
public static List<string> getTwitterHandles(String ocXml)
var xml = XDocument.Parse(ocXml);
var list = xml.Descendants("Company")
.Concat(xml.Descendants("Organization"))
.Select(element => element.Value)
.ToList();
return list;
}

Load repetitively-named XML nodes using Linq [C#]

I'm working on a program that needs to be able to load object-properties from an XML file. These properties are configurable by the user and XML makes sense to me to use.
Take the following XML document.
<?xml version="1.0" encoding="utf-8" ?>
<udpcommands>
<command name="requser">
<cvar name="reqchallege" value="false" />
</command>
<command name="reqprocs">
<cvar name="reqchallenge" value="false" />
</command>
</udpcommands>
I need to be able to load values from the cvars above to properties. I'm think Linq-To-XML would be good for it (I'm looking for applications of Linq so I can learn it). I've got a Linq-to-XML query done to select the right "command" based on the name.I was reading MSDN for help on this.
The following code snippet goes in a constructor that takes the parameter "string name" which identifies the correct XML <command> to pull.
I would like to have one linq statement to pull each <cvar> out of that XML given the section name, dumping everything to an IEnumerable. Or, I'm looking for a better option perhaps. I'm open for anything really. I would just like to use Linq so I can learn it better.
XElement doc = XElement.Load("udpcommands.xml");
IEnumerable<XElement> a = from el in doc.Elements()
where el.FirstAttribute.Value == name
select el;
foreach (var c in a)
{
Console.WriteLine(c);
}
The above code snippet outputs the following to the console:
<command name="requser">
<cvar name="reqchallege" value="false" />
</command>
Something like this should do:
var result =
doc.Elements("command")
.Single( x => x.Attribute("name").Value == name)
.Elements("cvar");
This will give you an IEnumerable<XElement> where each XElement represents a cvar in the specified command.
Note that if the specified command does not exist, the call to Single will cause an error. Likewise if the specified attribute is not found on the command.
EDIT As per your comments, you could do something along the lines of:
// Result will be an XElement,
// or null if the command with the specified attribute is not found
var result =
doc.Elements("command")
// Note the extra condition below
.SingleOrDefault( x => x.Attribute("name")!=null && x.Attribute("name").Value == name)
if(result!=null)
{
// results.Elements() gives IEnumerable<XElement>
foreach(var cvar in results.Elements("cvar"))
{
var cvarName = cvar.Attribute("name").Value;
var cvarValue = Convert.ToBoolean( cvar.Attribute("value").Value );
}
}

Categories