Grabbing Attribute from XMl document using Xdoc - c#

Having a hard time retrieving an attribute from my XML. I need to grab this attribute, and send and store it. I can't get it to garb the attributes. :( Just need help with the attributes.
<portfolios>
<portfolio>
<id>00001</id>
<investment ticker="ASD">
<shares>20</shares>
<price>42.50</price>
</investment>
</portfolio>
<pricedata days="4">
<stock ticker="ASD">
<price value="42.50"/>
<price value="43.50"/>
<price value="39.00"/>
<price value="45.00"/>
</stock>
</pricedata>
</portfolios>
What I have so far!
public bool readXmlData(String filename)
{
XDocument document = XDocument.Load(filename);
foreach (XElement portfolio in document.Descendants("portfolio"))
{
XElement id = portfolio.Element("id");
string id2 = id != null ? id.Value : string.Empty;
portList.Add(new SmallPortfolio(id2));
XAttribute ticker = portfolio.Attribute("investment");
foreach(XElement investment in document.Descendants("investment"))
{
XElement shares = investment.Element("shares");
XElement price = investment.Element("price");
temp.Add(new Investment(
ticker != null ? ticker.Value : string.Empty,
shares != null ? int.Parse(shares.Value) : default(int),
price != null ? double.Parse(shares.Value) : default(double)
));
}
}
foreach (XElement stock in document.Descendants("pricedata"))
{
XAttribute tick = stock.Attribute("stock");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in document.Descendants("stock"))
{
XAttribute value = price.Attribute("price");
pricetemp2.Add(value.Value);
}
groupList.Add(new PriceGroup(tick,pricetemp2));
}
return true;
}
public List<SmallPortfolio> getPortfolioList() { return null; }
public List<PriceGroup> getPriceList() { return null; }
}

<price> is an element, but you are accessing it as if it was an attribute <stock price="..."/>.
Try this:
foreach (XElement stock in document.Descendants("stock"))
{
string ticker = (string)stock.Attribute("ticker");
List<Double> pricetemp2 = new List<Double>();
foreach (XElement price in stock.Descendants("price"))
{
double value = (double)price.Attribute("value");
pricetemp2.Add(value);
}
groupList.Add(new PriceGroup(ticker, pricetemp2));
}
Casting XAttribute to double will use the proper XML rules for numbers (XmlConvert.ToDouble). Using double.Parse is incorrect as it uses culture-specific number formatting (e.g. in Germany it expects a decimal comma instead of a decimal point).

Related

Searching for distinct XML structures in a document with Linq to XML in C#

I have written a little C# to parse through a number of elements in an XML document and return only the first of those elements which have a distinct child structure? For example, if I have the following XML doc, then a call to rootElement.DistinctStructures("base") let's say returns an IEnumerable<XElement> containing just the base elements with ids 1, 3, and 5.
<root>
<base id="1">
<a>text</a>
</base>
<base id="2">
<a>more text</a>
</base>
<base id="3">
<b>text</b>
</base>
<base id="4">
<a>other text</a>
</base>
<base id="5">
<a>
<c>sub text</c>
</a>
</base>
</root>
The basic code generates a unique key from the element name and text nodes in the structure and compares them to a saved collection of unique elements. My question is whether there is a neater way to do this?
private Dictionary<string, XElement> uniqueElements = new Dictionary<string, XElement>();
public void Go()
{
foreach (var entry in xmlDoc.Elements("e"))
{
string keyString = AsStructureString(entry).ToString();
if (!uniqueElements.Keys.Contains(keyString))
{
uniqueElements.Add(keyString, entry);
}
}
}
public StringBuilder AsStructureString(this XElement input)
{
StringBuilder sb = new StringBuilder(input.Name.LocalName);
var NodesOfNote = input.Nodes().Where(n => n.NodeType == XmlNodeType.Element || n.NodeType == XmlNodeType.Text).ToList();
if (NodesOfNote.Any())
{
sb.Append(">>");
}
foreach (var childNode in NodesOfNote)
{
if (childNode.NodeType == XmlNodeType.Element)
{
sb.Append((childNode as XElement).AsStructureString());
}
if (childNode.NodeType == XmlNodeType.Text)
{
sb.Append("txt");
}
if (!childNode.IsLastIn(NodesOfNote))
{
sb.Append("|");
}
}
return sb;
}
It might be easier than you think. If what determines the structure of a node is its elements and text (regardless of the content), you could do this:
IEnumerable<XElement> DistinctStructures(XContainer root, XName name)
{
return
from d in root.Descendants(name)
group d by GetKey(d) into g
select g.First();
string GetKey(XElement n) =>
String.Join(",",
n.DescendantNodes().Select(d =>
d is XElement e ? $"{e.Name}^{GetDepth(e)}"
: d is XText t ? $"<text>^{GetDepth(t)}"
: default
)
);
int GetDepth(XObject o)
{
var depth = 0;
for (var c = o; c != null; c = c.Parent)
++depth;
return depth;
}
}

How to save attribute value on xml file?

