Auto load child items from immediate parent based on XML attribute - c#

I have an XML file which looks like...
<Modules>
<Module Title='Mod1' Attr2='' Attr3=''>
<Functions>
<Function Title='Fun1' Attr2=''>
<SubFunctions>
<SubFunction Title='SubFun1' Attr2='' />
<SubFunction Title='SubFun2' Attr2='' />
</SubFunctions>
</Function>
<Function Title='Fun2' Attr2='' />
<Function Title='Fun3' Attr2='' />
</Functions>
</Module>
<Module Title='Mod2' Attr2='' Attr3=''>
<Functions>
</Functions>
</Module>
<Module Title='Mod3' Attr2='' Attr3=''>
<Functions>
</Functions>
</Module>
I have been trying to write a generic function based on recursive in nature using LINQ and XML in C# but have not able to get hold on making the function as generic as possible.
What I have currently is my functions loads all the data related "Module", hence the count is 3 as per above XML data. When the user selects 'Mod1' the very first "Module", it should load all respective data which is under this 'Mod1' i.e. Fun1, Fun2, Fun3. Again if the user selects Fun1, it should load all the data under this 'Fun1' i.e. SubFun1, SubFun2 etc.
PLEASE NOTE: I don't want to hardcode any XML tags in my C# application.
Thanks in advance.

If you don't want to hardcode the names of the tags, we should make an assumption about the XML structure. According to your example it seems that there are two kinds of elements:
"Container" element. Modules, Functions, SubFunctions. Which can contains zero or more "Data" element
"Data" element. Module, Function, SubFunction. Which can contain zero or one "Container" element and always has a "Title" attribute which is used for selection.
Note that "Title" attribute name is hardcoded unless you use customized name. Your decisions.
The code can be generic, as we don't care about the names of the Elements, but only their relation.
As you described, user chooses the selected element gradually, following this requirement I would propose the following solution:
MyXmlReader - class that supplies the access to the XML file
DataElement - class that contains data about the queries Element, including its child elements
class MyXmlReader
{
// refference to current list element
XmlNode currentListElement = null;
XmlDocument xml;
// load file, initialize and return data that contains info ablut
// the first level elements names
public DataElement Load(string path)
{
xml = new XmlDocument();
xml.Load(path);
Init();
DataElement result = new DataElement();
result.SubTitles = GetChildTitles();
return result;
}
// Initialize the reader to read from the beggining
public void Init()
{
currentListElement = xml.DocumentElement;
}
// Get next child
public DataElement GetNext(string title)
{
string tempTitle;
foreach (XmlNode child in currentListElement.ChildNodes)
{
DataElement result = null;
if (child.Attributes["Title"].Value == title)
{
// create object that contains the data about the child nodes Titles
result = new DataElement();
result.Title = child.Attributes["Title"].Value;
if (child.FirstChild != null) // means no child nodes
{
currentListElement = child.FirstChild;
// add subelements subtitles
result.SubTitles.AddRange(GetChildTitles());
}
return result;
}
}
return null;
}
public List<string> GetChildTitles()
{
List<string> result = new List<string>();
foreach (XmlNode child in currentListElement.ChildNodes)
{
result.Add(child.Attributes["Title"].Value);
}
return result;
}
}
// add any other data to this class
// that you need about the element you return
class DataElement
{
public List<string> SubTitles = new List<string>();
public string Title { get; set; }
}
Test:
// test
static void Main(string[] args)
{
MyXmlReader reader = new MyXmlReader();
DataElement data = reader.Load("Data.xml");
// Generic test:
// get first module
data = reader.GetNext(data.SubTitles[0]);
// get first function
data = reader.GetNext(data.SubTitles[0]);
// get first sub-function
data = reader.GetNext(data.SubTitles[0]);
// you can write test with hardcode nodes names like this:
reader.Init();
// get first module
data = reader.GetNext("Mod1");
// get first function
data = reader.GetNext("Fun1");
// get first sub-function
data = reader.GetNext("SubFun1");
Console.Read();
}

Related

XML Element's element/attribute annotation in C#

