Converting XML nodes into attributes using C# - c#

I have an XML string that is loaded into an XMLDocument, similar to the one listed below:
<note>
<to>You</to>
<from>Me</from>
<heading>TEST</heading>
<body>This is a test.</body>
</note>
I would like to convert the text nodes to attributes (using C#), so it looks like this:
<note to="You" from="Me" heading="TEST" body="This is a test." />
Any info would be greatly appreciated.

Linq to XML is great for this kind of stuff. You could probably achieve it in one line if you'd want to. Just grab the child node names and their respective value and add all those 'key value pairs' as attributes instead.
MSDN docs here: http://msdn.microsoft.com/en-us/library/bb387098.aspx

Like seldon suggests, LINQ to XML would be a much better fit for this sort of task.
But here's a way to do it with XmlDocument. Not claiming this is fool-proof (haven't tested it), but it does seem to work for your sample.
XmlDocument input = ...
// Create output document.
XmlDocument output = new XmlDocument();
// Create output root element: <note>...</note>
var root = output.CreateElement(input.DocumentElement.Name);
// Append attributes to the output root element
// from elements of the input document.
foreach (var child in input.DocumentElement.ChildNodes.OfType<XmlElement>())
{
var attribute = output.CreateAttribute(child.Name); // to
attribute.Value = child.InnerXml; // to = "You"
root.Attributes.Append(attribute); // <note to = "You">
}
// Make <note> the root element of the output document.
output.AppendChild(root);

Following converts any simple XML leaf node into an attribute of its parent. It is implemented as a unit test. Encapsulate your XML content into an input.xml file, and check it saved as output.xml.
using System;
using System.Xml;
using System.Linq;
using NUnit.Framework;
[TestFixture]
public class XmlConvert
{
[TestCase("input.xml", "output.xml")]
public void LeafsToAttributes(string inputxml, string outputxml)
{
var doc = new XmlDocument();
doc.Load(inputxml);
ParseLeafs(doc.DocumentElement);
doc.Save(outputxml);
}
private void ParseLeafs(XmlNode parent)
{
var children = parent.ChildNodes.Cast<XmlNode>().ToArray();
foreach (XmlNode child in children)
if (child.NodeType == XmlNodeType.Element
&& child.Attributes.Count == 0
&& child.ChildNodes.Count == 1
&& child.ChildNodes[0].NodeType == XmlNodeType.Text
&& parent.Attributes[child.Name] == null)
{
AddAttribute(parent, child.Name, child.InnerXml);
parent.RemoveChild(child);
}
else ParseLeafs(child);
// show no closing tag, if not necessary
if (parent.NodeType == XmlNodeType.Element
&& parent.ChildNodes.Count == 0)
(parent as XmlElement).IsEmpty = true;
}
private XmlAttribute AddAttribute(XmlNode node, string name, string value)
{
var attr = node.OwnerDocument.CreateAttribute(name);
attr.Value = value;
node.Attributes.Append(attr);
return attr;
}
}

Related

How to check if first XML node exist?