I'm trying to save a value in my xml file. In the code below, the line "s.Attribute("Value").Value = value; break;" executes and the file is saved but it doesn't change the value of the attribute
public void CustomSettingXML_WriteValue(string key, string value)
{
XDocument doc = XDocument.Load(xmlFile);
var elements = from x in XElement.Load(xmlFile).Elements("Item") select x;
foreach (var s in elements)
{
if (s.Attribute("Text").Value == key)
{
s.Attribute("Value").Value = value;
doc.Save(#xmlFile);
break;
}
}
}
There are in fact two things that might have to vary.
a) You are reading the Xml using XDocument.Load as well as XElement.Load. While altering, you are using Elements, and while saving you are using XDocument.
b) Since hierarchy in XML is (Items.Item), it would be better you use Descendants to parse the elements.
Full Code
public void CustomSettingXML_WriteValue(string key, string value)
{
XDocument doc = XDocument.Load(xmlFile);
var elements = from x in doc.Descendants("Item") select x;
foreach (var s in elements)
{
if (s.Attribute("Text").Value == key)
{
s.Attribute("Value").Value = value;
doc.Save(#xmlFile);
break;
}
}
}

Optimisation of LinqToXml

Could anyone tell me if this is correct or if there's a faster/cleaner way to do it?
//I load p as a xDoc so I can use linq to xml
XDocument myDoc = XDocument.Parse(p);
// retrieve each "Item" node except if orderedQuantity is 0 or name is "nullorderitem"
IEnumerable<XElement> orderedRes = from item in myDoc.Descendants("Item")
where ((double)item.Element("orderedQuantity") > 0 && item.Element("ResourceId").Name != "NULLOrderItem")
select item;
foreach (XElement xelem in orderedRes)
{
if(xelem.Element("Name").Value.ToLower() == "oneofmyvalueIneed"
|| xelem.Element("Name").Value.ToLower() == "anotherone"
|| xelem.Element("Name").Value.ToLower() == "yetanother")
{
FieldProperties elem = new FieldProperties();
elem.Fieldname = xelem.Element("Name").Value;
elem.Fieldvalue = xelem.Element("OrderedQuantity").Value;
lElem.Add(elem);
}
}
Knowing that lElem is a list of FieldProperties, and FieldProperties is a class looking like this :
FieldProperties
string fieldname
string fieldvalue
and p is a string that looks like
<items>
<item>
<resourceid>blablabla</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>0.0</orderedquantity>
</item>
<item>
<resourceid>oneofmyvalueIneed</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>1.0</orderedquantity>
</item>
<item>
<resourceid>yetanother</resourceid>
<somerandomtag>whocares</somerandomtag>
<orderedquantity>0.0</orderedquantity>
</item>
</items>
You can improve the if statement by using the .Contains method.
var validNames = new List<string> {"oneofmyvalueIneed","anotherone","yetanother"}
XDocument myDoc = XDocument.Parse(p);
var result = from item in myDoc.Descendants("Item")
where ((double)item.Element("orderedQuantity") > 0 &&
item.Element("ResourceId").Name != "NULLOrderItem") && // See Charles's comment about this line
validNames.Contains(iten.Element("Name").Value.ToLower())
select item;
foreach (var item in orderedRes)
{
FieldProperties elem = new FieldProperties();
elem.Fieldname = xelem.Element("Name").Value;
elem.Fieldvalue = xelem.Element("OrderedQuantity").Value;
lElem.Add(elem);
}
Then you can also replace the foreach with
select new FieldProperties
{
Fieldname = item.Element("Name").Value,
Fieldvalue = xelem.Element("OrderedQuantity").Value
};
Adding it all together + some optimization when accessing the child elements it would look like:
var validNames = new List<string> {"oneofmyvalueIneed","anotherone","yetanother"}
XDocument myDoc = XDocument.Parse(p);
var result = from item in myDoc.Descendants("Item")
let value = item.Element("orderedQuantity")
let name = iten.Element("Name").Value.ToLower()
where ((double)value > 0 &&
item.Element("ResourceId").Name != "NULLOrderItem") && // See Charles's comment about this line
validNames.Contains(name)
select new FieldProperties
{
Fieldname = name
Fieldvalue = value
};

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.

Linq to Xml Convert a list

I am having trouble wrapping my mind around how to do this in linq.
How can i convert this:
<mytags>
<tag1>hello</tag1>
<tag2>hello</tag2>
<tag1>MissingTag</tag1>
<tag1>Goodbye</tag1>
<tag2>Goodbye</tag2>
</mytags>
to this
List<MyObject>
public class MyObject
{
public tag1;
public tag2;
}
Try this out:
string input = "<mytags><tag1>hello</tag1><tag2>hello</tag2><tag1>MissingTag</tag1><tag1>Goodbye</tag1><tag2>Goodbye</tag2></mytags>";
var xml = XElement.Parse(input);
var list = (from x in xml.Elements("tag1")
let next = x.NextNode as XElement
select new MyObject
{
Tag1 = x.Value,
Tag2 = (next != null && next.Name == "tag2") ? next.Value : ""
}).ToList();
This only works for scenarios where tag2 is missing, not the other way around.

Categories