I am using XDocument.Parse method to load following XML :
<AuditMessage>
<Event Action="Read" DateTime="2013/26/7" EventID="100"/>
<User Role="Admin" UserID="12123"/User>
<SourceIdentification SourceID="TeamLondon" SourceType="3"/>
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="1" />
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="`2" />
<Participant ParticipantID="0001" ParticipantType ="2"/>
<Participant ParticipantID="0002" ParticipantType ="3"/>
<Participant ParticipantID="0003" ParticipantType ="3" ParticipantName = "Housh Mangrove"/>
</AuditMessage>
I need to retrieve the values of following attributes in the above XML.
-DateTime
-Role
-AccessPointID
-ParticipantID
-ParticipantName
I have used sourceXML.Root.Element(nodeName).Attribute(attributeToMatch).Value to read a single attribute. I am failing to understand how can I iterate the same thing over different nodes, provided some nodes might be missing.
Please notice :
<Network> and <Participant> nodes are repeating.
ParticipantName attribute exists only in one Instance of
Lastly, any node could be missing in different XMLs provided as Input. Therefore I need to write code in such a way that if a node is missing, the application doesn't throw OBJECT REFERENCE NOT FOUND Exception
here's a quick attempt, you can figure out the ones I didn't add in:
public static void Main()
{
GetAtts(xml);
}
public static Atts GetAtts(string xml)
{
Atts atts = new Atts();
XDocument doc = XDocument.Parse(xml);
if (doc.Root.Element("Event") != null)
atts.datetime = doc.Root.Element("Event").Attribute("DateTime").Value;
//...
foreach (XElement ele in doc.Descendants("Network"))
atts.accesspointid.Add(ele.Attribute("AccessPointID").Value);
return atts;
}
public class Atts
{
public string datetime { get; set; }
public string role { get; set; }
private List<string>_accesspointid = new List<string>();
public List<string> accesspointid { get { return _accesspointid; } set { _accesspointid = value; } }
public List<string> _participantid = new List<string>();
public List<string> participantid { get { return _participantid; } set { _participantid = value; } }
public string participantname { get; set; }
}
you'll have an object you can handle more easily
You can use the Elements method to get an enumeration of nodes of a given name.
You can then test if the enumeration returned any results and look for the appropriate attributes in them.
Something like this, if you want it as CSV:
var data = new List<string>();
var events = doc.Root.Elements("Event");
if (events.Any())
{
foreach (var evt in events)
{
data.Add(evt.Attribute("DateTime").Value);
}
}
var participants = doc.Root.Elements("Participant");
if (participants.Any())
{
foreach (var participant in participants)
{
data.Add(participant.Attribute("ParticipantID").Value);
}
}
var csv = string.Join(", ", data);
Quick and dirty solution - hopefully you can take it from here:
var participantID = String.Join(", ",
xdoc.Root.Elements("Participant")
.Select(e => e.Attribute("ParticipantID"))
.Where(a => a != null)
.Select(a => a.Value)
.Distinct());
Related
<?xml version="1.0" encoding="utf-8" ?>
<root>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>configurationtestbucket</UploadBucket>
<FileType>
<type>*.txt</type>
<type>*.OpticomCfg</type>
</FileType>
</fileUploadSpecification>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>loguploadbucket</UploadBucket>
<FileType>
<type>*.Xml</type>
<type>*.Json</type>
</FileType>
</fileUploadSpecification>
</root>
This is the XML file I need to parse, I want to get each instance of fileUploadSpecification so that I can put each set of details into a list, I think some type of for loop would be appropriate, where I loop through and add the first set of upload details and then loop through and add the second. This is what I currently have, but it never gets to the second fileUploadSpecification element, it just returns the same one again.
The idea would be to create a new SettingsData for every set of fileUploadSpecification elements, whether it be two like shown above, or 10.
public interface ISettingsEngine
{
IEnumerable<SettingsData> GetSettings();
}
public class SettingsEngine : ISettingsEngine
{
public IEnumerable<SettingsData> GetSettings()
{
List<SettingsData> dataList = new List<SettingsData>();
try
{
var xDoc = XDocument.Load("File1.xml");
var instancesToParse = xDoc.Root.Elements().Count();
var fileCount = xDoc.Root.Elements("FileType").Count();
for (int x = 0; x < instancesToParse; x++)
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = xDoc.Root.Element("fileUploadSpecification").Element("UploadBucket").Value;
newSettingsData.Region = xDoc.Root.Element("fileUploadSpecification").Element("Region").Value;
newSettingsData.DirectoryPath = xDoc.Root.Element("fileUploadSpecification").Element("DirectoryPath").Value;
var query = xDoc.Root.Descendants("FileType").Elements("type");
foreach (XElement e in query)
{
newSettingsData.FileType.Add(e.Value);
}
dataList.Add(newSettingsData);
}
return dataList;
}
catch(Exception)
{
return dataList;
}
}
}
public class SettingsData
{
public List<string> FileType { get; set; }
public string DirectoryPath { get; set; }
public string Region { get; set; }
public string UploadBucket { get; set; }
public SettingsData()
{
FileType = new List<string>();
}
}
var dataList = (from fus in xDoc.Root.Elements("fileUploadSpecification")
select new SettingsData
{
UploadBucket = fus.Element("UploadBucket").Value,
Region = fus.Element("Region").Value,
DirectoryPath = fus.Element("DirectoryPath").Value,
FileType = fus.Element("FileType")
.Elements("type").Select(f =>f.Value).ToList()
}).ToList();
Each time through the loop, you're looking up the first fileUploadSpecification element all over again. You used the Elements() method already, in a few places. That's the one you want. Always favor foreach over for in C#, when you're looping over a collection. It's quicker (to code, not at runtime) and less error prone.
foreach (var uploadSpec in xDoc.Root.Elements("fileUploadSpecification"))
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = uploadSpec.Element("UploadBucket").Value;
newSettingsData.Region = uploadSpec.Element("Region").Value;
newSettingsData.DirectoryPath = uploadSpec.Element("DirectoryPath").Value;
var types = uploadSpec.Descendants("FileType").Elements("type").Select(e => e.Value);
foreach (var type in types)
{
newSettingsData.FileType.Add(type);
}
// Or if newSettingsData.FileType is List<String>...
//newSettingsData.FileType.AddRange(types);
dataList.Add(newSettingsData);
}
James Curran's answer is functionally the same, but it's better form.
I am trying to fill a list of clients from an xml file using linq, but i always get this null exception: Object reference not set to an instance of an object.. Here is my code, starting with the Client class:
public class Client
{
// Personne Physique
public string IdClient { get; set; }
public string NomClient { get; set; }
public string PrenomClient { get; set; }
public Client(){}
}
The code to fill the list:
var doc = XDocument.Load(pathXml);
List<Client> lc = doc.Descendants("Anzeige")
.FirstOrDefault()
.Descendants("Kunde")
.Select(p => new Client()
{
IdClient = p.Element("KundNr").Value,
NomClient = p.Element("Nachname").Value,
PrenomClient = p.Element("Vorname").Value
}).ToList<Client>();
The xml file looks like this:
<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Nachname>111</Nachname>
<Vorname>111</Vorname>
</Kunde>
</Anzeige>
Help please! I am pressed by time.
That code will be fine for the sample Xml you have posted.
However you are not handling some scenarios where your code will break. For example:
An Xml document with no <Anzeige> node will cause a null exception
in doc.Descendants("Anzeige").FirstOrDefault().Descendants("Kunde")
as the result from FirstOrDefault() will be null.
An Xml document where one of the <Kunde> nodes doesn't have one of
the value nodes will also cause an exception. For example if there is
no <Vorname> value then this piece of code will throw an exception
p.Element("Vorname").Value
You can tweak a bit your code to handle these scenarios.
Edit: You can use Elements instead of Descendants to force an xml where the Anzeige nodes come directly after the root and Kunde are direct childs of Anzeige. I have also edited my answer to take advantage of the cast operators that you can use directly on an XElement. This way (int?) p.Element("KundNr") returns either an int or null value if the node doesn't exist. Combined with ?? operator its a clean way to read the value. The cast will work with string values and basic value types like int or decimal. (Just for the purpose of demonstrating this I changed IdClient to an int)
You can still have an error if you try to convert to int? and the node value is something that cannot be converted on an int like "ABC". However you had all fields as strings so that shouldn't be a problem for you:
var clients = doc.Descendants("Anzeige").Descendants("Kunde").Select(p => new Client()
{
IdClient = (int?) p.Element("KundNr") ?? -1,
NomClient = (string) p.Element("Nachname") ?? String.Empty,
PrenomClient = (string) p.Element("Vorname") ?? String.Empty
}).ToList();
I have put together a small console application testing a few sample xmls:
static void Main(string[] args)
{
var doc = XDocument.Parse(
#"<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Nachname>111</Nachname>
<Vorname>111</Vorname>
</Kunde>
<Kunde>
<KundNr>222</KundNr>
<Nachname>222</Nachname>
<Vorname>222</Vorname>
</Kunde>
</Anzeige>");
ExtractClients(doc);
var docWithMissingValues = XDocument.Parse(
#"<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Vorname>111</Vorname>
</Kunde>
<Kunde>
<KundNr>222</KundNr>
<Nachname>222</Nachname>
</Kunde>
<Kunde>
</Kunde>
</Anzeige>");
ExtractClients(docWithMissingValues);
var docWithoutAnzeigeNode = XDocument.Parse(
#"<AnotherNode>
<Kunde>
<KundNr>111</KundNr>
<Vorname>111</Vorname>
</Kunde>
</AnotherNode>");
ExtractClients(docWithoutAnzeigeNode);
var docWithoutKundeNodes = XDocument.Parse(
#"<Anzeige>
<OtherNode></OtherNode>
</Anzeige>");
ExtractClients(docWithoutKundeNodes);
var emptyDoc = new XDocument();
ExtractClients(emptyDoc);
Console.ReadLine();
}
private static void ExtractClients(XDocument doc)
{
var clients = doc.Descendants("Anzeige").Descendants("Kunde").Select(p => new Client()
{
//You can manually get the value like this:
//IdClient = p.Element("KundNr") != null ? p.Element("KundNr").Value : String.Empty,
//NomClient = p.Element("Nachname") != null ? p.Element("Nachname").Value : String.Empty,
//PrenomClient = p.Element("Vorname") != null ? p.Element("Vorname").Value : String.Empty
//Or directly cast the node value to the type (value types or strings) required like:
IdClient = (int?) p.Element("KundNr") ?? -1,
NomClient = (string) p.Element("Nachname") ?? String.Empty,
PrenomClient = (string) p.Element("Vorname") ?? String.Empty
}).ToList();
Console.WriteLine();
foreach (var client in clients)
{
Console.WriteLine("{0},{1},{2}", client.IdClient, client.NomClient, client.PrenomClient);
}
Console.WriteLine(new string('-',30));
}
public class Client
{
// Personne Physique
public int IdClient { get; set; }
public string NomClient { get; set; }
public string PrenomClient { get; set; }
public Client() { }
}
Hope this helps!
How can i import xml elements to object? My code below doesn't work, it fails at the SetValue and i can't figure out why.
But even then, i suspect that linq has a much cleaner way of doing this but i can't find any examples.
class Printers {
public List<Printer> list = new List<Printer>();
public Printers()
{
var xDoc = XDocument.Load(Properties.Settings.Default.XmlSetupPath).Root;
var xPrinters = xDoc.Element("printers").Elements();
foreach (var xPrinter in xPrinters)
{
var printer = new Printer();
foreach (var xEl in xPrinter.Elements())
{
printer.GetType().GetProperty(xEl.Name.ToString()).SetValue(printer, xEl.Value);
}
}
}
}
class Printer
{
public string name;
public string ip;
public string model;
public string infx86;
public string infx64;
public string location;
public string comment;
}
my XML:
<printers>
<printer>
<name>my Printer</name>
<ip>192.168.100.100</ip>
<model>Brother</model>
<driver>ab</driver>
<infx86>ab\cd.INF</infx86>
<comment>Copycenter</comment>
</printer>
<printer>
<name>my Printer</name>
<foobar>oh no!</foobar>
</printer>
</printers>
I want to
You're asking for properties - but your type only has fields. Either make them properties, like this:
public string name { get; set; }
... or use Type.GetField instead.
In terms of making it prettier, I'd personally add a static FromXElement method to your Printer class, at which point you could have:
list = xDoc.Element("printers")
.Elements()
.Select(Printer.FromXElement)
.ToList();
Or you could write a generic method to create a new instance and populate it via reflection, e.g.
public static T FromXElement<T>(XElement element) where T : class, new()
{
T value = new T();
foreach (var subElement in element.Elements())
{
var field = typeof(T).GetField(subElement.Name.LocalName);
field.SetValue(value, (string) subElement);
}
return value;
}
Then:
list = xDoc.Element("printers")
.Elements()
.Select(XmlReflection.FromXElement<Printer>)
.ToList();
Working in Visual Studio 2010, .NET v4
I'm a relative newbie to both C# and XML. Using website resources, I was able to work out a fairly simple approach to parsing XML for specific values. However, the XML became increasingly complex, and the method I'm using (sort of a 'navigate via iterations' technique) is starting to look rather absurd, with many nested reader.Read() calls. I'm certain there's a less embarrassing way, but when the only tool you have is a hammer...
So, the question is: What's a neat, clean way to parse the XML (see below), returning a list of all 'action' values, only from an item matching <item itemkind="Wanted"> and <phrase>SomeSearchString</phrase>?
Here is a fragment of the XML:
<formats>
<items>
<item itemkind="NotWanted">
<phrase>Not this one</phrase>
<actions>
<action>
<actionkind>SetStyle</actionkind>
<parameter>Normal</parameter>
</action>
<action>
<actionkind>SetMargins</actionkind>
<parameter>0.25,0.25,1,4</parameter>
</action>
</actions>
</item>
<item itemkind="Wanted">
<phrase>SomeSearchString</phrase>
<actions>
<action>
<actionkind>Action 1</actionkind>
<parameter>Param 1</parameter>
</action>
<action>
<actionkind>Action 2</actionkind>
<parameter>Param 2</parameter>
</action>
<action>
<actionkind>Action 3</actionkind>
<parameter>Param 3</parameter>
</action>
</actions>
</item>
</items>
<styles>
<style stylename="Normal">
<fontname>Arial</fontname>
<fontsize>10</fontsize>
<bold>0</bold>
</style>
<style stylename="Heading">
<fontname>fntame frhead</fontname>
<fontsize>12</fontsize>
<bold>1</bold>
</style>
</styles>
</formats>
And here's the code that I've arrived at. It does work, but, well, see for yourself. Please be gentle:
public static List<TAction> GetActionsForPhraseItem(string AFileName, string APhrase)
{
List<TAction> list = new List<TAction>();
string xmlactionkind = null;
string xmlparameter = null;
string match = null;
// Search through XML items
using (XmlReader reader = XmlReader.Create(AFileName))
{
if (reader.ReadToFollowing("items"))
{
while (reader.Read())
{
if (reader.ReadToFollowing("item"))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.GetAttribute("itemkind") == "Phrase")
{
if (reader.ReadToFollowing("phrase"))
{
match = reader.ReadString();
if (match == APhrase)
{
if (reader.ReadToFollowing("actions"))
{
// Use a subtree to deal with just the aItemKind item actions
using (var SubTree = reader.ReadSubtree())
{
bool HaveActionKind = false;
bool HaveParameter = false;
while (SubTree.Read())
{
if (SubTree.NodeType == XmlNodeType.Element && SubTree.Name == "actionkind")
{
xmlactionkind = SubTree.ReadString();
HaveActionKind = true;
}
if (SubTree.NodeType == XmlNodeType.Element && SubTree.Name == "parameter")
{
xmlparameter = SubTree.ReadString();
HaveParameter = true;
}
if ((HaveActionKind == true) && (HaveParameter == true))
{
TAction action = new TAction()
{
ActionKind = xmlactionkind,
Parameter = xmlparameter
};
list.Add(action);
HaveActionKind = false;
HaveParameter = false;
}
}
}
}
}
}
}
}
}
}
}
}
return list;
}
Bearing in mind that I'm new to C#, I suspect that LINQ would be quite useful here, but so far I haven't been able to wrap my brain around it. Trying to learn too many new things at once, I imagine. Thanks in advance for any help (and constructive criticisms).
EDIT: This is the final working code I ended up with. Thanks everyone who responded!
public static List<TAction> GetActionsForPhraseItemTWO(string AFileName, string ASearchPhrase)
{
List<TAction> list = new List<TAction>();
var itemKind = "Wanted";
var searchPhrase = ASearchPhrase;
var doc = XDocument.Load(AFileName);
var matches = doc.Descendants("item")
.Where(x => x.Attribute("itemkind") != null &&
x.Attribute("itemkind").Value == itemKind &&
x.Descendants("phrase").FirstOrDefault() != null &&
x.Descendants("phrase").FirstOrDefault().Value == searchPhrase)
.SelectMany(x => x.Descendants("action"));
foreach (var temp in matches)
{
TAction action = new TAction()
{
ActionKind = temp.Element("actionkind").Value.ToString(),
Parameter = temp.Element("parameter").Value.ToString()
};
list.Add(action);
}
return list;
}
var node = XDocument.Load(fname)
.XPathSelectElement("//item[#itemkind='Wanted']/phrase");
var text = node.Value;
var val = XDocument.Load(filename) // OR XDocument.Parse(xmlstring)
.Descendants("item")
.First(i => i.Attribute("itemkind").Value == "Wanted")
.Element("phrase")
.Value;
Linq to XML is what you want... also you want the Actions right? I don't think any of the other answers give you that...
var itemKind = "Wanted";
var searchPhrase = "SomeSearchString";
var doc = XDocument.Load(filename);
var matches = doc.Descendants("item")
.Where(x => x.Attribute("itemkind") != null &&
x.Attribute("itemkind").Value == itemKind &&
x.Descendants("phrase").FirstOrDefault() != null &&
x.Descendants("phrase").FirstOrDefault().Value == searchPhrase)
.SelectMany(x => x.Descendants("action"));
Since you have a defined XML schema, I would just declare matching classes to represent that data, and use the XmlSerializer to deserialize the XML.
So based on your posted XML, your classes might look like:
[XmlType("formats")]
public class Formats
{
[XmlArray("items")]
public List<Item> Items { get; set; }
[XmlArray("styles")]
public List<Style> Styles { get; set; }
}
[XmlType("item")]
public class Item
{
[XmlAttribute("itemkind")]
public string ItemKind { get; set; }
[XmlElement("phrase")]
public string Phrase { get; set; }
[XmlArray("actions")]
public List<Action> Actions { get; set; }
}
[XmlType("action")]
public class Action
{
[XmlElement("actionkind")]
public string ActionKind { get; set; }
[XmlElement("parameter")]
public string Parameter { get; set; }
}
[XmlType("style")]
public class Style
{
[XmlAttribute("stylename")]
public string StyleName { get; set; }
[XmlElement("fontname")]
public string FontName { get; set; }
[XmlElement("fontsize")]
public int FontSize { get; set; }
[XmlElement("bold")]
public bool Bold { get; set; }
}
A sample deserialization code might look like:
XmlSerializer mySerializer = new XmlSerializer(typeof(Formats));
FileStream myFileStream = new FileStream(AFileName, FileMode.Open);
var formats = (Formats)mySerializer.Deserialize(myFileStream);
Then searching the data is a simple as iterating through your objects and their properties:
List<Action> matchingActions = formats.Items
.Where(item => item.ItemKind == "Wanted")
.Where(item => item.Phrase == "SomeSearchString")
.SelectMany(item => item.Actions)
.ToList();
A good aspect of this is you reduce dependencies on hard-coded string literals that define your XML schema. It becomes very easy to quickly refactor or change the structure of your data without accidentally breaking your application. Also note that it handles several cases of type-parsing for you (for example, the Style.Bold property can easily be strongly typed as a bool rather than parsed as a "0" or "1" string)
Another option is to use the XML Serialization Framework. This allows you to map XML and C# objects back and forth. For some applications, this works quite well. It lets you deal with C# objects almost exclusively, not having to worry about parsing XML bit by bit.
You might want to take a look at the MSDN pages for LINQToXML. LINQ to XML for example. Alternatively just search for "LinQ to XML". There are lots of useful articles.
In the code below, check the following line:
//here I need to put the object "nd" into a "bucket" so that I can finish the loop and then return EVERYTHING together.
My question is, how do I combine objects to return as JSON? The reason why I need to "combine" is because of the loop which assigns values to specific properties of this class. Once each class has been done getting property values, I need to return everything as JSON.
namespace X
{
public class NotificationsController : ApiController
{
public List<NotificationTreeNode> getNotifications(int id)
{
var bo = new HomeBO();
var list = bo.GetNotificationsForUser(id);
var notificationTreeNodes = (from GBLNotifications n in list
where n.NotificationCount != 0
select new NotificationTreeNode(n)).ToList();
foreach (var notificationTreeNode in notificationTreeNodes)
{
Node nd = new Node();
nd.notificationType = notificationTreeNode.NotificationNode.NotificationType;
var notificationList = bo.GetNotificationsForUser(id, notificationTreeNode.NotificationNode.NotificationTypeId).Cast<GBLNotifications>().ToList();
List<string> notificationDescriptions = new List<string>();
foreach (var item in notificationList)
{
notificationDescriptions.Add(item.NotificationDescription);
}
nd.notifications = notificationDescriptions;
//here I need to put the object "nd" into a "bucket" so that I can finish the loop and then return EVERYTHING together.
}
return bucket;
}
}
public class Node
{
public string notificationType
{
get;
set;
}
public List<string> notifications
{
get;
set;
}
}
}
You can simply add each item to a list as you're iterating through the source collection:
public List<Node> getNotifications(int id)
{
var bucket = new List<Node>(notificationTreeNodes.Count);
foreach (var notificationTreeNode in notificationTreeNodes)
{
Node nd = new Node();
...
bucket.Add(nd);
}
return bucket;
}