I was trying to use a LINQ query that will iterate through an xml document. However I wanted to either use an OR statement or a string.toLower() to make sure it will always get the data it needs
I currently have:
// read all <item> tags and put the in an array.
XDocument xml = XDocument.Parse(xmlData);
var newItems = (from story in xml.Descendants("item")
select new Feed
{
Title = ((string) story.Element("title")),
Link = ((string) story.Element("link")),
Description = ((string) story.Element("description")),
PublishDate = ((string) story.Element("pubDate")),
}
).Take(20).ToList();
what I still want to change:
(E.G.) Title = ((string)story.Element("title")) needs to search case insensitive.
from story in xml.Descendants("item") select new Feed needs to search in item as well as in entry (both case-insensitive).
PS: as I am iterating through the an RSS document I cannot directly access the XML document.
thanks for input.
You can aways create extension methods for that. Here is the class that I normally use:
public static class XElementExtensions {
public static bool EqualsIgnoreCase(this XName name1, XName name2) {
return name1.Namespace == name2.Namespace &&
name1.LocalName.Equals(name2.LocalName, StringComparison.OrdinalIgnoreCase);
}
public static XElement GetChild(this XElement e, XName name) {
return e.EnumerateChildren(name).FirstOrDefault();
}
public static IEnumerable<XElement> EnumerateChildren(this XElement e, XName name) {
return e.Elements().Where(i = > i.Name.EqualsIgnoreCase(name));
}
}
Then, you can change your code to something like this:
var newItems = (from story in xml.Root.EnumerateChildren("item")
select new Feed
{
Title = ((string) story.GetChild("title")),
Link = ((string) story.GetChild("link")),
Description = ((string) story.GetChild("description")),
PublishDate = ((string) story.GetChild("pubDate")),
}).Take(20).ToList();
XML is typically defined by a schema - it should have a fixed format for the element names - so title isn't the same as TiTlE in XML terms. I don't think it's possible to do what you want, using .Element
Related
I have an XML document from a web service that I am trying to query. However, I am not sure how to query the XML when it has elements nested inside other elements.
Here is a section of the XML file (I haven't included all of it because it's a long file):
<response>
<display_location>
<full>London, United Kingdom</full>
<city>London</city>
<state/>
<state_name>United Kingdom</state_name>
<country>UK</country>
<country_iso3166>GB</country_iso3166>
<zip>00000</zip>
<magic>553</magic>
<wmo>03772</wmo>
<latitude>51.47999954</latitude>
<longitude>-0.44999999</longitude>
<elevation>24.00000000</elevation>
</display_location>
<observation_location>
<full>London,</full>
<city>London</city>
<state/>
<country>UK</country>
<country_iso3166>GB</country_iso3166>
<latitude>51.47750092</latitude>
<longitude>-0.46138901</longitude>
<elevation>79 ft</elevation>
</observation_location>
I can query "one section at a time" but I'm constructing an object from the LINQ. For example:
var data = from i in weatherResponse.Descendants("display_location")
select new Forecast
{
DisplayFullName = i.Element("full").Value
};
var data = from i in weatherResponse.Descendants("observation_location")
select new Forecast
{
ObservationFullName = i.Element("full").Value
};
And my "Forecast" class is basically just full of properties like this:
class Forecast
{
public string DisplayFullName { get; set; };
public string ObservationFullName { get; set; };
//Lots of other properties that will be set from the XML
}
However, I need to "combine" all of the LINQ together so that I can set all the properties of the object. I have read about nested LINQ but I do not know how to apply it to this particular case.
Question: How do I go about "nesting/combining" the LINQ so that I can read the XML and then set the appropriate properties with said XML?
One possible way :
var data = from i in weatherResponse.Descendants("response")
select new Forecast
{
DisplayFullName = (string)i.Element("display_location").Element("full"),
ObservationFullName = (string)i.Element("observation_location").Element("full")
};
Another way ... I prefer using the Linq extension methods in fluent style
var results = weatherResponse.Descendants()
.SelectMany(d => d.Elements())
.Where(e => e.Name == "display_location" || e.Name == "observation_location")
.Select(e =>
{
if(e.Name == "display_location")
{
return new ForeCast{ DisplayFullName = e.Element("full").Value };
}
else if(e.Name == "observation_location")
{
return new ForeCast{ ObservationFullName = e.Element("full").Value };
}
else
{
return null;
}
});
I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;
I want to read files, each of which contains a person's details, as below, and convert it to a Person object.
Covert below
id=1
firstName=John
lastName=Smith
To:
public class Person
{
public int Id {get;set;}
public string FirstName{get;set;}
public string LastName{get;set;}
}
Are there .NET built-in methods to do that, or third party library. I cannot find it via google.
Update:
The file format CANNOT be changed.
.NET is really into XML, so you won't find build-in functionality for INI-like formats. But there are a bunch of libs that make it easy to read and write such files, e.g. ini-parser or nini, but you still have to do the mapping to and from objects manually.
You could parse the text with String.Split and LINQ:
Dictionary<string, string> dict = text
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select(e => e.Split('='))
.ToDictionary(strings => strings[0], strings => strings[1]);
Then use something like Dictionary Adapter.
For example using File.ReadAllLines, a little bit of Linq and String.Substring?
var lines = File.ReadAllLines(path).Select(l => l.Trim());
var idLine = lines.FirstOrDefault(l => l.StartsWith("id=", StringComparison.OrdinalIgnoreCase));
var lNameLine = lines.FirstOrDefault(l => l.StartsWith("lastname=", StringComparison.OrdinalIgnoreCase));
var fNameLine = lines.FirstOrDefault(l => l.StartsWith("firstname=", StringComparison.OrdinalIgnoreCase));
if (idLine != null && lNameLine != null && fNameLine != null)
{
Person person = new Person()
{
Id = int.Parse(idLine.Substring(idLine.IndexOf("=") + 1)),
FirstName = fNameLine.Substring(fNameLine.IndexOf("=") + 1),
LastName = lNameLine.Substring(lNameLine.IndexOf("=") + 1)
};
}
(assuming that there's just one person per file)
But i would use a different format like XML (or a database of course).
I really think you should consider changing your input data format into something more standard (like XML or JSON).
But that does not mean you can't read your file at all. You should just read your text file by your own:
var people = new List<Person>();
using (var stream = File.OpenRead("Input.txt"))
{
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
int id = int.Parse(reader.ReadLine().Substring(3));
string firstName = reader.ReadLine().Substring(10);
string lastName = reader.ReadLine().Substring(9);
var newPerson = new Person()
{
Id = id,
FirstName = firstName,
LastName = lastName
};
people.Add(newPerson);
}
}
}
If you have the data in a format like this:
<Person>
<Id>1</Id>
<FirstName>John</FirstName>
<LastName>Smith</LastName>
</Person>
Then this C# code will desrialise into an instance of Person
//assuming you have a string called "data" that contains the above XML.
XDocument xd=XDocument.Parse(data); //needs System.Xml.Linq for XDocument type.
using(var reader = xd.CreateReader())
{
using(XmlSerializer ser = new XmlSerializer(typeof(Person))
{
Person p = ser.Deserialize(reader) as Person;
//p will be null if it didn't work, so make sure to check it!
}
}
Note that the deserializer is case sensitive so you need to make sure the element cases match the casing of the properties in your class (You can get arond this by decorating your properties with Serializer attributes that tell the serialzer how to map them here)
The plain native serialzer is great for simple objects like this but can trip you up on some data types like char, bool, etc, so do checkout that link on the attributes.
If you wanted to do it from the format you gave in the question, you'd need to write a custom serialiser, in your case my advice would be to read from your file and generate XML from the data using XDocument Hope that helps.
I'm working on a project that requires me to build a game's rooms, items and NPC in a separate database. I've chosen XML, but something prevents me from properly parsing the XML in my C# code. What am I doing wrong?
My errors are these:
System.xml.xmlnode does not contain a definition for HasAttribute
(this goes for GetAttribute as well) and no extension method accepting 'HasAttribute' accepting a first argument of type System.Xml.XmlNode ?
This also goes for GetParentNode, and my very last line
string isMoveableStr = xmlRoom.GetAttribute("isMoveable");
somehow goes:
the name xmlRoom does not exist in the current context
Here's the method:
public void loadFromFile()
{
XmlDocument xmlDoc = new XmlDocument(); // create an xml document object in memory.
xmlDoc.Load("gamedata.xml"); // load the XML document from the specified file into the object in memory.
// Get rooms, NPCs, and items.
XmlNodeList xmlRooms = xmlDoc.GetElementsByTagName("room");
XmlNodeList xmlNPCs = xmlDoc.GetElementsByTagName("npc");
XmlNodeList xmlItems = xmlDoc.GetElementsByTagName("item");
foreach(XmlNode xmlRoom in xmlRooms) { // defaults for room:
string roomID = "";
string roomDescription = "this a standard room, nothing special about it.";
if( !xmlRoom.HasAttribute("ID") ) //http://msdn.microsoft.com/en-us/library/acwfyhc7.aspx
{
Console.WriteLine("A room was in the xml file without an ID attribute. Correct this to use the room");
continue; //skips remaining code in loop
} else {
roomID = xmlRoom.GetAttribute("id"); //http://msdn.microsoft.com/en-us/library/acwfyhc7.aspx
}
if( xmlRoom.hasAttribute("description") )
{
roomDescription = xmlRoom.GetAttribute("description");
}
Room myRoom = new Room(roomDescription, roomID); //creates a room
rooms.Add(myRoom); //adds to list with all rooms in game ;)
} foreach(XmlNode xmlNPC in xmlNPCs)
{ bool isMoveable = false;
if( !xmlNPC.hasAttribute("id") )
{
Console.WriteLine("A NPC was in the xml file, without an id attribute, correct this to spawn the npc");
continue; //skips remaining code in loop
}
XmlNode inRoom = xmlNPC.getParentNode();
string roomID = inRoom.GetAttribute("id");
if( xmlNPC.hasAttribute("isMoveable") )
{
string isMoveableStr = xmlRoom.GetAttribute("isMoveable");
if( isMoveableStr == "true" )
isMoveable = true;
}
}
}
System.Xml.XmlElement has the function you are looking for. You are getting XMLNode's. You will need to cast the nodes to XmlElement to get that function.
xmlElement = (System.Xml.XmlElement)xmlRoom;
This is not specifically germane to your question, but a response to #ChaosPandion's suggestion and your question in the comments, here is your code example using Linq to XML:
var xdoc = XDocument.Load("gamedata.xml");
var xRooms = xdoc.Descendants("room");
List<Room> rooms;
//If an element doesn't have a given attribute, the Attribute method will return null for that attribute
//Here we first check if any rooms are missing the ID attribute
if (xRooms.Any( xRoom => (string)xRoom.Attribute("ID") == null )) {
Console.WriteLine("A room was in the xml file without an ID attribute...");
} else {
rooms = (
from xRoom in xRooms
select new Room(
xRoom.Attribute("description") ?? "this a standard room, nothing special about it.",
(int)xRoom.Attribute("ID")
)
).ToList();
}
var xNPCs = xdoc.Descendants("npc");
if (xNPCs.Any( xNPC => (string)xNPC.Attribute("id") == null )) {
Console.WriteLine("A NPC was in the xml file, without an id attribute, correct this to spawn the npc");
} else {
var npcs = (
from xNPC in xNPCs
let inRoom = xNPC.Parent
select new {
xNPC,
inRoom,
isMoveable = (string)xNPC.Attribute("isMoveable") != null &&
(string)inRoom.Attribute("isMoveable") == true
}
).ToList();
}
Then you can use a simple foreach on the npcs collection:
foreach (var npc in npcs) {
Console.WriteLine(inRoom.Attribute("ID"));
Console.WriteLine(npc.IsMoveable);
}
OTOH since this code makes use of the Descendants method, which returns an collection of XElement (the type corresponding to an XML element) and not of XNode (the type corresponding to an XML node), the whole issue of a node object not having attributes is neatly sidestepped.
XmlNode does not have methods HasAttribute or GetAttribute. If you look at the MSDN entry for XmlNode, you can see the methods it has available.
http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.aspx
If you use XmlNode.Attributes["ATTRIBUTE_NAME"] or in your case xmlRoom.Attributes["ID"], you should be able to find the attribute you're looking for. That is, if you would like to continue using XmlNodes.
The following link has an example of how to retrieve attributes by name from an XmlNode:
http://msdn.microsoft.com/en-us/library/1b823yx9.aspx
I'm currently working on a Silverlight app and need to convert XML data into appropriate objects to data bind to. The basic class definition for this discussion is:
public class TabularEntry
{
public string Tag { get; set; }
public string Description { get; set; }
public string Code { get; set; }
public string UseNote { get; set; }
public List<string> Excludes { get; set; }
public List<string> Includes { get; set; }
public List<string> Synonyms { get; set; }
public string Flags { get; set; }
public List<TabularEntry> SubEntries { get; set; }
}
An example of the XML that might come in to feed this object follows:
<I4 Ref="1">222.2
<DX>Prostate</DX>
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)</I>
</I>
</EX>
<FL>M</FL>
</I4>
So, various nodes map to specific properties. The key ones for this question are the <EX> and <I> nodes. The <EX> nodes will contain a collection of one or more <I> nodes and in this example matches up to the 'Excludes' property in the above class definition.
Here comes the challenge (for me). I don't have control over the web service that emits this XML, so changing it isn't an option. You'll notice that in this example one <I> node also contains another collection of one or more <I> nodes. I'm hoping that I could use a LINQ to XML query that will allow me to consolidate both levels into a single collection and will use a character that will delimit the lower level items, so in this example, when the LINQ query returned a TablularEntry object, it would contain a collection of Exclude items that would appear as follows:
adenomatous hyperplasia of prostate
(600.20-600.21)
prostatic:
*adenoma (600.20-600.21)
*enlargement (600.00-600.01)
*hypertrophy (600.00-600.01)
So, in the XML the last 3 entries are actually child objects of the second entry, but in the object's Excludes property, they are all part of the same collection, with the former child objects containing an identifier character/string.
I have the beginnings of the LINQ query I'm using below, I can't quite figure out the bit that will consolidate the child objects for me. The code as it exists right now is:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Elements("I")
select i.Value).ToList()
}).ToList();
return result;
}
I'm thinking that I need to nest a FROM statement inside the
Excludes = (from i...)
statement to gather up the child nodes, but can't quite work it through. Of course, that may be because I'm off in the weeds a bit on my logic.
If you need more info to answer, feel free to ask.
Thanks in advance,
Steve
Try this:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Descendants("I")
select (i.Parent.Name == "I" ? "*" + i.Value : i.Value)).ToList()
}).ToList();
return result;
}
(edit)
If you need the current nested level of "I" you could do something like:
List<TabularEntry> GetTabularEntries(XElement source)
{
List<TabularEntry> result;
result = (from tabularentry in source.Elements()
select new TabularEntry()
{
Tag = tabularentry.Name.ToString(),
Description = tabularentry.Element("DX").ToString(),
Code = tabularentry.FirstNode.ToString(),
UseNote = tabularentry.Element("UN") == null ? null : tabularentry.Element("UN").Value,
Excludes = (from i in tabularentry.Element("EX").Descendants("I")
select (ElementWithPrefix(i, '*'))).ToList()
}).ToList();
return result;
}
string ElementWithPrefix(XElement element, char c)
{
string prefix = "";
for (XElement e = element.Parent; e.Name == "I"; e = e.Parent)
{
prefix += c;
}
return prefix + ExtractTextValue(element);
}
string ExtractTextValue(XElement element)
{
if (element.HasElements)
{
return element.Value.Split(new[] { '\n' })[0].Trim();
}
else
return element.Value.Trim();
}
Input:
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)
<I>Bla1</I>
<I>Bla2
<I>BlaBla1</I>
</I>
<I>Bla3</I>
</I>
</I>
</EX>
Result:
* adenomatous hyperplasia of prostate (600.20-600.21)
* prostatic:
* *adenoma (600.20-600.21)
* *enlargement (600.00-600.01)
* *hypertrophy (600.00-600.01)
* **Bla1
* **Bla2
* ***BlaBla1
* **Bla3
Descendants will get you all of the I children. The FirstNode will help seperate the value of prostatic: from the values of its children. The there's a return character in the value of prostatic:, which I removed with Trim.
XElement x = XElement.Parse(#"
<EX>
<I>adenomatous hyperplasia of prostate (600.20-600.21)</I>
<I>prostatic:
<I>adenoma (600.20-600.21)</I>
<I>enlargement (600.00-600.01)</I>
<I>hypertrophy (600.00-600.01)</I>
</I>
</EX>");
//
List<string> result = x
.Descendants(#"I")
.Select(i => i.FirstNode.ToString().Trim())
.ToList();
Here's a hacky way to get those asterisks in. I don't have time to improve it.
List<string> result2 = x
.Descendants(#"I")
.Select(i =>
new string(Enumerable.Repeat('*', i.Ancestors(#"I").Count()).ToArray())
+ i.FirstNode.ToString().Trim())
.ToList();