Is that possible to get an element's (child) element or an element's own attribute via xml annotation without phantom classes?
example current code snipet:
XML:
<root>
...
<epno type="1">12</epno>
...
</root>
C# classes:
[XmlRoot("root")]
class Root {
...
[XmlElement("epno")]
public Epno epno;
...
}
class Epno { //"Phantom" class
[XmlAttribute("type")]
public int type;
[XmlText]
public int epno;
}
What I want is to remove that Epno class and move those props into the Root class...
[XmlRoot("root")]
class Root {
...
[XmlElement("epno")]
[XmlAttribute("type")] // I need a solution for this...
public int type;
[XmlElement("epno")]
public int epno;
...
}
Also theres another place, where is a plus level, there I want to get an element's attribute, which is an another element... Than I want to get the element's element value.
For this an Xml example:
<root>
<rates>
<permanent votes="100">6.54</permanent>
<temprate votes="100">6.54</temprate>
</rates>
</root>
Here I want to put those values in to the root class, but in this case, there's need minimum 2 classes for parse it.
So, there's exist a way to deserialize these classes via annotation without these phantom classes and without writing my own xml parser?
There is no way to get "one child element and its attributes" of a root class and transform these into elements of its root class during XML Serialization by using only XML Serialization Attributes.
But, You can archieve desired result by:
Using XSLT Transformations
Implementing IXmlSerializable
I think what you are looking for is XmlDocument.
This does your second task:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml("<root><rates> <permanent votes = \"100\" > 6.54 </permanent> <temprate votes = \"100\" > 6.54 </temprate></rates> </root> ");
// find the nodes we are interested in
XmlNode Rates = xDoc.SelectSingleNode("//rates");
XmlNode Root = xDoc.SelectSingleNode("root");
// We can't modify a collection live so create a temporary list
List<XmlNode> TempList = new List<XmlNode>();
foreach (XmlNode Node in Rates.ChildNodes)
{
TempList.Add(Node);
}
// remove the nodes and their container node
foreach (XmlNode Node in TempList)
{
Node.ParentNode.RemoveChild(Node);
}
// Use this to remove the parent and children
// in one step
// Rates.ParentNode.RemoveChild(Rates);
// insert in desired location
foreach (XmlNode Node in TempList)
{
Root.AppendChild(Node);
}
// Hope this works!
xDoc.Save("C:\\test.xml");

How add,edit and delete node by line in xml