I have the following XML:
<Root><Node1>node1 value</Node1><Node2>node2 value</Node2></Root>
I'd like to check if Root is the first node. If so, I then want to get the values for the to child nodes.
This XML is inside of an XElement. I've tried this:
xml.Element("Root")
but that returns null. If Root exist, shouldn't it return a non null value?
string xml = #"<Root><Node1>node1 value</Node1><Node2>node2 value</Node2></Root>";
XDocument doc = XDocument.Parse(xml);
var root = doc.Root;
if(root.Name == "Root")
{
foreach(var el in root.Descendants())
{
string nodeValue = el.Value;
}
}
You can check the name of the Root element from Root.Name. After that loop all elements in the root using doc.Root.Descendants().
Since xml is an instance of XElement, it already references the root element, which in this case named Root. Doing xml.Element("Root") will return result only if the <Root> element has another child <Root>.
I'd like to check if Root is the first node.
You can simply check the Name of the root element :
var raw = "<Root><Node1>node1 value</Node1><Node2>node2 value</Node2></Root>";
var xml = XElement.Parse(raw);
if (xml.Name.ToString() == "Root")
Console.WriteLine("Success");
else
Console.WriteLine("Fail");
Try this solution
XDocument doc = XDocument.Parse(#"<Root><Node1>node1value</Node1><Node2>node2value</Node2></Root>");
if(doc!=null)
{
if (doc.Root.Name.LocalName == "Root")
{
foreach (var i in doc.Descendants())
Console.WriteLine(i.Value);
}
}

How to remove empty XML child tags from an XML

I am looking to remove empty xml tags which are under a particular tag.
For Example, I have this XML:
<?xml version="1.0" encoding="UTF-16"?>
<apiRequest>
<policyTransaction>
<addDriver>
<driverName>xyz</driverName>
<IsCoInsured/>
<OnlyInsuredWithThisPolicy/>
</addDriver>
<retrieveDrivers/>
<validateDrivers/>
</policTransaction>
</apirequest>
I want this to be:
<?xml version="1.0" encoding="UTF-16"?>
<apiRequest>
<policyTransaction>
<addDriver>
<driverName>xyz</driverName>
</addDriver>
<retrieveDrivers/>
<validateDrivers/>
</policTransaction>
</apirequest>
I am using the code below to do this
var doc= XDocument.Parse(requestString);
var emptyElements = from descendant in doc.Descendants()
where descendant.IsEmpty || string.IsNullOrWhiteSpace(descendant.Value)
select descendant;
emptyElements.Remove();
But the elements after the adddriver also get eliminated.
<?xml version="1.0" encoding="UTF-16"?>
<apiRequest>
<policyTransaction>
<addDriver>
<driverName>xyz</driverName>
</addDriver>
</policTransaction>
</apirequest>
Which I don't want to happen. How could I check only for child elements to be removed if they are empty? I want the elements retrieveDrivers and validateDrivers to be there.
The above is obtained by a request, so once this XML is sent I get the response back with the elements for those two tags filled.
You want to only remove empty elements from a specific element, not the document in general, so instead of
doc.Decendants()
use
doc.Descendants("addDriver").Elements() // all sub elements of elements called addDriver
This is the most simple solution. This doesnt remove elements that had only empty elements.
Be careful though, if there are namespaces declared, you have to use the correct XName instead of plain string. See this for more info.
Edit
Here is a more generic approach
using System;
using System.Linq;
using System.Xml.Linq;
namespace Sandbox
{
public class Program
{
public static void Main(string[] args)
{
string xml = #"<?xml version='1.0' encoding='UTF-16'?>
<apiRequest>
<policyTransaction>
<addDriver>
<driverName>xyz</driverName>
<IsCoInsured/>
<OnlyInsuredWithThisPolicy/>
</addDriver>
<retrieveDrivers/>
<validateDrivers/>
</policyTransaction>
</apiRequest>";
XDocument doc = XDocument.Parse(xml);
doc.Root.RemoveEmptyChildren("addDriver");
Console.WriteLine(doc);
Console.ReadKey();
}
}
public static class XElementExtension
{
public static void RemoveEmptyChildren(this XElement element, XName name = null, bool recursive = false)
{
var children = name != null ? element.Descendants(name) : element.Descendants();
foreach (var child in children.SelectMany(child => child.Elements()).ToArray())
{
if (child.IsEmpty || string.IsNullOrWhiteSpace(child.Value))
child.Remove();
}
}
}
}
It is working fine If I use it in this way.
var requestString = XmlSerializer.Serialize(request);
var requestStringWithoutNullElements = XDocument.Parse(requestString);
var emptyElements = from descendant in requestStringWithoutNullElements.Descendants()
where descendant.IsEmpty && descendant.Name != "retrieveDrivers" && descendant.Name != "validateDrivers"
select descendant;
emptyElements.Remove();

Validate XML with multiple namespace

I have an XML document using two XSD and I want to validate it.
First of all, I retrieve all XSD used and add them in an XmlSchemaSet.
Next, I read my document with an XmlReader. If this works perfectly with an Xml with only one namespace, It fails when I try to validate my XML with two namespace or more like this :
<element1>
<LMSG:element2>value</LMSG:element2>
</element1>
Do you have a work around for it ?
Thanks for your help
-EDIT-
here is my function to load all my XSD to validate my file (It retrieve the XSD depending on a country standards):
public XmlSchemaSet GetAllXSDUsed(String strCountrySpecific, String strCurrentXSDPath, XmlNode currentNode = null)
{
XmlSchemaSet xSet = new XmlSchemaSet();
if (currentNode == null)
currentNode = this;
if (!String.IsNullOrWhiteSpace(currentNode.NamespaceURI))
{
String strXsdName = currentNode.NamespaceURI.Substring(currentNode.NamespaceURI.LastIndexOf(':') + 1) + ".xsd";
String strPath = strCurrentXSDPath;
XmlSchema schema = null;
if (!String.IsNullOrWhiteSpace(strCountrySpecific) && File.Exists(Path.Combine(strPath, strCountrySpecific, strXsdName)))
schema = xSet.Add(currentNode.NamespaceURI, Path.Combine(strPath, strCountrySpecific, strXsdName));
else if (File.Exists(Path.Combine(strPath, strXsdName)))
schema = xSet.Add(currentNode.NamespaceURI, Path.Combine(strPath, strXsdName));
}
foreach (XmlNode node in currentNode.ChildNodes)
{
XmlSchemaSet newxSet = GetAllXSDUsed(strCountrySpecific, strCurrentXSDPath, currentNode: node);
if (newxSet.Count > 0)
foreach (XmlSchema xs in newxSet.Schemas())
if (!xSet.Contains(xs.TargetNamespace))
xSet.Add(xs);
}
return xSet;
}
The problem is maybe that I specify the prefix used nowhere.
Here is the error that I get : The element 'CBISDDReqLogMsg' has invalid child element 'GrpHdr' in namespace 'urn:CBI:xsd:CBISDDReqLogMsg.00.00.06'. List of possible elements expected: 'GrpHdr'. for this block :
<CBISDDReqLogMsg>
<LMSG:GrpHdr xmlns:LMSG="urn:CBI:xsd:CBISDDReqLogMsg.00.00.06">
<LMSG:MsgId>Value</LMSG:MsgId>
Note that the prefix 'LMSG' is also declared at the root of the document.

Parsing XML in Compact Framework 3.5

I have a following XML that I need to parse, but I am having some problems. First, the amount of tags that I have inside Class tag is not known, and they not distinct (so I can't specify them by their name).
XML example:
<Class value="1B2">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
<Class value="2C4">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
Now this is my code so far to parse it:
Define xmlReader and XElement
XmlReader xmlReader = XmlReader.Create(modFunctions.InFName);
XElement xElem = new XElement("FirstName");
Then I am making connection to SQL Server CE database and this is my main loop that reads xmlfile:
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element &&
(xmlReader.LocalName == "Class" || xmlReader.LocalName == "FirstName") &&
xmlReader.IsStartElement() == true)
{
// Find Class tag
if (xmlReader.LocalName == "Class")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
// Get 1B2 value
HRName = xElem.FirstAttribute.Value;
// Tried to read each node in xElement to get FirstName values.. this didn't work
for ( (XNode e in (XNode)xElem)
{
string newString = ((string)xElem.Element("FirstName"));
}
}
// Also tried this before, but it is skips it since FirstName tags are inside Class tag.
if (xmlReader.LocalName == "FirstName")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
record.SetValue(0, xElem.Value);
record.SetValue(1, HRName);
rs.Insert(record);
}
}
}
And in my table (to where I am trying to write this) consists of two columns (FirstName and Class)
As mentioned in the comments, Linq to Xml is your best bet: http://msdn.microsoft.com/en-us/library/system.xml.linq(v=vs.90).aspx
Your parse would look something like:
string xml = "<root><Class value='1B2'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class>" +
"<Class value='2C4'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class></root>";
XDocument doc = XDocument.Parse(xml);
foreach(var node in doc.Descendants("Class"))
{
var cls = node.Attribute("value").Value;
string[] firstNames = node.Descendants("FirstName").Select(o => o.Value).ToArray();
}

