Update XML file from List<T> - c#

I have a XML file containing records like -
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCLocation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CLocation>
<CId>5726</CId>
<Long>0</Long>
<Lat>0</Lat>
<Status>Pending</Status>
</CLocation>
<CLocation>
<CId>5736</CId>
<Long>0</Long>
<Lat>0</Lat>
<Status>Processed</Status>
</CLocation>
</ArrayOfCLocation>
I take these records into List as -
XDocument xDocument = XDocument.Load(filePath);
List<T> list = xDocument.Descendants("CLocation")
.Select(c => (new T()
{
CId = Convert.ToInt32(c.Descendants("CId").FirstOrDefault().Value),
Lat = Convert.ToDouble(c.Descendants("Lat").FirstOrDefault().Value),
Long = Convert.ToDouble(c.Descendants("Long").FirstOrDefault().Value),
Status = (Status)Enum.Parse(typeof(Status), c.Descendants("Status").FirstOrDefault().Value)
}))
.Where(c => c.Status == Status.Pending)
.Take(listCount)
.ToList();
Now, I update T objects(setting their Lat/Log fields) in above collection
and after processing these objects, I want to update these records back into XML file.
Can anyone please guide me for a efficient solution for how can I update these objects back into XML file.

You could do something like this:
foreach (var location in list)
{
var elem = xDocument.Root.Elements()
.Single(e => (int)e.Element("CId") == location.CId);
elem.Element("Long").ReplaceNodes(location.Long);
elem.Element("Lat").ReplaceNodes(location.Lat);
}
You can then save the modified xDocument back to a file, or whatever.
If you find this is not efficient enough, there are several ways to speed things up. For example create a Dictionary of elements by CId, so that the whole document is not searched every time.
But if you have huge files, loading them whole into memory might not be possible or a good idea. Using XmlReader and XmlWriter will work for files of any size, but they are not as easy to use.
Another option to consider is XML serialization. It's made specifically for converting XML into your objects and back.
Also, the code you have could be simplified quite a lot, and in the process made faster:
xDocument.Root.Elements("CLocation")
.Select(c => new Location
{
CId = (int)c.Element("CId"),
Lat = (double)c.Element("Lat"),
Long = (double)c.Element("Long"),
Status = (Status)Enum.Parse(typeof(Status), c.Element("Status").Value)
})

Related

Is my usage of LINQ to XML correct when I'm trying to find multiple values in the same XML file?

I have to extract values belonging to certain elements in an XML file and this is what I ended up with.
XDocument doc = XDocument.Load("request.xml");
var year = (string)doc.Descendants("year").FirstOrDefault();
var id = (string)doc.Descendants("id").FirstOrDefault();
I'm guessing that for each statement I'm iterating through the entire file looking for the first occurrence of the element called year/id. Is this the correct way to do this? It seems like there has to be a way where one would avoid unnecessary iterations. I know what I'm looking for and I know that the elements are going to be there even if the values may be null.
I'm thinking in the lines of a select statement with both "year" and "id" as conditions.
For clearance, I'm looking for certain elements and their respective values. There'll most likely be multiple occurrences of the same element but FirstOrDefault() is fine for that.
Further clarification:
As requested by the legend Jon Skeet, I'll try to clarify further. The XML document contains fields such as <year>2015</year> and <id>123032</id> and I need the values. I know which elements I'm looking for, and that they're going to be there. In the sample XML below, I would like to get 2015, The Emperor, something and 30.
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<make>Apple</make>
<year>2015</year>
<customer>
<name>The Emperor</name>
<level2>
<information>something</information>
</level2>
<age>30</age>
</customer>
A code that doesn't parse the whole xml twice would be like:
XDocument doc = XDocument.Load("request.xml");
string year = null;
string id = null;
bool yearFound = false, idFound = false;
foreach (XElement ele in doc.Descendants())
{
if (!yearFound && ele.Name == "year")
{
year = (string)ele;
yearFound = true;
}
else if (!idFound && ele.Name == "id")
{
id = (string)ele;
idFound = true;
}
if (yearFound && idFound)
{
break;
}
}
As you can see you are trading lines of code for speed :-) I do feel the code is still quite readable.
if you really need to optimize up to the last line of code, you could put the names of the elements in two variables (because otherwise there will be many temporary XName creation)
// before the foreach
XName yearName = "year";
XName idName = "id";
and then
if (!yearFound && ele.Name == yearName)
...
if (!idFound && ele.Name == idName)

