Adding child Xelement if it doesnt exist - c#

I have few solutions on how to do this, but I was wondering if there is a neat way to do this.
<Project>
<Test>
<Name value="zero">
<Name value="One">
<Name value="Two">
</Test>
</Project>
Now, I have access to testElement. I want to add new child XElement to it, only when it doesn't exist.
What I am currently doing is this. This is only sample code I am typing which is equivalent to what I am doing, so pardon me for minor mistakes.
XElement element = (from item
in testElement.Elements("name")
where item.Attribute("value") == "zero"
select item).SingleOrDefault();
if (element == null)
{
testElement.add(newElement);
}
Is there is a better way to do this? Maybe a simpler check?

you may perhaps use XPath extensions available via using System.Xml.XPath; to avoid lengthy LINQ, this is pretty simpler and easy
example
XElement element = testElement.XPathSelectElement("Name[#value='zero']");
if (element == null)
{
testElement.add(newElement);
}
XPath Name[#value='zero'] in the example above says that you are looking for an element named Name which has an attribute # named value and has the value of zero. so the linq in the question is reduced to on single XPathSelectElement and rest remains same.
optional, this is just a rewrite of above code
if (testElement.XPathSelectElement("Name[#value='zero']") == null)
{
testElement.add(newElement);
}

You are comparing an XAttribute to a string, so you probably are not getting the result you want.
item.Attribute("value") == "zero"
Try changing it to:
(string)item.Attribute("value") == "zero"
This converts the attribute to a string before the comparison.

Related

How to get text value from XDocument?

I'm having an XDocument.For example,
<cars>
<name>
<ford>model1</ford>
textvalue
<renault>model2</renault>
</name>
</cars>
How to get the text value from the XDocument? How to identify the textvalue among the elements?
Text values are interpreted by XLinq as XText. therefore you can easily check if a node is of type XText or by checking the NodeType see:
// get all text nodes
var textNodes = document.DescendantNodes()
.Where(x => x.NodeType == XmlNodeType.Text);
However, it strikes me that you only want to find that piece of text that seems a bit lonely named textvalue. There is no real way to recognize this valid but unusual thing. You can either check if the parent is named 'name' or if the textNode itself is alone or not see:
// get 'lost' textnodes
var lastTextNodes = document.DescendantNodes()
.Where(x => x.NodeType == XmlNodeType.Text)
.Where(x => x.Parent.Nodes().Count() > 1);
edit just one extra comment, i see that many people claim that this XML is invalid. I have to disagree with that. Although its not pretty, it's still valid according to my knowledge (and validators)
You can use the Nodes property to iterate over the child nodes of the document's root element. From there on, text nodes will be represented by XText instances, and their text value is available through their Value property:
string textValue = yourDoc.Root.Nodes.OfType<XText>().First().Value;
Assuming the variable "doc" contains the XDocument representing your XML above,
doc.XPathSelectElement("cars/name").Nodes().OfType<XText>()
This should give you all of the XText type text nodes that contain plain text that you are looking for.

parse XDocument for attributes

I have tried to parse an XML file like this:
<books>
<book>
<attr name="Moby Dick">Moby Dick is a classic</attr>
<attr isbn="isbnNumber">123456789</attr>
</book>
</books>
How can I get the value of "123456789"? I don't really need the first attribute.
I tried reading these in a foreach loop getting XElements but I keep getting a NULL object exception.
foreach(XElement xe in XDoc.Attribute("attr"))
{
string str = xe.Attribute("isbnNumber").Value // NULL EXCEPTION HERE
}
Thanks in advance...
You could try using the XPathSelectElement() extension method [you'll need to use System.Xml.XPath to get them].
E.g.
var isbn = xDoc.XPathSelectElement("//book/attr[#isbn='isbnNumber']").Value
PS. A good XPath tester is at: http://www.yetanotherchris.me/home/2010/6/7/online-xpath-tester.html
123456789 is actually the value of an element, not an attribute. What you want can be done like so:
XElement attr = xDoc.Descendants("attr")
.FirstOrDefault( x=>
(string)x.Attribute("isbn") == "isbnNumber"
);
string isbn = (string)attr;
You could even make it one line but this might be easier to read if you're new to LINQ to XML.
Well, I can't figure out how to respond to the individual answers..... but I implemented them both and they both work.
I am going with Reddog's answer as it is a little more straightforward and being new to LINQ it is the easiest as of now for readability.
Thanks for the responses!

Parse Delphi Project File on C#

This may seem like an odd question, but I have my own reasons for this! I am trying to parse a Delphi 2009 project file (.dproj), which is an XML representation of the project. I Can load the document into an XmlDocument, but when I try and get to the units that are used in the project, SelectNodes gives me an empty list.
An example of the project is below :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
...
<ItemGroup>
<DelphiCompile Include="Package.dpk">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="vcl.dcp"/>
<DCCReference Include="Unit1.pas"/>
<DCCReference Include="Unit2.pas"/>
<DCCReference Include="Unit3.pas"/>
<DCCReference Include="Unit4.pas"/>
<DCCReference Include="Unit5.pas"/>
...
</ItemGroup>
</Project>
An example of the code is below:
ProjectDocument.Load(FileName);
XmlNodeList nodeList;
XmlElement RootNode = ProjectDocument.DocumentElement;
string xmlns = RootNode.Attributes["xmlns"].Value;
// This gives an empty list
nodeList = RootNode.SelectNodes("/Project/ItemGroup/DCCReference");
foreach (XmlNode title in nodeList)
{
Console.WriteLine(title.InnerXml);
}
// This also gives an empty list
nodeList = RootNode.SelectNodes("/ItemGroup/DCCReference");
foreach (XmlNode title in nodeList)
{
Console.WriteLine(title.InnerXml);
}
The question is really, what am I doing wrong, as I must be missing something. The only odd thing is that the document is not a .xml, it is a .dproj.
So, thanks in advance if you can solve this.
Mark
Quoting the documentation:
Remarks
If the XPath expression requires namespace resolution, you must use the SelectNodes overload which takes an XmlNamespaceManager as its argument. The XmlNamespaceManager is used to resolve namespaces.
Note If the XPath expression does not include a prefix, it is assumed that the namespace URI is the empty namespace. If your XML includes a default namespace, you must still use the XmlNamespaceManager and add a prefix and namespace URI to it; otherwise, you will not get any nodes selected.
You need the two-argument version of SelectNodes. Also, note that the nodes you're selecting have no contents, so the InnerXML property will be empty. Once you get a non-empty list, your code will still print a list of empty lines.
You need to use a XmlNamespaceManager:
var nsmgr = new XmlNamespaceManager(ProjectDocument.NameTable);
nsmgr.AddNamespace("x", ProjectDocument.DocumentElement.NamespaceURI);
var nodes = doc.SelectNodes("descendant::x:DCCReference", nsmgr);
Also, why aren't you parsing the DPR file with your own simple parser? Find the uses
line, then grab every line after that, strip the comments, and the final comma, until you hit a line with a semicolon.
Benefit #2 besides simpler parsing is this works with EVERY delphi version, whereas DPROJ and equivalent files are famously different from delphi version to version.

How to determine if XElement.Elements() contains a node with a specific name?

For example for the following XML
<Order>
<Phone>1254</Phone>
<City>City1</City>
<State>State</State>
</Order>
I might want to find out whether the XElement contains "City" Node or not.
Just use the other overload for Elements.
bool hasCity = OrderXml.Elements("City").Any();
It's been a while since I did XLinq, but here goes my WAG:
from x in XDocument
where x.Elements("City").Count > 0
select x
;
David's is the best but if you want you can write your own predicate if you need some custom logic OrderXML.Elements("City").Exists(x=>x.Name =="City")

linq to xml - get rid of blank xmlns

I'm trying to get rid of empty namespace tags in my xml file. All of the solutions i've seen are based creating the xml from scratch. I have various xelements constructed from a previous xml. All I'm doing is
XElement InputNodes = XElement.Parse(InputXML);
m_Command = InputNodes.Element("Command");
and it adding the xmlns = "" everywhere. This is really infuriating. Thanks for any help.
There's a post on MSDN blogs that shows how to get around this (reasonably) easily. Before outputing the XML, you'll want to execute this code:
foreach (XElement e in root.DescendantsAndSelf())
{
if (e.Name.Namespace == string.Empty)
{
e.Name = ns + e.Name.LocalName;
}
}
The alternative, as the poster mentions, is prefixing every element name with the namespace as you add it, but this seems like a nicer solution in that it's more automated and saves a bit of typing.
Possibly it's this: Empty namespace using Linq Xml
This would indicate your document is in a different default namespace than the elements you add.
I think the second answer down on this post:
XElement Add function adds xmlns="" to the XElement
was very useful. Basically if you just do
XNamespace rootNamespace = doc.Root.Name.NamespaceName;
XElement referenceElement = new XElement(rootNamespace + "Reference");
That should solve it. So I guess you have to tell it not to worry about a special namespace when you are creating the element. Odd.

Categories

Resources