I have string XML. I loaded to XmlDocument. How can I add, edit and delete by simplest method by line, because I know only line which I should edit. It's better work wih XML like with string, or better work with XmlDocuments?
using System;
using System.Xml;
namespace testXMl
{
class Program
{
static void Main(string[] args)
{
string xml="<?xml version=\"1.0\"?>\r\n<application>\r\n<features>\r\n<test key=\"some_key\">\r\n</features>\r\n</application>";
XmlDocument xm = new XmlDocument();
xm.LoadXml(xml);
//Edit third line
//xm[3].EditName(featuresNew);
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<featuresNew>\r\n<test key=\"some_key\">\r\n</featuresNew>\r\n</application>"
//Add fourth line the Node
//xm[4].AddNode("FeatureNext");
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<FeatureNext>\r\n<FeatureNext>\r\n</features2>\r\n<test key=\"some_key\">\r\n</features>\r\n</application>"
//Delete sixth line
//xm[6].DeleteNode;
//xml->"<?xml version=\"1.0\"?>\r\n<application>\r\n<FeatureNext>\r\n<FeatureNext>\r\n</features2>\r\n</features>\r\n</application>"
}
}
}
Thanks, in advance.
You should always work with XDocument/XmlDocument objects. A key knowledge is the XPath query language.
This a quick XML crash course. Run with debugger and inspect the XML variable as you move on.
var xml = new XmlDocument();
xml.LoadXml(#"<?xml version='1.0'?>
<application>
<features>
<test key='some_key' />
</features>
</application>");
// Select an element to work with; I prefer to work with XmlElement instead of XmlNode
var test = (XmlElement) xml.SelectSingleNode("//test");
test.InnerText = "another";
test.SetAttribute("sample", "value");
var attr = test.GetAttribute("xyz"); // Works, even if that attribute doesn't exists
// Create a new element: you'll need to point where you should add a child element
var newElement = xml.CreateElement("newElement");
xml.SelectSingleNode("/application/features").AppendChild(newElement);
// You can also select elements by its position;
// in this example, take the second element inside "features" regardless its name
var delete = xml.SelectSingleNode("/application/features/*[2]");
// Trick part: if you found the element, navigate to its parent and remove the child
if (delete != null)
delete.ParentNode.RemoveChild(delete);

Looking for performant XyDiff Port in C# (XML Diff Patch with XID)

I store some data as XML and the changes of the user as diff to the original XML, so the data for the user can be patched on runtime.
Example for the original xml (only part of it):
<module id="">
<title id="">
...
</title>
<actions>
...
</actions>
<zones id="" selected="right">
<zone id="" key="right" name="Right" />
</zones>
</module>
Example of the user diff (the user changed the value of selected from right to left):
<xd:xmldiff version="1.0" srcDocHash=""
options="IgnoreChildOrder IgnoreNamespaces IgnorePrefixes IgnoreSrcValidation "
fragments="no"
xmlns:xd="http://schemas.microsoft.com/xmltools/2002/xmldiff">
<xd:node match="1">
<xd:node match="3">
<xd:change match="#selected">left</xd:change>
</xd:node>
</xd:node>
</xd:xmldiff>
The problem is, that the patch looks for the order of the XML nodes. If the order changes than the diff cannot be applied anymore or even worse it will be applied wrongly. So I would prefer patching by XID.
Does anyone know a performant library or algorith for C# for a XyDiff?
We developed now our own solution that works fine.
What did we do:
ensure that every xml node in the original file has an unique id (no
matter on which level)
generate a flat xml patch for user changes that only saves the
changes of each changed node (without the level structure)
if the user changes a value, write a xmlpatch node in the patch xml
with attribute targetid pointing to the id of the original node
if the user changed an attribute, write the attribute with the new
value to the xmlpatch node
if the user changes a value, write the value to the xmlpatch node
The xml patch looks the following:
<patch>
<xmlpatch sortorder="10" visible="true" targetId="{Guid-x}" />
<xmlpatch selected="left" targetId="{Guid-y}" />
<xmlpatch targetId="{Guid-z}">true</xmlpatch>
</patch>
The code to produce the patch xml is pretty easy. We loop throug all xml nodes and for each node through all attributes. If an attribute or value of a node is different to the original, we generate the patch node with the attribute or value. Please note that the code was written in one night ;)
public static XDocument GenerateDiffGram(XDocument allUserDocument, XDocument runtimeDocument)
{
XDocument diffDocument = new XDocument();
XElement root = new XElement("patch");
AddElements(root, runtimeDocument, allUserDocument.Root);
diffDocument.Add(root);
return diffDocument;
}
private static void AddElements(XElement rootPatch, XDocument runtimeDocument, XElement allUserElement)
{
XElement patchElem = null;
if (allUserElement.Attribute("id") != null
&& !string.IsNullOrWhiteSpace(allUserElement.Attribute("id").Value))
{
// find runtime element by id
XElement runtimeElement = (from e in runtimeDocument.Descendants(allUserElement.Name)
where e.Attribute("id") != null
&& e.Attribute("id").Value.Equals(allUserElement.Attribute("id").Value)
select e).FirstOrDefault();
// create new patch node
patchElem = new XElement("xmlpatch");
// check for changed attributes
foreach (var allUserAttribute in allUserElement.Attributes())
{
XAttribute runtimeAttribute = runtimeElement.Attribute(allUserAttribute.Name);
if (!allUserAttribute.Value.Equals(runtimeAttribute.Value))
{
patchElem.SetAttributeValue(allUserAttribute.Name, runtimeAttribute.Value);
}
}
// check for changed value
if (!allUserElement.HasElements
&& !allUserElement.Value.Equals(runtimeElement.Value))
{
patchElem.Value = runtimeElement.Value;
}
}
// loop through all children to find changed values
foreach (var childElement in allUserElement.Elements())
{
AddElements(rootPatch, runtimeDocument, childElement);
}
// add node for changed value
if (patchElem != null
&& (patchElem.HasAttributes
|| !string.IsNullOrEmpty(patchElem.Value)))
{
patchElem.SetAttributeValue("targetId", allUserElement.Attribute("id").Value);
rootPatch.AddFirst(patchElem);
}
}
On runtime we patch the changes saved in the patch xml back. We geht the original node by the targetid and overwrite the attributes and values.
public static XDocument Patch(XDocument runtimeDocument, XDocument userDocument, string modulePath, string userName)
{
XDocument patchDocument = new XDocument(userDocument);
foreach (XElement element in patchDocument.Element("patch").Elements())
{
// get id of the element
string idAttribute = element.Attribute("targetId").Value;
// get element with id from allUserDocument
XElement sharedElement = (from e in runtimeDocument.Descendants()
where e.Attribute("id") != null
&& e.Attribute("id").Value.Equals(idAttribute)
select e).FirstOrDefault();
// element doesn't exist anymore. Maybe the admin has deleted the element
if (sharedElement == null)
{
// delete the element from the user patch
element.Remove();
}
else
{
// set attributes to user values
foreach (XAttribute attribute in element.Attributes())
{
if (!attribute.Name.LocalName.Equals("targetId"))
{
sharedElement.SetAttributeValue(attribute.Name, attribute.Value);
}
}
// set element value
if (!string.IsNullOrEmpty(element.Value))
{
sharedElement.Value = element.Value;
}
}
}
// user patch has changed (nodes deleted by the admin)
if (!patchDocument.ToString().Equals(userDocument.ToString()))
{
// save back the changed user patch
using (PersonalizationProvider provider = new PersonalizationProvider())
{
provider.SaveUserPersonalization(modulePath, userName, patchDocument);
}
}
return runtimeDocument;
}

