Linq updating XML-document - c#

I'm just creating an app that stores the settings in a xml file.
If I now want to save a new parameter, I need to check if that parameter already exists and update it or add it to the document.
Actually I solve it this way:
XDocument xDocument = XDocument.Load(appDataFolder + #"\settings.xml");
foreach(XElement xElement in xDocument.Descendants("Settings"))
{
if(xElement.Element("projectFile") != null)
xElement.Element("projectFile").Value = projectFile;
else
xElement.Add(new XElement("projectFolder", projectFile));
if (xElement.Element("projectFolder") != null)
xElement.Element("projectFolder").Value = projectFolder;
else
xElement.Add(new XElement("projectFolder", Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments)));
}
xDocument.Save(appDataFolder + #"\settings.xml");
This is the actual settings.xml:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--System-Settings-->
<Settings version="1.0.0.0">
<projectFile>C:\Users\Public\Documents\project.prj</projectFile>
<projectFolder>C:\Users\Public\Documents</projectFolder>
</Settings>
Isn't there a more handy way that automatically adds an element, if it doesn't exist?

System.Xml.Linq.XElement actually has a method that does exactly that, namely SetElementValue.
It takes an XName and the desired content, adding or updating according to your intuition.
Both
var xml = XElement.Parse("<outer></outer>");
xml.SetElementValue("inner", 2);
and
var xml = XElement.Parse("<outer><inner>1</inner></outer>");
xml.SetElementValue("inner", 2);
result in
<outer><inner>2</inner></outer>
As we can see from the remarks section, it was designed with your use case in mind.
This method is designed to make it easy to maintain a list of
name/value pairs as a set of children elements. When maintaining the
list, you need to add pairs, modify pairs, or delete pairs. If you
call this method passing a name that does not exist as a child
element, this method creates a child element for you. If you call this
method passing the name of an existing child element, this method
modifies the value of the child element to the value that you specify.
If you pass null for value, this method removes the child element.

Related

XPath replace value of attribute with dynamic name in root element in C#

I am looking for a way, how to replace attribute value in XML node, where the issue with an attribute is, that its name is generated dynamically.
As an example, I can have two different XML files containing something like this
fileA.xaml
<root xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:p="clr-namespace:FullAssemblyIdentifier.Wrong_Namespace;assembly=AssemblyName">
</root>
fileB.xaml
<root xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:p2="clr-namespace:FullAssemblyIdentifier.Wrong_Namespace;assembly=AssemblyName">
</root>
As you can see, there is actually a difference in name. xmlns:p vs xmlns:p2. The thing is, that it can be called anyhow. It can even be xmlns:ABC. What I am trying to do is to create some kind of ReferenceChanger class, that will fix the reference in all XAML files at once. So far I was thinking about something like this:
ReferenceChanger.cs
public void ChangeReference(XmlNode xmlDoc)
{
const string newReference = "FullAssemblyIdentifier.CorrectReference";
// this does not select the element
var element = (XmlElement)xmlDoc.SelectSingleNode("//root[contains(.,'FullAssemblyIdentifier.Wrong_Namespace')]");
// How do i get the name of the attribute ???
string dynamicallyGeneratedAttributeName = element.GetDynamicallyGeneratedAttributeIdentifier();
if (element != null)
{
// this I can not use, because I do not know the attribute name
element.SetAttribute(dynamicallyGeneratedAttributeName, newReference);
}
}
One thing, that is certain is, that this wrong value is (and always will be) only in root element and not in any other element.

GetElementByTagName returns System.Xml.XmlElementList

I have an XML document that looks like this
<?xml version="1.0" encoding="utf-8" ?>
<event>
<name>Test Event</name>
<date>07/09/1997</date>
<description>Birthday</description>
<blogURL></blogURL>
</event>
I want to grab these fields and display them in ASP:Labels
This is my code behind
protected void Page_Load(object sender, EventArgs e)
{
XmlDocument pressRelease = new XmlDocument();
pressRelease.Load(Server.MapPath("~/PressSection.xml"));
XmlNodeList name = pressRelease.GetElementsByTagName("name");
CurrentEventName.Text = name.ToString();
}
But this is what it displays in the label
System.Xml.XmlElementList
Not really sure what I'm doing wrong.
As the name might suggest and, as the documentation tells you, the method returns:
An XmlNodeList containing a list of all matching nodes. If no nodes match name, the returned collection will be empty.
You need to iterate that list, or simply take the first item if you're sure one will always be there:
var names = pressRelease.GetElementsByTagName("name");
CurrentEventName.Text = names[0].Value;
That said, LINQ to XML is a far nicer API, I would definitely encourage you to learn more about it:
var doc = XDocument.Load(Server.MapPath("~/PressSection.xml"));
CurrentEventName.Text = (string)doc.Descendants("name").Single();
try this way
XDocument doc = XDocument.Load(Server.MapPath("~/PressSection.xml"));
var query = doc.Descendants("event").Elements("name").FirstOrDefault();
Console.WriteLine(query.Value);
This is actually the intended behavior.
The reason for it is that it returns a list of all elements that match your criteria. If you know for sure that you'll always want the first element, you could always get the first element by:
name[0].ToString()
However, you might also want to add some null and empty checking for the XmlElementList as it may also be empty, which will result in you getting a null pointer exception if you try to get an item from it.

Duplicate values in Xml database

I need to insert only unique values in my XML database based on the "userName" value. For example there can't be two players which has same name in the database. The implementation of my xml database which is named as "MyXML.xml" is like that :
<?xml version="1.0" encoding="utf-8"?>
<Players>
<player name="cag" score="5555" />
<player name="cihan" score="1222" />
<player name="can" score="333" />
</Players>
and my related code in order to detetect duplication is:
public bool insertUserDetails(string userName,float userScore){
XDocument doc = Document.Load(HttpContext.Current.Server.MapPath("MyXML.xml"));
var duplicate = doc.Element("Players").Elements("player").Where(x =>(string)x.Value == userName).SingleOrDefault();
if (duplicate != null){
return false;
}
else{
return true;
}
}
when I try to insert a duplicate value for example "can" and "333", var duplicate value turns out to be null. How can I fix this problem ?
"can" is defined as a attribute in your XML; It is not a value. Value is something which comes in between open and closing tags of xml.
For example, if you have a xml element like this
<SomeTag name="somename">Hello world</SomeTag>
Then, SomeTag is the element name, somename is the attribute value of attribute "name" and "Hello world" is the value of the xml element itself.
So, You need to find the attribute using Attribute method to access its value.
var duplicate = doc.Element("Players")
.Elements("player")
.Where(x=>x.Attribute("name").Value == userName)
.SingleOrDefault();
You're iterating over the Value of each player element, rather than the Attributes of those elements. Since there's no text inside the player element, the value is always an empty string, and thus never matched the name you're looking for. Instead, select the .GetAttribute("name") for each element
var duplicate = doc
.Element("Players")
.Elements("player")
.Select(ele => ele.GetAttribute("name"))
.Where(att =>(string)att.Value == userName)
.SingleOrDefault();
you're comparing the value of an XElement, "x", to userName.
you should compare the "name" attribute of x to it.
var duplicate = doc.Element("Players").Elements("player").Where(x => (string)x.Attribute("name").Value == userName).SingleOrDefault();

I need some help specifying the sort key to use in my XPathExpression AddSort() method call

I am having trouble in my C# .NET app sorting the nodes of an XML document based on a time in each node. I think my problem is that I don’t know how to properly construct the XPathExpression that specifies the item on which sorting is to be done.
The XML contains information about batch jobs that have run, including the name of each job, its running status, the time it started, and the time it ended.
My XML document is in this format:
<results>
<result>
(Info on one job)
</result>
</results>
Here is an example of a result node for a single job:
<result offset='14'>
<field k='job_name'>
<value h='1'><text>Y_RUNNING_3</text></value>
</field>
<field k='job_did_start'>
<value><text>08-01-2012 04:00:00</text></value>
</field>
</result>
I’ve omitted values for several irrelevant fields. Each field is named “field” and has the name of the field specified using the attribute “k”. (I didn’t design the XML format.)
In my code I am successfully using XPathDocument and XPathNavigator to load the XML document without sorting it. I need, however, to sort the nodes on the value of the field with the “k” attribute set to ‘job_did_start’.
Here is part of the code:
XPathDocument doc = new XPathDocument(strFilepath);
XPathNavigator nav = doc.CreateNavigator();
XPathExpression selectExpression = nav.Compile("/results/result"); // QQQ not sure about this
// XPathExpression sortExpression = nav.Compile("//#k='job_did_start'");
// selectExpression.AddSort(sortExpression, new DateTimeComparer());
foreach (XPathNavigator item in nav.Select(selectExpression))
{
item.MoveToFirstChild();
do
{
**I’ve omitted the code here because it works and probably is not relevant.**
} while (item.MoveToNext());
}
I’ve commented out lines that set sortExpression and that add that sortExpression to the selectExpression because they don’t work.
I THINK all I need to do is to figure out the value for the string passed to nav.Compile() in the statement that sets sortExpression.
By the way, here is the DateTimeComparer, which I borrowed from somewhere else on the net. I set a breakpoint inside the Compare() method to confirm that objects x and y are not strings representing a date, but that’s what they need to be.
public class DateTimeComparer : IComparer
{
public int Compare(object x, object y)
{
DateTime dt1 = DateTime.Parse(x.ToString());
DateTime dt2 = DateTime.Parse(y.ToString());
return dt1.CompareTo(dt2);
}
}
Since the ‘job_did_start’ value can be an empty string, I’ll probably need to account for that possibility in the Compare() method.
So can someone help me set the sort key properly?
Thank you.
I figured it out. The following works:
XPathExpression sortExpression = nav.Compile("./field[#k='job_did_start']");

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

Categories

Resources