In my code i iterate through an xelement and have it return the value of each node within that element e.g.
foreach(XElement n in XDocument.Descedants("element_name)
{
Console.WriteLine("Searching: " n.Value);
}
My problem is the both <Directory> elements are returned in the string
Searching: C:\Users\215358\OneDrive\MusicC:\Users\215358\Dropbox\Music
My XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Directories>
<Directory>C:\Users\215358\OneDrive\Music</Directory>
<Directory>C:\Users\215358\Dropbox\Music</Directory>
</Directories>
I expect it to output the second line element in <Directory> like this:
C:\Users\215358\Dropbox\Music
Why is this happening?
XElement.Value gets the concatenated text contents of an element. This includes the text of child elements which is not always very helpful. If you just want the text from the current element, you can find the text node in its child nodes.
foreach(XElement n in XDocument.Descedants("Directory"))
{
var text = n.Nodes().Where (x => x is XText).Cast<XText>().FirstOrDefault ();
if(text!=null){
Console.WriteLine("Searching: " + text.Value);
}else{
Console.WriteLine("No text node found");
}
}
Since you want to iterate through each entry and search element value, you could do something like this.
foreach (var element in doc.Descendants("Directory"))
{
if((string)element.Value == "searchstring")
{
// your logic
}
}
In case if you are looking for second element in the xml, you could apply Skip extension to skip specified count of elements.
var secondelement = doc.Descendants("Directory").Skip(1); // Skip first element;
or if you are looking for last element, you could take Last or LastOrDefault extension.
var lastelement = doc.Descendants("Directory").LastOrDefault();
Check this example.
Related
I need to convert text file with some conversion columns-elements to an nested xml file -
for example
txt flat file - Name,Age,Street name,street number,city name
conversion table-
flat file - Name,Age,Street name,street number,city name
conversion table -
Name-FullName
Age-Age
Street Name-AddressDetail-StreetName
Street Number-AddressDetail-StreetNumber
City Name-AddressDetail-CityName
xml file
<?xml version="1.0" encoding="UTF-8"?>
<Envelop>
<FullName>Steve Mate</FullName>
<Age>22</Age>
<AddressDetail>
<StreetName>Rockford</StreetName>
<StreetNumber>111</StreetNumber>
<CityName>Alimena</CityName>
</AddressDetail>
</Envelop>
What is the best way to implement it?
Can it be used with MVC?
Using XElement?
The following code should work for you
var lines = text.Split(new[] {"\r", "\n", "\r\n"}, 0);
var xDoc = new XDocument(new XElement("Envelop"));
foreach (var line in lines)
{
var values = line.Split('-');
var parentNode = xDoc.Root;
foreach (var value in values.Reverse().Skip(1))
{
var name = value.Replace(" ", "");
var node = parentNode.Element(name);
if (node == null)
{
node = new XElement(name);
parentNode.Add(node);
}
parentNode = node;
}
parentNode.Value = values.Last();
}
Split the text based on newlines.
For each line, split it based on -
Take the root the starting node
For each value in the split line, skipping the first item...
Remove spaces (you can't have spaces in a node name).
Get the child node with that name
If it doesn't exist, create it.
Set the current parent as this node.
Finally set the value of this node to the last item in the split values
dotnetfiddle
Output:
<Envelop>
<FullName>FullName</FullName>
<Age>Age</Age>
<AddressDetail>
<StreetName>StreetName</StreetName>
<StreetNumber>StreetNumber</StreetNumber>
<CityName>CityName</CityName>
</AddressDetail>
</Envelop>
I've done similar a number of times.
There is no special trick to this. You will need to parse the input file to get the elements you need, and then you'll need to create an XML file with the data.
I don't know of any existing way to convert your particular text file format.
I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?
You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();
You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.
How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;
If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();
Here's some fantastic example XML:
<root>
<section>Here is some text<mightbe>a tag</mightbe>might <not attribute="be" />. Things are just<label>a mess</label>but I have to parse it because that's what needs to be done and I can't <font stupid="true">control</font> the source. <p>Why are there p tags here?</p>Who knows, but there may or may not be spaces around them so that's awesome. The point here is, there's node soup inside the section node and no definition for the document.</section>
</root>
I'd like to just grab the text from the section node and all sub nodes as strings. BUT, note that there may or may not be spaces around the sub-nodes, so I want to pad the sub notes and append a space.
Here's a more precise example of what input might look like, and what I'd like output to be:
<root>
<sample>A good story is the<book>Hitchhikers Guide to the Galaxy</book>. It was published<date>a long time ago</date>. I usually read at<time>9pm</time>.</sample>
</root>
I'd like the output to be:
A good story is the Hitchhikers Guide to the Galaxy. It was published a long time ago. I usually read at 9pm.
Note that the child nodes don't have spaces around them, so I need to pad them otherwise the words run together.
I was attempting to use this sample code:
XDocument doc = XDocument.Parse(xml);
foreach(var node in doc.Root.Elements("section"))
{
output += String.Join(" ", node.Nodes().Select(x => x.ToString()).ToArray()) + " ";
}
But the output includes the child tags, and is not going to work out.
Any suggestions here?
TL;DR: Was given node soup xml and want to stringify it with padding around child nodes.
Incase you have nested tags to an unknown level (e.g <date>a <i>long</i> time ago</date>), you might also want to recurse so that the formatting is applied consistently throughout. For example..
private static string Parse(XElement root)
{
return root
.Nodes()
.Select(a => a.NodeType == XmlNodeType.Text ? ((XText)a).Value : Parse((XElement)a))
.Aggregate((a, b) => String.Concat(a.Trim(), b.StartsWith(".") ? String.Empty : " ", b.Trim()));
}
You could try using xpath to extract what you need
var docNav = new XPathDocument(xml);
// Create a navigator to query with XPath.
var nav = docNav.CreateNavigator();
// Find the text of every element under the root node
var expression = "/root//*/text()";
// Execute the XPath expression
var resultString = nav.evaluate(expression);
// Do some stuff with resultString
....
References:
Querying XML, XPath syntax
Here is a possible solution following your initial code:
private string extractSectionContents(XElement section)
{
string output = "";
foreach(var node in section.Nodes())
{
if(node.NodeType == System.Xml.XmlNodeType.Text)
{
output += string.Format("{0}", node);
}
else if(node.NodeType == System.Xml.XmlNodeType.Element)
{
output += string.Format(" {0} ", ((XElement)node).Value);
}
}
return output;
}
A problem with your logic is that periods will be preceded by a space when placed right after an element.
You are looking at "mixed content" nodes. There is nothing particularly special about them - just get all child nodes (text nodes are nodes too) and join they values with space.
Something like
var result = String.Join("",
root.Nodes().Select(x => x is XText ? ((XText)x).Value : ((XElement)x).Value));
I have an xml and I want to append an attribute to each element in xml file.
IEnumerable<XElement> childList = from el in xml.Elements()
select el;
textBox1.Text = childList.ToString();
foreach (XElement el in childList)
{
el.Add(new XAttribute("Liczba_Potomkow", "dziesiec"));
textBox1.Text = el.ToString();
xml.Save("Employees.xml");
}
unfortunately, when I open the file only the first line seems to be affected. (only first elements gets new attribute). Why is so ?
I assume xml is an XDocument? If so, you're calling Elements() directly on the parent of the root element - so the only element it finds will be the root element itself.
If you want to do something for all elements in the document, you should use the Descendants() method.
Additionally, your query expression is pointless - you might as well just use xml.Elements() - and I really don't think you should be saving in a loop.
I think you just want:
foreach (var element in xml.Descendants())
{
element.Add(new XAttribute("Liczba_Potomkow", "dziesiec"));
}
xml.Save("Employees.xml");
How to get a value of XElement without getting child elements?
An example:
<?xml version="1.0" ?>
<someNode>
someValue
<child>1</child>
<child>2</child>
</someNode>
If i use XElement.Value for <someNode> I get "somevalue<child>1</child><child>2<child>" string but I want to get only "somevalue" without "<child>1</child><child>2<child>" substring.
You can do it slightly more simply than using Descendants - the Nodes method only returns the direct child nodes:
XElement element = XElement.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
Note that this will work even in the case where the child elements came before the text node, like this:
XElement element = XElement.Parse(
#"<someNode><child>1</child><child>2</child>some value</someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
There is no direct way. You'll have to iterate and select. For instance:
var doc = XDocument.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var textNodes = from node in doc.DescendantNodes()
where node is XText
select (XText)node;
foreach (var textNode in textNodes)
{
Console.WriteLine(textNode.Value);
}
I think what you want would be the first descendant node, so something like:
var value = XElement.Descendents.First().Value;
Where XElement is the element representing your <someNode> element.
You can specifically ask for the first text element (which is "somevalue"), so you could also do:
var value = XElement.Descendents.OfType<XText>().First().Value;