Change Value of the last Attribute in an XML c#

I was working on a bunch of XMLs that all share an attribute that contains the string "name" in them. The following code selects the attribute with string "name" in it and assign a new value to it.
public void updateXmlFile(string strFileName)
{
try
{
//Load the Document
XmlDocument doc = new XmlDocument();
doc.Load(strFileName);
//Set the changed Value
string newValue = GetUniqueKey();
//Select all nodes in the XML then choose from them
//the first node that contain string "name" in it
XmlNodeList list = doc.SelectNodes("//#*");
XmlNode filteredNode = list.Cast<XmlNode>()
.First(item => item.Name.ToLower().Contains("name"));
//Assign the newValue to the value of the node
filteredNode.Value = newValue;
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
Now a new XMLs were added that dosen't have the string "name" in them, so instead of modifying the attribute with string "name" in it I decided to simply modify the last attribute no matter what it was (not the first)
Can anybody tell me how to do that?
EDIT
Here is an example of my XML
<?xml version="1.0" encoding="utf-8"?>
<CO_CallSignLists Version="24" ModDttm="2010-09-13T06:45:38.873" ModUser="EUADEV\SARE100" ModuleOwner="EUADEVS06\SS2008" CreateDttm="2009-11-05T10:19:31.583" CreateUser="EUADEV\A003893">
<CoCallSignLists DataclassId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" EntityId="E3FC5E2D-FE84-492D-AD94-3ACCED870714" MissionID="4CF71AB2-0D92-DE11-B5D1-000C46F3773D" BroadcastType="S" DeputyInSpecialList="1" SunSpots="1537634cb70c6d80">
<CoCallSigns EntityId="DEBF1DDB-3C92-DE11-A280-000C46F377C4" CmdID="C45F3EF1-1292-DE11-B5D1-000C46F3773D" ModuleID="6CB497F3-AD63-43F1-ACAE-2C5C3B1D7F61" ListType="HS" Name="Reda Sabassi" Broadcast="INTO" PhysicalAddress="37" IsGS="1" HCId="0" CommonGeoPos="1" GeoLat="0.0000000" GeoLong="0.0000000">
<CoRadios EntityId="E1BF1DDB-3C92-DE11-A280-000C46F377C4" RadioType="HF" />
</CoCallSigns>
</CoCallSignLists>
</CO_CallSignLists>
#Alex: You notice that the "SunSpots" attribute (last attribute in the first child element) is successfully changed. But now when I wanna load the XML back into the DB it gives me an error
Here is the modified code
public void updateXmlFile(string strFileName)
{
try
{
XDocument doc = XDocument.Load(strFileName);
XAttribute l_attr_1 = (doc.Elements().First().Elements().First().Attributes().Last());
l_attr_1.Value = GetUniqueKey();
Console.WriteLine("Name: {0} Value:{1}", l_attr_1.Name, l_attr_1.Value);
doc.Save(strFileName);
}
catch (XmlException xex) { Console.WriteLine(xex); }
}
I was thinking of making an if statment which checks if the XML has an attribute that contains string "name" in it (since most of my XMLs has an attribute that contains name in them) if it does then change the attribute's value if not look for the last attribute and change it.. not the best solution but just throwing it out there
Then definitely use Linq to XML.
Example:
using System.Xml.Linq;
string xml = #"<?xml version=""1.0"" encoding=""utf-8""?>
<Commands Version=""439"" CreateUser=""Reda"">
<CmCommands DataclassId=""57067ca8-ef96-4d2e-a085-6bd7e8b24126"" OrderName = ""Tea"" Remark=""Black"">
<CmExecutions EntityId=""A9A5B0F2-6AB4-4619-9106-B0F85F86EE01"" Lock=""n"" />
</CmCommands>
</Commands>";
XDocument x = XDocument.Parse(xml);
Debug.Print(x.Elements().First().Elements().First().Attributes().Last().Value);
// Commands ^ CmCommands ^ Remark ^
That is, word for word, the last attribute of the first child of the first element.
You can also query for element/attribute names, like:
Debug.Print(x.Descendants(XName.Get("CmCommands", "")).First().Attribute(XName.Get("Remark", "")).Value);
And of course you can use all of the Linq goodness like Where, Select, Any, All etc.
Note: replace XDocument.Parse with XDocument.Load if appropriate etc.
I've not tested this but you should be able to do all of this in the XPath expression. Something like this:
//#*[contains(node-name(.), 'name')][last()]
This will return only the last attribute with the string name anywhere in its name.
If you only want the last attribute, irrespective of it's name, use this:
//#*[last()]
Look at class XmlAttributeCollection. You can get this collection by reading property Attributes of XmlNode. Just get the last by index.
Instead of .First(), use an extension method like this:
public static T LastOrDefault<T>(this IEnumerable<T> list)
{
T val = null;
foreach(T item in list)
{
val = item;
}
return val;
}

C# newbie: reading repetitive XML to memory

I'm new to C#. I'm building an application that persists an XML file with a list of elements. The structure of my XML file is as follows:
<Elements>
<Element>
<Name>Value</Name>
<Type>Value</Type>
<Color>Value</Color>
</Element>
<Element>
<Name>Value</Name>
<Type>Value</Type>
<Color>Value</Color>
</Element>
<Element>
<Name>Value</Name>
<Type>Value</Type>
<Color>Value</Color>
</Element>
</Elements>
I have < 100 of those items, and it's a single list (so I'm considering a DB solution to be overkill, even SQLite). When my application loads, I want to read this list of elements to memory. At present, after browsing the web a bit, I'm using XmlTextReader.
However, and maybe I'm using it in the wrong way, I read the data tag-by-tag, and thus expect the tags to be in a certain order (otherwise the code will be messy). What I would like to do is read complete "Element" structures and extract tags from them by name. I'm sure it's possible, but how?
To clarify, the main difference is that the way I'm using XmlTextReader today, it's not tolerant to scenarios such as wrong order of tags (e.g. Type comes before Name in a certain Element).
What's the best practice for loading such structures to memory in C#?
It's really easy to do in LINQ to XML. Are you using .NET 3.5? Here's a sample:
using System;
using System.Xml.Linq;
using System.Linq;
class Test
{
[STAThread]
static void Main()
{
XDocument document = XDocument.Load("test.xml");
var items = document.Root
.Elements("Element")
.Select(element => new {
Name = (string)element.Element("Name"),
Type = (string)element.Element("Type"),
Color = (string)element.Element("Color")})
.ToList();
foreach (var x in items)
{
Console.WriteLine(x);
}
}
}
You probably want to create your own data structure to hold each element, but you just need to change the "Select" call to use that.
Any particular reason you're not using XmlDocument?
XmlDocument myDoc = new XmlDocument()
myDoc.Load(fileName);
foreach(XmlElement elem in myDoc.SelectNodes("Elements/Element"))
{
XmlNode nodeName = elem.SelectSingleNode("Name/text()");
XmlNode nodeType = elem.SelectSingleNode("Type/text()");
XmlNode nodeColor = elem.SelectSingleNode("Color/text()");
string name = nodeName!=null ? nodeName.Value : String.Empty;
string type = nodeType!=null ? nodeType.Value : String.Empty;
string color = nodeColor!=null ? nodeColor.Value : String.Empty;
// Here you use the values for something...
}
It sounds like XDocument, and XElement might be better suited for this task. They might not have the absolute speed of XmlTextReader, but for your cases they sound like they would be appropriate and it would make dealing with fixed structures a lot easier. Parsing out elements would work like so:
XDocument xml;
foreach (XElement el in xml.Element("Elements").Elements("Element")) {
var name = el.Element("Name").Value;
// etc.
}
You can even get a bit fancier with Linq:
XDocument xml;
var collection = from el in xml.Element("Elements").Elements("Element")
select new { Name = el.Element("Name").Value,
Color = el.Element("Color").Value,
Type = el.Element("Type").Value
};
foreach (var item in collection) {
// here you can use item.Color, item.Name, etc..
}
You could use XmlSerializer class (http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx)
public class Element
{
public string Name { get; set; }
public string Type { get; set; }
public string Color { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml =
#"<Elements>
<Element>
<Name>Value</Name>
<Type>Value</Type>
<Color>Value</Color>
</Element>(...)</Elements>";
XmlSerializer serializer = new XmlSerializer(typeof(Element[]), new XmlRootAttribute("Elements"));
Element[] result = (Element[])serializer.Deserialize(new StringReader(xml));}
You should check out Linq2Xml, http://www.hookedonlinq.com/LINQtoXML5MinuteOverview.ashx

Categories

Resources