Change XML node with same name?

everyone!
I have an XML file and need to change the value of a node, specifically the indicated line. The problem i have is that as you can see, there are many nodes.
How can i change this line? This XML file could be much larger, so i am looking for a solution that would take different amounts of 'launch.file' nodes into account.
The node that will need to be set to True will be identified by the corresponding NAME tag. So if i typed in ULTII, the DISABLED node for that block will be set to True. If i typed in Catl, then the DISABLED node for that block would be changed.
<?xml version="1.0" encoding="windows-1252"?>
<SBase.Doc Type="Launch" version="1,0">
<Descr>Launch</Descr>
<Filename>run.xml</Filename>
<Disabled>False</Disabled>
<Launch.ManualLoad>False</Launch.ManualLoad>
<Launch.File>
<Name>Catl</Name>
<Disabled>False</Disabled>
<ManualLoad>False</ManualLoad>
<Path>ft\catl\catl.exe</Path>
</Launch.File>
<Launch.File>
<Disabled>False</Disabled> <!-- change to True -->
<ManualLoad>False</ManualLoad>
<Name>ULTII</Name>
<Path>F:\ULTII.exe</Path>
<NewConsole>True</NewConsole>
</Launch.File>
<Launch.File>
<Name>ECA</Name>
<Disabled>False</Disabled>
<Path>C:\ECA.exe</Path>
</Launch.File>
</SBase.Doc>
I am using Visual Studio 2012, should you need to know.
Thank you to anyone who can help me out on this, i really appreciate it.
Heres my method to do what you want
private void DisableLaunchFile(string xmlfile, string launchFileName){
XDocument doc = XDocument.Load(xmlfile);
var launchFileElement = doc.Descendants("Launch.File").Where (d => d.Element("Name").Value == lauchFileName);
launchFileElement.Elements("Disabled").First().Value = true.ToString();
doc.Save(xmlfile);
}
Use it like:
string pathToXmlFile = //assign ;
DisableLaunchFile(pathToXmlFile, "Catl");
DisableLaunchFile(pathToXmlFile, "ULTII");
This can be achieved by using LINQ to XML (see XDocument Class).
Assuming that there is the single Launch.File element with Name element with value "ULTII":
var document = XDocument.Load(...);
var ultiiElement = document
.Descendants("Launch.File")
.Single(fileElement => fileElement.Element("Name").Value == "ULTII");
ultiiElement.Element("Disabled").Value = "True"; // or true.ToString()
document.Save(...);
This method will do the trick:
public void ChangeNode(string name, string filePath)
{
XDocument xDocument;
using (var streamReader = new StreamReader(filePath))
{
xDocument = XDocument.Parse(streamReader.ReadToEnd());
}
var nodes = xDocument.Descendants("Launch.File");
foreach (var node in nodes)
{
var nameNode = node.Descendants("Name").FirstOrDefault();
if (nameNode != null && nameNode.Value == name)
{
var disabledNode = node.Descendants("Disabled").FirstOrDefault();
if (disabledNode != null)
{
disabledNode.SetValue("True");
}
}
}
using (var streamWriter = new StreamWriter(filePath))
{
xDocument.Save(streamWriter);
}
}
The name you want to pass in is the name of the node that you want to change and the path is the file path to the xml file. So you might call it like:
ChangeNode("ULTII", "C:\\output.xml");
You may need to tidy this up a bit like matching the node name invariant of case or culture but it should get you started.

Categories