Parsing xml string to get certain tag values within

I have an xml string and have different records within and i want to extract the id within each record. Here is a sample of the xml:
<UploadsInformation >
<Record>
<TaskGUID>48A583CA-A532-419A-9CDB-292764CEC541</TaskGUID>
</Record>
<Record>
<TaskGUID>ED6BA682-2BB2-4ADF-8355-9C605E16E088</TaskGUID>
</Record>
<Record>
<TaskGUID>D20D7042-FC5B-4CF7-9496-D2D9DB68CF52</TaskGUID>
</Record>
<Record>
<TaskGUID>F5DB10C5-D517-4CDA-8AAA-4E3F50B5FF3C</TaskGUID>
</Record>
</UploadsInformation>
This is what i have as a string to extract the information that i need but not sure if it correct or not because when i debug the string seems to be the xml file and not just the specified guid.
string data = new XDocument(new XElement("Record",
uploads.Select(guid => new XElement("TaskGUID", guid.ToString()))))
.ToString();
uploads is: List<Guid?> uploads
If I understand your question correctly, you want to extract the Guids from the source XML, which you indicate is a string.
You can create an XDocument from a string with the following command:
XDocument doc = XDocument.Parse(xmlString);
XNamespace ns = "http://schemas.acatar.com/2013/03/Malt.Models";
List<string> uploads = doc.Descendants(ns + "TaskGUID")
.Select(x => x.Value).ToList();
string uploadString = String.Join(",", uploads);
I used XNamespace because there is a namespace (two, actually) defined in the XML, and unless you prefix the correct one to the element name you won't get any results.
You might be able to combine the last two steps into one line, but I'm not 100% sure.
The above code was tested with your example, and produces the following value for uploadString:
48A583CA-A532-419A-9CDB-292764CEC541,ED6BA682-2BB2-4ADF-8355-9C605E16E088,D20D7042-FC5B-4CF7-9496-D2D9DB68CF52,F5DB10C5-D517-4CDA-8AAA-4E3F50B5FF3C
However, if you're going to loop through the result and pass each one in singularly to a stored procedure, I'd skip the String.Join and just loop through the List:
foreach (string id in uploads)
{
// Do your stored procedure call for each Guid.
}
Added in Response to Comment
In the situation in your comment, if you have a List that you want to get the values for, you'd do essentially the same, but you'll need to check for nulls and (probably) convert the Guid to a string before passing it into the stored proc:
foreach (Guid? g in uploads)
{
if (g != null)
{
string newGuid = g.ToString();
// do your data access stuff here
}
}
You can't use local names of elements, because you have namespace declared. So, you should use namespace to provide names:
XNamespace ns = "http://schemas.acatar.com/2013/03/Malt.Models";
var guids = from r in xdoc.Root.Elements(ns + "Record")
select Guid.Parse((string)r.Element(ns + "TaskGUID"));
Or query your xml without specifying names of elements:
var guids = xdoc.Root.Elements()
.Select(r => Guid.Parse((string)r.Elements().Single()));
I think this is either what you are after or perhaps might shed some light on the direction to go:
string xml = ""; // XML data here
XDocument doc = XDocument.Parse(xml);
List<Guid> guids = doc.Descendants("TaskGUID")
.Select(g => new Guid(g.Value))
.ToList();

Parsing XML file using C#?

