linq separate xml values in child nodes - c#

I want to create a config file to validate userinputs. How do i access the different nodes - the value seems to be every node inside the element. I want to get the separate values to check inputs for length and the right format etc.
C#
var xml = XElement.Load (#"C:\Project\conf\config.xml");
foreach(var child in xml
.Element("Felder").Elements())
{
string cld = child.Name + " " + child.Value;
}
xml:
<?xml version="1.0"?>
<Config>
<Anrede>
<key_1>Testval</key_1>
</Anrede>
<Felder>
<KNR>
<Length>6</Length>
<Format>######</Format>
</KNR>
<AddressStatus>
<Length>1</Length>
<Format>0</Format>
</AddressStatus>
<adressResearch>
<Length>1</Length>
<Format>0</Format>
</adressResearch>
<AnredeNr>
<Length>1</Length>
<Format>0</Format>
</AnredeNr>
</Felder>
</Config>
Output:
KNR 6######
AddressStatus 10
adressResearch 10
AnredeNr 10
Desired Output:
KNR [6,######] or even better KNR.Length = 6, KNR.Format = "######"
Is there a better way to write a config like this?

this linq query (SelectMany in query syntax)
IEnumerable<DictionaryEntry> keyValuePairs =
from child in xml.Element("Felder").Elements()
from tag in child.Elements()
select new DictionaryEntry(String.Format("{0}.{1}", child.Name, tag.Name), tag.Value);
gives output for current xml structure (formatting may be different):
{ Key = KNR.Length, Value = 6 }
{ Key = KNR.Format, Value = ###### }
{ Key = AddressStatus.Length, Value = 1 }
{ Key = AddressStatus.Format, Value = 0 }
{ Key = adressResearch.Length, Value = 1 }
{ Key = adressResearch.Format, Value = 0 }
{ Key = AnredeNr.Length, Value = 1 }
{ Key = AnredeNr.Format, Value = 0 }
try it with a fiddle

Value will return all descendant text nodes, hence when you're getting Value for e.g. KNR it returns 6######.
You need to access the child element values separately:
foreach(var field in xml.Element("Felder").Elements())
{
var fieldName = field.Name.LocalName;
var length = (int)field.Element("Length");
var format = (string)field.Element("Format");
// do what you want with these
// or ...
var childValues = field.Elements().Select(x => x.Value);
var childValuesCommaSeparated = string.Join("," childValues);
}

If you would like to search Xml data for specific node to get its subnodes, you can use Linq to XML. For example:
XDocument xdoc = XDocument.Load(#"C:\Project\conf\config.xml");
string sSetting = "KNF";
string sSetting = "KNR";
var result = xdoc.Descendants("Felder").Descendants()
.Where(x=>x.Name == sSetting)
.Select(x=>new {
Length = x.Element("Length").Value,
Format = x.Element("Format").Value
});
Result:
Length Format
6 ######
In case you want to get every node to be get list of setting's name, length and format, you can try this:
var result1 = xdoc.Descendants("Felder").Elements()
.Select(x=>new
{
Name = x.Name,
Length = x.Element("Length").Value,
Format = x.Element("Format").Value
})
.ToList();
foreach(var setting in result1)
{
Console.WriteLine("{0} | {1} | {2}",
setting.Name, setting.Length, setting.Format);
}

Related

Parse() Can't Get All Element

i have smailler XML file i want to parse it .
<?xml version="1.0" encoding="utf-8"?>
<!-- GeeSuth Checker Time -->
<Geranal>
<AftrNoon Min="20" StartAftrNoon="05:00:00" EndAftrNoon="11:01:00" />
<Night Min="50" StartNight="2:00:00" EndNight="6:00:00" />
<AlFajr Min="100" StartAlfajr="9:00:00" EndAlfajr="10:00:00" />
</Geranal>
i want to get all the value in line , like
<AftrNoon Min="20" StartAftrNoon="05:00:00" EndAftrNoon="11:01:00" />
i need save the values in string paramater :.
Min
StartAftrNoon
EndAftrNoon
and save it in paramater ?
im using this :.
XmlReader ReaderXML = XmlReader.Create("Date.xml");
while (ReaderXML.Read())
{
ReaderXML.IsStartElement("Geranal");
if (ReaderXML.NodeType == XmlNodeType.Element && ReaderXML.Name == "AftrNoon")
{
//AftarNoon Fill
txt_Min_Aftrnoon.Text = ReaderXML.GetAttribute(0);
dt_Aftr_start.Text = ReaderXML.GetAttribute(1);
dt_aftar_End.Text = ReaderXML.GetAttribute(2);
}
if (ReaderXML.NodeType == XmlNodeType.Element && ReaderXML.Name == "Night")
{
txt_Min_Night.Text = ReaderXML.GetAttribute(0);
dt_Night_Start.Text = ReaderXML.GetAttribute(1);
dt_Night_end.Text = ReaderXML.GetAttribute(2);
}
if (ReaderXML.NodeType == XmlNodeType.Element && ReaderXML.Name == "AlFajr")
{
txt_Min_Fajr.Text = ReaderXML.GetAttribute(0);
dt_Fajr_Start.Text = ReaderXML.GetAttribute(1);
dt_fajar_end.Text = ReaderXML.GetAttribute(2);
}
}
It's Not get all elements value.
Just put all name/value pairs to a dictionary. Using Linq2Xml
var values = XDocument.Load(filename)
.Descendants("AftrNoon")
.First()
.Attributes()
.ToDictionary(a => a.Name, a => a.Value);
Now you can access them like
var min = values["Min"];
or
foreach(var kv in values)
{
Console.WriteLine(kv.Key + ":" + kv.Value);
}
To start with your XML has a small issue with the naming of the attributes that makes it hard to parse - the element AlFajr has a capital F, yet the attributes do not. If you can fix that then this code works nicely:
var xd = XDocument.Load("Date.xml");
var nodes =
(
from e in xd.Root.Elements()
let Min = e.Attribute("Min").Value
let Start = e.Attribute("Start" + e.Name.LocalName).Value
let End = e.Attribute("End" + e.Name.LocalName).Value
select new { e.Name, Min, Start, End, }
).ToDictionary(x => x.Name, x => new { x.Min, x.Start, x.End });
That gives me this:
Now I can use that to populate your fields very easily:
txt_Min_Aftrnoon.Text = nodes["AftrNoon"].Min;
dt_Aftr_start.Text = nodes["AftrNoon"].Start;
dt_aftar_End.Text = nodes["AftrNoon"].End;
txt_Min_Night.Text = nodes["Night"].Min;
dt_Night_Start.Text = nodes["Night"].Start;
dt_Night_end.Text = nodes["Night"].End;
txt_Min_Fajr.Text = nodes["AlFajr"].Min;
dt_Fajr_Start.Text = nodes["AlFajr"].Start;
dt_fajar_end.Text = nodes["AlFajr"].End;
Alternatively, you could also set up a dictionary for your text boxes and, using the above code, assign the values like this:
var textBoxes = new []
{
new { Name = "AftrNoon", Min = txt_Min_Aftrnoon, Start = dt_Aftr_start, End = dt_aftar_End },
new { Name = "Night", Min = txt_Min_Night, Start = dt_Night_Start, End = dt_Night_end },
new { Name = "AlFajr", Min = txt_Min_Fajr, Start = dt_Fajr_Start, End = dt_fajar_end },
};
foreach (var tb in textBoxes)
{
tb.Min.Text = nodes[tb.Name].Min;
tb.Start.Text = nodes[tb.Name].Start;
tb.End.Text = nodes[tb.Name].End;
}
Another alternative, that eliminates the need to fix the attribute naming issue, is to just do this:
var xd = XDocument.Load("Date.xml");
txt_Min_Aftrnoon.Text = xd.Root.Element("AftrNoon").Attribute("Min").Value;
dt_Aftr_start.Text = xd.Root.Element("AftrNoon").Attribute("StartAftrNoon").Value;
dt_aftar_End.Text = xd.Root.Element("AftrNoon").Attribute("EndAftrNoon").Value;
txt_Min_Night.Text = xd.Root.Element("Night").Attribute("Min").Value;
dt_Night_Start.Text = xd.Root.Element("Night").Attribute("StartNight").Value;
dt_Night_end.Text = xd.Root.Element("Night").Attribute("EndNight").Value;
txt_Min_Fajr.Text = xd.Root.Element("AlFajr").Attribute("Min").Value;
dt_Fajr_Start.Text = xd.Root.Element("AlFajr").Attribute("StartAlfajr").Value;
dt_fajar_end.Text = xd.Root.Element("AlFajr").Attribute("EndAlfajr").Value;

Differentiating between different values with a Line.Contains()

Something I was curious about, I'm coding a utility for a old game that I play and this allows for custom NPC's. Long story short, I'm coding a reader for these custom NPC files. I've gotten most of the reading down with a line.contains() method (all code will be shown later) but there's a problem. The file can contain either just "height" or "gfxheight" which both do different things. Using line.contains("width") will make it output both width and gfxwidth twice. I don't really know any good way to explain it so here's the file:
width=32
height=32
gfxwidth=64
gfxheight=32
nofireball=1
noiceball=1
noyoshi=1
grabside=0
The Console output when I read it in and do what I need to split the lines and such:
And here's the code I use for height and gfxheight (of course there are others but these are the only problems I have when reading):
if (line.Contains("height"))
{
var split = line.Split(new char[] { '=' }, 2);
decimal dc;
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcHeight.Value = Decimal.Parse(split[1].ToString());
npcHeight.Enabled = true;
npcHCb.Checked = true;
}
if (line.Contains("gfxheight"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
}
Of course there's also the code for width and gfxwidth and the other various codes but I'm not going to bother posting those because I can apply what I get for the height to those.
So what would I have to do to differentiate between them? Suggestions?
Thanks in advanced,
Mike
Read the file into a string array, then parse it into a dictionary.
var file = File.ReadAllLines(yourFile);
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = s[1] })
.ToDictionary(x => x.Key, x => x.Value);
Now you can access anything you want by referencing the key:
var gfxHeight = config["gfxheight"]; // gfxHeight is a string containing "32"
If you know the value after the = is always a number, you could parse it:
var config = (from line in file
let s = line.Split('=')
select new { Key = s[0], Value = int.Parse(s[1]) })
.ToDictionary(x => x.Key, x => x.Value);
var gfxHeight = config["gfxheight"]; // gfxHeight is an int containing 32
Instead of trying to figure out what each line is before splitting it, try splitting it first. This parsing approach leverages the format of the file and has a much-reduced dependency on its data:
foreach (var line in lines) {
var data = line.Split('=', 2);
if (data.Length != 2) {
continue;
}
var attrib = data[0];
var value = data[1];
Console.WriteLine(attrib + " is equal to " + value);
switch (attrib) {
case "height":
// ...
break;
case "gfxheight":
// ...
break;
}
}
I figured out a solution actually! Be warned: I haven't refreshed the page yet to see any proposed answers.
if (line.Contains("width"))
{
if (line.Contains("gfx"))
{
var split = line.Split(new char[] { '=' }, 2);
//var val = int.Parse(split.ToString());
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
npcWidth.Value = Decimal.Parse(split[1].ToString());
npcWidth.Enabled = true;
npcWCb.Checked = true;
}
else
{
var split = line.Split(new char[] { '=' }, 2);
Console.WriteLine(split[0].ToString() + " is equal to " + split[1].ToString());
pNpcWidth.Value = Decimal.Parse(split[1].ToString());
pNpcWidth.Enabled = true;
pNpcWidthCb.Checked = true;
}
}
Basically that ^
What it does is checks if the line is width with that line.Contains method. And if it does, it then checks to see if it contains gfx in it (as in gfxheight, gfxwidth, etc) and if it does, then that's the gfxheight or gfxwidth value. If not, it's the regular height/width.

Get first value in column from CSV file in C#

I am using this code in my Web Api to get data from a csv file, and plug that data into a Item List.
private List<Item> ietms = new List<Item>();
public ItemRepository()
{
string filename = HttpRuntime.AppDomainAppPath + "App_Data\\items.csv";
var lines = File.ReadAllLines(filename).Skip(1).ToList();
for (int i = 0; i < lines.Count; i++)
{
var line = lines[i];
var columns = line.Split('$');
//get rid of newline characters in the middle of data lines
while (columns.Length < 9)
{
i += 1;
line = line.Replace("\n", " ") + lines[i];
columns = line.Split('$');
}
//Remove Starting and Trailing open quotes from fields
columns = columns.Select(c => { if (string.IsNullOrEmpty(c) == false) { return c.Substring(1, c.Length - 2); } return string.Empty; }).ToArray();
items.Add(new Item()
{
Id = int.Parse(columns[0]),
Name = columns[1],
Description = columns[2],
Price = string.IsNullOrEmpty(columns[3].Trim()) ? null : (double?)double.Parse(columns[3]),
Weight = columns[8],
PhotoUrl = columns[7],
Category=columns[9]
});
}
}
In the csv file one of the columns/value is structured like this:
Groups>Subgroup>item
or in some cases
MajorGroup|Groups>Subgroup>item
How do I pull out only the first value before the > or |, so that I would get the value as Groups in the first case and MajorGroup in the second, and store it in the Category property in the Item List, which is now just set to the entire value in column 9 which would return the whole string "Groups>Subgroup>item".
Add the following line before calling "Add item"
var temp = columns[9].Split('|', '>');
Then assign the category as follows.
Category = temp[0];
Based on: MSDN String Method Documentation
Did you mean something like this?
string data = "MajorGroup|Groups>Subgroup>item";
string groupOrCategory;
if (data.Contains('|'))
{
groupOrCategory = data.Substring(0, data.IndexOf('|'));
}
else
{
groupOrCategory = data.Substring(0, data.IndexOf('>'));
}
Console.WriteLine(groupOrCategory);

Read XML using Linq without element names

I need to read an XML file, I'm trying to use Linq but I'm having some issues getting to the descendants. I need to be able to get the desendants without knowing the element names. Is this possible?
Here is the XML:
<Root>
<myResponse>
<GUID></GUID>
<TType code="1">myTType Value
<TSubType tc="1">TSubType Value</TSubType>
</TType>
<TDate>1999-09-19</TDate>
<TTime>16:00:00.0Z</TTime>
</myResponse>
</Root>
Here is my code:
using (XmlReader nodeReader = XmlReader.Create(#"C:\Projects\GetXML\Test2.xml"))
{
nodeReader.MoveToContent();
XDocument xRoot = XDocument.Load(nodeReader, LoadOptions.SetLineInfo);
foreach (XElement e in xRoot.Elements("Root").DescendantsAndSelf())
Console.WriteLine("{0}{1}{2}",
("".PadRight(e.Ancestors().Count() * 2) + e.Name).PadRight(20), " = " ,
(e.Value).PadRight(5));
}
My results:
Root =
myTType Value
TSubType Value
1999-09-19
16:00:00.0Z
myResponse =
myTType Value
TSubType Value
1999-09-19
16:00:00.0Z
GUID =
TType = myTType Value
TSubType Value
TSubType = TSubType Value
TDate = 1999-09-19
TTime = 16:00:00.0Z
What I am expecting:
Root =
myResponse =
GUID =
TType = myTType Value
TSubType = TSubType Value
TDate = 1999-09-19
TTime = 16:00:00.0Z
Instead of using e.Value, try using:
string.Concat(e.Nodes().OfType<XText>().Select(t => t.Value)).
You could also use:
e.Nodes().OfType<XText>().FirstOrDefault().Value
but then you risk not getting all of the text (if it is split). For example:
<Root>
<myResponse>
<GUID></GUID>
<TType code="1">myTType Value
<TSubType tc="1">TSubType Value</TSubType>
Some more text
</TType>
<TDate>1999-09-19</TDate>
<TTime>16:00:00.0Z</TTime>
</myResponse>
</Root>
With this XML the first piece of code will also include "Some more text", and the second piece won't.
Here is what I did to get the formatted result similar to what you are expecting.
XElement root = XElement.Load("xmlfile1.xml");
var allNodes = root.DescendantNodesAndSelf();
int maxNameLength = allNodes.OfType<XElement>()
.Select(x => x.Name.LocalName.Length)
.Max();
foreach (XNode x in allNodes)
{
XElement node = x as XElement;
if (node != null)
{
int numAncestors = node.Ancestors().Count();
XText textNode = node.Nodes().OfType<XText>().FirstOrDefault();
string text = "";
if (textNode != null)
text = textNode.Value.Trim();
string name = node.Name.LocalName;
// PadLeft() subtracts the string.Length from the padding
// so add its length to the padding amount
string left = name.PadLeft(numAncestors * 5 + name.Length);
// PadRight() wasn't giving the expected results
// so improvised with the following
string right = left + "".PadLeft(maxNameLength + 5 - name.Length);
Console.WriteLine("{0} = {1}", right, text);
}
}
With the result:
Root =
myResponse =
GUID =
TType = myTType Value
TSubType = TSubType Value
TDate = 1999-09-19
TTime = 16:00:00.0Z
Edited to calculate the max name length of all the names, in-case you have a bigger file to run this against and have some really long names.

How to retrieve value from attribute in XML?

What I have:
<?xml version="1.0" encoding="Unicode" standalone="yes"?>
<FiberItems>
<Fiber China="1a" England="1b" Japan="1c" France="1d" Korea="1e"/>
<Fiber China="2a" England="2b" Japan="2c" France="2d" Korea="2e"/>
</FiberItems>
What I want:
1.retrive all the value of "China" into a string array.
2.if a value of China is "1a", retrive all the value of the rest attributes(1b,1c,1d,1e),.
What I do:
1. I write codes for purpose 1 , but never works >_<
XDocument doc = XDocument.Load("/FiberItems.xml");
IEnumerable<string> query = from fiber in doc.Root.Elements("Fiber")
select (string)fiber.Attribute("China");
string[] myString = new string[3];
for (int i = 0; i < 3; i++)
{
foreach (string item in query)
{
myString[i] = (string)item;
}
}
2. for purpose 2, have no idea yet >_<
need helps
You probably shouldn't use an array to collect your data but when you do:
string[] myString = new string[3];
// for (int i = 0; i < 3; i++)
// {
int i = 0;
foreach (string item in query)
{
myString[i] = (string)item;
i += 1;
}
// }
You are doing 3x3 rounds where only 3 are needed.
You can use the following code:
XDocument root = XDocument.Load("/FiberItems.xml");
var attributesChina = root.Elements("FiberItems").Elements("Fiber").Attributes("China");
// attributesChina will contain all the values of china
foreach (XAttribute china in attributesChina)
{
string value = china.value;
}
Check out System.Xml.Linq.
Here's an example to get a list of all the attributes for one element.
var xml = #"<?xml version=""1.0"" encoding=""Unicode"" standalone=""yes""?>
<FiberItems>
<Fiber China=""1a"" England=""1b"" Japan=""1c"" France=""1d"" Korea=""1e""/>
<Fiber China=""2a"" England=""2b"" Japan=""2c"" France=""2d"" Korea=""2e""/>
</FiberItems>";
XDocument doc = XDocument.Parse(xml);
XElement ele = doc.Root.Element("Fiber");
var att = ele.Attributes();
//Load XML element
XElement root = XElement.Parse(File.ReadAllText("/FiberItems.xml"));
//All china attributes (retrieves 1a,2a)
var chinaAttributes= root.Elements().Attributes("China");
//load all rest attributes for china = 1a, loads 1b,1c,1d,1e
var chinaOneARestAttributes = root.Elements().Where(a=>a.Attribute("China")!=null && a.Attribute("China").Value=="1a").Attributes().Select(x=>x.Value).Where(x=>!String.Equals(x,"1a"));
UPDATED for Null Reference Exception. With the data i had tried earlier, i ensured Attribute China was present for all elements.
In XPath:
/FiberItems/Fiber[#China='1a']/#*
gets you all the attributes of Fiber elements with China='1a' and
/FiberItems/Fiber[#China='1a']/#*[local-name()!='China']
gets you the same sequence of attributes excluding the China attribute.
So your requirement is if the attribute China value is "1a" then get all the attribute value of that node. I think it should work
XDocument doc = XDocument.Load("/FiberItems.xml");
IEnumerable<string> query = from fiber in doc.Root.Elements("Fiber")
//condition 1
where (string)fiber.Attribute("China") == "1a"
//condition 2 : Select all except china attribute
select fiber.Attributes().Where(a => a.Name !="China");
Using LINQ to XML:
var xmlStr = #"<?xml version=""1.0"" encoding=""Unicode"" standalone=""yes""?>
<FiberItems>
<Fiber China=""1a"" England=""1b"" Japan=""1c"" France=""1d"" Korea=""1e""/>
<Fiber China=""2a"" England=""2b"" Japan=""2c"" France=""2d"" Korea=""2e""/>
</FiberItems>";
var doc = XDocument.Parse(xmlStr);
var query =
from fiber in doc.Descendants("Fiber")
where (string)fiber.Attribute("China") == "1a"
select String.Join(", ",
(from attr in fiber.Attributes()
where attr.Name != "China"
select (string)attr).ToArray());
This will return a sequence of the other attribute values for each Fiber element that contains a China attribute with the value 1a.

Categories