Xml updating with linq not working when quering - c#

I have problem i'm trying to update a specific part of the XML with the linq query but it doesn't work. So i an xml file:
<?xml version="1.0" encoding="utf-8"?>
<DesignConfiguration>
<Design name="CSF_Packages">
<SourceFolder>C:\CSF_Packages</SourceFolder>
<DestinationFolder>C:\Documents and Settings\xxx</DestinationFolder>
<CopyLookups>True</CopyLookups>
<CopyImages>False</CopyImages>
<ImageSourceFolder>None</ImageSourceFolder>
<ImageDesinationFolder>None</ImageDesinationFolder>
</Design>
</DesignConfiguration>
I want to select the part where the part where there is Design name="somethning" and get the descendants and then update the descendants value that means this part:
<SourceFolder>C:\CSF_Packages</SourceFolder>
<DestinationFolder>C:\Documents and Settings\xxx</DestinationFolder>
<CopyLookups>True</CopyLookups>
<CopyImages>False</CopyImages>
<ImageSourceFolder>None</ImageSourceFolder>
<ImageDesinationFolder>None</ImageDesinationFolder>
I have this code:
XDocument configXml = XDocument.Load(configXMLFileName);
var updateData = configXml.Descendants("DesignConfiguration").Elements().Where(el => el.Name == "Design" &&
el.Attribute("name").Value.Equals("CSF_Packages")).FirstOrDefault();
configXml.Save(configXMLFileName);
I'm getting the null data in the updateData varibale. When I'm trying the Descendat's function through QuickWatch it also returns a null value. When I'm checking the configXML variable it has data that is my whole xml. What am I doing wrong?

Try this:
var updateData =
confixXml
.Root //Root Element
.Elements("Design") //All elements under root called Design
.Where(element => (String)element.Attribute("name") == "AFP_GRAFIKA") //Find the one with the name Attribute of AFP_GRAFIKA
.FirstOrDefault(); //Grab the first one it finds or return null.
if (updateData != null)
{
var myElements =
updateData
.Elements(); //All the elements under the Design node
}

XDocument xml = XDocument.Load("");
XElement settings = (from children in xml.Descendants("DesignConfiguration")
where children.Name.Equals("Design") && children.Attribute("name").Equals("CSF_Packages")
select children).FirstOrDefault();
settings.Element("SourceFolder").SetValue("filepath");
settings.Element("CopyImages").SetValue(true);

Ok, so I've managed to fix the problem. I don't know why but it worked. It seems that the Descendants function returns null as a stand alone function but with linq it works. So for my solution only thing what should be done is this:
var updateData = (from s in configXml.Descendants("Design")
where s.Attribute("name").Value == design.DesignName
select s).First();
At first before I sent you my question I've tried this but I didn't have the select s part. Besides when I wrote the where s.Atribute part in the curly brackets I've inserted the design.DesignName object instead of the name of the attribute. So no it works ok. Thanks for your help and everything. Til nex time. Have a nice day/night everyone :)