I'm new to both XML and C#; I'm trying to find a way to efficiently parse a given xml file to retrieve relevant numerical values, base on the "proj_title" value=heat_run or any other possible values. For example, calculating the duration of a particular test run (proj_end val-proj_start val).
ex.xml:
<proj ID="2">
<proj_title>heat_run</proj_title>
<proj_start>100</proj_start>
<proj_end>200</proj_end>
</proj>
...
We can't search by proj ID since this value is not fixed from test run to test run. The above file is huge: ~8mb, and there's ~2000 tags w/ the name proj_title. is there an efficient way to first find all tag names w/ proj_title="heat_run", then to retrieve the proj start and end value for this particular proj_title using C#??
Here's my current C# code:
public class parser
{
public static void Main()
{
XmlDocument xmlDoc= new XmlDocument();
xmlDoc.Load("ex.xml");
//~2000 tags w/ proj_title
//any more efficient way to just look for proj_title="heat_run" specifically?
XmlNodeList heat_run_nodes=xmlDoc.GetElementsByTagName("proj_title");
}
}
8MB really isn't very large at all by modern standards. Personally I'd use LINQ to XML:
XDocument doc = XDocument.Load("ex.xml");
var projects = doc.Descendants("proj_title")
.Where(x => (string) x == "heat_run")
.Select(x => x.Parent) // Just for simplicity
.Select(x => new {
Start = (int) x.Element("proj_start"),
End = (int) x.Element("proj_end")
});
foreach (var project in projects)
{
Console.WriteLine("Start: {0}; End: {1}", project.Start, project.End);
}
(Obviously adjust this to your own requirements - it's not really clear what you need to do based on the question.)
Alternative query:
var projects = doc.Descendants("proj")
.Where(x => (string) x.Element("proj_title") == "heat_run")
.Select(x => new {
Start = (int) x.Element("proj_start"),
End = (int) x.Element("proj_end")
});
You can use XPath to find all nodes that match, for example:
XmlNodeList matches = xmlDoc.SelectNodes("proj[proj_title='heat_run']")
matches will contain all proj nodes that match the critera. Learn more about XPath: http://www.w3schools.com/xsl/xpath_syntax.asp
MSDN Documentation on SelectNodes
Use XDocument and use the LINQ api.
http://msdn.microsoft.com/en-us/library/bb387098.aspx
If the performance is not what you expect after trying it, you have to look for a sax parser.
A Sax parser will not load the whole document in memory and try to apply an xpath expression on everything in memory. It works more in an event driven approach and in some cases this can be a lot faster and does not use as much memory.
There are probably sax parsers for .NET around there, haven't used them myself for .NET but I did for C++.

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

Converting one XML document into another XML document

I want to convert an XML document containing many elements within a node (around 150) into another XML document with a slightly different schema but mostly with the same element names. Now do I have to manually map each element/node between the 2 documents. For that I will have to hardcode 150 lines of mapping and element names. Something like this:
XElement newOrder = new XElement("Order");
newOrder.Add(new XElement("OrderId", (string)oldOrder.Element("OrderId")),
newOrder.Add(new XElement("OrderName", (string)oldOrder.Element("OrderName")),
...............
...............
...............and so on
The newOrder document may contain additional nodes which will be set to null if nothing is found for them in the oldOrder. So do I have any other choice than to hardcode 150 element names like orderId, orderName and so on... Or is there some better more maintainable way?
Use an XSLT transform instead. You can use the built-in .NET XslCompiledTransform to do the transformation. Saves you from having to type out stacks of code. If you don't already know XSL/XSLT, then learning it is something that'll bank you CV :)
Good luck!
Use an XSLT transformation to translate your old xml document into the new format.
XElement.Add has an overload that takes object[].
List<string> elementNames = GetElementNames();
newOrder.Add(
elementNames
.Select(name => GetElement(name, oldOrder))
.Where(element => element != null)
.ToArray()
);
//
public XElement GetElement(string name, XElement source)
{
XElement result = null;
XElement original = source.Elements(name).FirstOrDefault();
if (original != null)
{
result = new XElement(name, (string)original)
}
return result;
}

Categories