Because DesignConfiguration was your root node, the Descendants("DesignConfiguration) was returning null. By using the .Descendants("Design"), you were looking at child nodes, not the self.

Related

When using C# XmlSerializer and SetElementValue on an XElement when you set it the value to null or empty string it's removed

If you open an XML Document with XDocument.Load(path) and then look through Descendants when you find the one you are looking for and use SetElementValue if you set the value to an empty string ("") or null it ends up removing the tag so when you save the document it's lost.
I want to be able to keep the tag when the value is null or an empty string. I've not been able to find a way to do this.
Is my only option to deserialize the entire XML document into objects edit those objects and write over the file rather than just loading the XmlDocument and editing it?
Sorry, it took me a while to get back to this. I found what my issue was so the code was all correct. What I hadn't noticed before was that this line was at the end before the save.
xDocument.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
This goes through all the descendants and finds any that are null or empty strings and removes them which was my problem.
XElement.SetElementValue(elementName, elementValue);
This does exactly as documented. When the elementValue is NULL it will remove the element but when it's an empty string it will put leave the element as an empty element in long-form, not the short form which is fine for my case.
For completeness of this answer and since those asked for example code here is some.
Sample.cfg
<?xml version="1.0" encoding="utf-8"?>
<ParentNode>
<ChildNode>
<PropertyOne>1</PropertyOne>
<PropertyTwo>Y</PropertyTwo>
</ChildNode>
<ChildNode>
<PropertyOne>2</PropertyOne>
<PropertyTwo>N</PropertyTwo>
</ChildNode>
</ParentNode>
Sample Code
// See https://aka.ms/new-console-template for more information
using System.Xml.Linq;
var xDocument = XDocument.Load("Sample.cfg");
foreach (var childNode in xDocument.Descendants("ChildNode"))
{
foreach (var element in childNode.Elements())
{
if (element.Name == "PropertyOne" && element.Value == "2")
{
childNode.SetElementValue("PropertyTwo", "");
}
// Uncomment this line to always have it remove null and empty string descendants
//xDocument.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();
xDocument.Save("Sample.cfg");
}
}

Creating XML nodes dynamically to fetch its attribute values

I'm working on this using C# .net VS 2013.
I have a scenario where I'm having the structure as below,
<td>
<text text="abc">abc
<tspan text = "bcd">bcd
<tspan text = "def">def
<tspan text = "gef">gef
</tspan>
</tspan>
</tspan>
</text>
</td>
As shown above, I don't know how many tspan nodes will be there, currently I have 3, I may get 4 or more than that.
Once after finding the text node, to get the value of that node I'll use the code,
labelNode.Attributes["text"].Value
to get its adjacent tspan node, I have to use it like
labelNode.FirstChild.Attributes["text"].Value
to get its adjacent tspan node, I have to use it like
labelNode.FirstChild.FirstChild.Attributes["text"].Value
Like this it will keep on going.
Now my question is, if I know that i have 5 tags, is there any way to dynamically add "FirstChild" 5 times to "labelNode" so that I can get the text value of the last node, like this
labelNode.FirstChild.FirstChild.FirstChild.FirstChild.FirstChild.Attributes["text"].Value
If I need 2nd value i need to add it 2 times, if I need 3rd then I need to add it thrice.
Please let me know is there any solution for this.
Please ask me, if you got confused with my question.
Thanking you all in advance.
Rather than adding FirstChild dynamically, I think this would be a simpler solution:
static XmlNode GetFirstChildNested(XmlNode node, int level) {
XmlNode ret = node;
while (level > 0 && ret != null) {
ret = ret.FirstChild;
level--;
}
return ret;
}
Then you could use this function like this:
var firstChild5 = GetFirstChildNested(labelNode, 5);
I would suggesting using Linq to Xml which has cleaner way parsing Xml
Using XElement (or XDocument) you could flatten the hierarchy by calling Descendant method and do all required queries.
ex..
XElement doc= XElement.Load(filepath);
var results =doc.Descendants()
.Select(x=>(string)x.Attribute("text"));
//which returns
abc,
bcd,
def,
gef
If you want to get the last child you could simply use.
ex..
XElement doc= XElement.Load(filepath);
doc.Descendants()
.Last() // get last element in hierarchy.
.Attribute("text").Value
If you want to get third element, you could do something like this.
XElement doc= XElement.Load(filepath);
doc.Descendants()
.Skip(2) // Skip first two.
.First()
.Attribute("text").Value ;
Check this Demo

Checking the name and value of an XML node, using LINQ

I have a C# application where I need to parse XML using LINQ. This is my first LINQ experience that’s why I am struggling with basic operations
My XML looks something similar to:
<Main>
<Data>
<NodeTypeA>
<ElementA>23</ElementA>
<ElementB>24</ElementB>
</NodeTypeA>
</Data>
</Main>
So first I want to check the name of the first child of “Data”. In this case it is “NodeTypeA”.
Second I want to read the value of ElementA value. In this example it is “23”
You can do the following:
var firstElement = xml.Descendants("Data").Elements().FirstOrDefault();
if (firstElement != null && firstElement.Name == "NodeTypeA")
{
var elementAValue = (string)firstElement.Element("ElementA");
}

How to delete certain root from xml file?

My '.xml' file looks this way:
<?xml version="1.0" encoding="utf-8"?>
<Requestes>
<Single_Request num="1">
<numRequest>1</numRequest>
<IDWork>1</IDWork>
<NumObject>1</NumObject>
<lvlPriority>Высокий</lvlPriority>
</Single_Request>
<Single_Request num="2">
<numRequest>2</numRequest>
<IDWork>2</IDWork>
<NumObject>2</NumObject>
<lvlPriority>Средний</lvlPriority>
</Single_Request>
<Periodic_Request num="1">
<numRequest>3</numRequest>
<IDWork>23</IDWork>
<pFrequency>23</pFrequency>
<lvlPriority>Низкий</lvlPriority>
<time_service>23</time_service>
<time_last_service>23</time_last_service>
<relative_time>23</relative_time>
</Periodic_Request>
</Requestes>
So I need to delete Single_Request with atribute value equal to sTxtBlock_numRequest.Text. I have tried to do it this way:
XDocument doc = XDocument.Load(FilePath);
IEnumerable<XElement> sRequest = doc.Root.Descendants("Single_Request").Where(
t => t.Attribute("num").Value =="sTxtBlock_numRequest.Text"); //I'm sure, that problem is here
sRequest.Remove();
doc.Save(FilePath);
Unfortunattly, nothing has happanned, don`t know how to solve the problem.
This is why , I am looking forward to your help.
You are comparing attribute value with string literal "sTxtBlock_numRequest.Text". You should pass value of textbox text instead:
doc.Root.Elements("Single_Request")
.Where(t => (string)t.Attribute("num") == sTxtBlock_numRequest.Text)
.Remove();
Note - it's better to use Elements when you are getting Single_Request elements of root, because Descendants will search whole tree, instead of looking at direct children only. Also you can call Remove() without saving query to local variable.

Finding element in XDocument?

I have a simple XML
<AllBands>
<Band>
<Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
<Last>1</Last>
<Salary>2</Salary>
</Band>
<Band>
<Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
<Last>1</Last>
<Salary>2</Salary>
</Band>
</AllBands>
However ,
when I want to reach the "Doors band" and to change its ID :
using (var stream = new StringReader(result))
{
XDocument xmlFile = XDocument.Load(stream);
var query = from c in xmlFile.Elements("Band")
select c;
...
query has no results
But
If I write xmlFile.Elements().Elements("Band") so it Does find it.
What is the problem ?
Is the full path from the Root needed ?
And if so , Why did it work without specify AllBands ?
Does the XDocument Navigation require me to know the full level structure down to the required element ?
Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:
var query = from c in xmlFile.Descendants("Band") select c;
Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:
<Band>
<BandProperties Name ="Doors" ID="222" started="1968" />
<Description>regular Band<![CDATA[lalala]]></Description>
<Last>1</Last>
<Salary>2</Salary>
</Band>
You can do it this way:
xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
You should use Root to refer to the root element:
xmlFile.Root.Elements("Band")
If you want to find elements anywhere in the document use Descendants instead:
xmlFile.Descendants("Band")
The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:
var query = from c in xmlFile.Descendants("Band")
My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).
In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral
In short, you can create a GetElement function:
private XElement GetElement(XDocument doc,string elementName)
{
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
return element;
}
}
return null;
}
Which you can then call like this:
XElement element = GetElement(doc,"Band");
Note that this will return null if no matching element is found.
The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:
var query = from c in xmlFile.Root.Elements("Band")
select c;
Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...
private static List<XElement> GetElements(XDocument doc, string elementName)
{
List<XElement> elements = new List<XElement>();
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
elements.Add(element);
}
}
return elements;
}
Call it thus:
var elements = GetElements(xamlFile, "Band");
or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:
var elements = GetElements(xamlFile, "TextBlock");

Categories