Say I have this XML:
<fields>
<field fieldid="fdtElem3Group">
<value actionid="1" actiontype="review">123456789</value>
<value actionid="2" actiontype="review">123456789</value>
<value actionid="3" actiontype="review">123456789</value>
<value actionid="4" actiontype="review">123456789</value>
<value actionid="5" actiontype="review">123456789</value>
</field>
<field fieldid="fdtElem7Group">
<value actionid="1" actiontype="review">29/10/75</value>
<value actionid="2" actiontype="review">29/10/74</value>
<value actionid="3" actiontype="review">29/10/74</value>
<value actionid="4" actiontype="review">29/10/76</value>
<value actionid="5" actiontype="review">29/10/74</value>
</field>
</fields>
I'm trying to get the value of the last 'value' element of each respective 'field' element using XmlReader. How would I do that please? This doesn't work:
while (xmlReader.Read())
{
if ((xmlReader.NodeType == System.Xml.XmlNodeType.Element) && (xmlReader.Name == "field"))
{
xmlReader.ReadToDescendant("value");
while (xmlReader.ReadToNextSibling("value"))
{
//just iterate over until it reaches the end
}
xmlReader.Read();
Console.WriteLine(xmlReader.Value);
}
}
Sorry, just read now you're looking for an xmReader solution. But using XDocument and Linq, you could do the following:
string xml = #"<fields>
<field fieldid=""fdtElem3Group"">
<value actionid=""1"" actiontype=""review"">123456789</value>
<value actionid=""2"" actiontype=""review"">123456789</value>
<value actionid=""3"" actiontype=""review"">123456789</value>
<value actionid=""4"" actiontype=""review"">123456789</value>
<value actionid=""5"" actiontype=""review"">123456789</value>
</field>
<field fieldid=""fdtElem7Group"">
<value actionid=""1"" actiontype=""review"">29/10/75</value>
<value actionid=""2"" actiontype=""review"">29/10/74</value>
<value actionid=""3"" actiontype=""review"">29/10/74</value>
<value actionid=""4"" actiontype=""review"">29/10/76</value>
<value actionid=""5"" actiontype=""review"">29/10/74</value>
</field>
</fields>";
var xmlDoc = XDocument.Parse(xml).Root;
var lastElements = xmlDoc.Descendants("field").Select(x => x.LastNode);
Hope this helps !
static void Main(string[] args)
{
string xml = #"<fields><field fieldid='fdtElem3Group'><value actionid='1' actiontype='review'>123456789</value><value actionid='2' actiontype='review'>123456789</value><value actionid='3' actiontype='review'>123456789</value><value actionid='4' actiontype='review'>123456789</value><value actionid='5' actiontype='review'>123456789</value></field><field fieldid='fdtElem7Group'><value actionid='1' actiontype='review'>29/10/75</value> <value actionid='2' actiontype='review'>29/10/74</value><value actionid='3' actiontype='review'>29/10/74</value><value actionid='4' actiontype='review'>29/10/76</value><value actionid='5' actiontype='review'>29/10/74</value></field></fields>";
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
foreach (XmlNode item in xmlDocument.DocumentElement.ChildNodes)
{
Console.WriteLine(item.LastChild.InnerXml);
}
Console.ReadKey();
}
If you don't want to load your entire XML into an XDocument (because, e.g., it is very large), you can adopt a hybrid approach where you use an XmlReader to iterate through the <field> and <value> elements in your XML file, load each <value> element into an XElement, then select the last one of each parent <field>. This keeps the memory footprint small and constant no matter how large the XML file grows.
First introduce the following two extension methods:
public static class XmlReaderExtensions
{
public static IEnumerable<IEnumerable<XElement>> ReadNestedElements(this XmlReader xmlReader, string outerName, string innerName)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == outerName)
{
using (var subReader = xmlReader.ReadSubtree())
{
yield return subReader.ReadElements(innerName);
}
}
xmlReader.Read();
}
}
public static IEnumerable<XElement> ReadElements(this XmlReader xmlReader, string name)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == name)
{
var element = (XElement)XNode.ReadFrom(xmlReader);
yield return element;
}
else
{
xmlReader.Read();
}
}
}
}
Then your algorithm to get the last 'value' element of each respective 'field' element becomes very simple:
public static List<string> LastFieldValues(XmlReader reader)
{
var query = reader.ReadNestedElements("field", "value")
.Select(l => l.LastOrDefault())
.Where(v => v != null)
.Select(v => (string)v);
return query.ToList();
}
Notes:
XmlReaderExtensions.ReadNestedElements() returns an enumerable of enumerables of <value> elements belonging to <field> elements. Enumerable.LastOrDefault() is then used to select the last <value> belonging to each <field>.
Whenever working directly with XmlReader, be sure to test XML both with and without indentation, for reasons explained here and here.
XmlReader.ReadSubtree() leaves the XmlReader positioned on the EndElement node of the element being read, while XNode.ReadFrom() leaves the reader positioned immediately after the EndElement node of the element being read. Just an annoying inconsistency to watch out for.
On the other hand, if you are willing to load the entire XML into memory as an XDocument, this can be done very simply using XPathSelectElements():
// Parse the XML into an XDocument.
// You can use use XDocument.Load(fileName) to load from a file
var doc = XDocument.Parse(xmlString);
var xpathLastValues = doc
.XPathSelectElements(#"//fields/field/value[last()]")
.Select(e => e.Value)
.ToList();
Sample fiddle.
Related
I'm working with a third party system that returns the following xml response
{<origenxml type="data">
<data>
<item>
<id><![CDATA[PIN/4590/67]]></id>
<filename><![CDATA[CS.STAR]]></filename>
<group>
<id>MAIN</id>
<dictionary id="CS.ST.BOXNO">
<desc><![CDATA[boxes]]></desc>
<value ln="0"></value>
<raw-value ln="0"></raw-value>
<value ln="1"><![CDATA[121880 ]]></value>
<raw-value ln="1"><![CDATA[B-FILE394**BCBF*BC*121880*]]></raw-value>
<value ln="2"><![CDATA[121881 ]]></value>
<raw-value ln="2"><![CDATA[B-FILE394**BCBF*BC*121881*]]></raw-value>
<value ln="3"><![CDATA[121882 ]]></value>
<raw-value ln="3"><![CDATA[B-FILE394**BCBF*BC*121882*]]></raw-value>
<value ln="4"><![CDATA[940288 ]]></value>
<raw-value ln="4"><![CDATA[B-FILE80**BCBF*BC*940288*]]></raw-value>
<value ln="5"><![CDATA[170415 ]]></value>
<raw-value ln="5"><![CDATA[ALPHA**BC*BC*170415*]]></raw-value>
</raw-value>
</dictionary>
</group>
</item>
</data>
</origenxml>}
Each line under Boxes, represents an object where the value is the Id and the raw-value is the data ( so line 5 - ID = 170415 and the Value = ALPHA**BC*BC*170415*) but i really can't figure out the best way to parse the xml. I have no control over the response xml, so i can't anything helpful like extra node names
First thing, I want to make sure this is really the XML, because it's malformed. There's a floating end tag for the </raw-value> right before the </dictionary>. Assuming that is a mistake then the solution is easy with Linq over XML.
You'll need to add the following using statements:
using System.IO;
using System.Xml.Linq;
I created this sample in a Console application, but you should be able to easily adapt it. The first step is to create an XDocument object from your XML. I'm pulling the text from a constant string value.
static XDocument CreateDocument()
{
using (var reader = new StringReader(testData)) {
return XDocument.Load(reader);
}
}
The rest is simply to create a query and enumerate it.
var doc = CreateDocument();
var results = from value in doc.Descendants("value")
join rawValue in doc.Descendants("raw-value")
on value.Attribute("ln").Value equals rawValue.Attribute("ln").Value
select new { Value = value.Attribute("ln").Value, RawValue = rawValue.Value };
foreach (var result in results) {
Console.WriteLine($"{result.Value} => {result.RawValue}");
}
There are some fields on my xml document which are same value but under different name. I want to select the value of "Error" on this xml document and so want to display "deger2". Also I want to display "deger5" How can I do this?
<?xml version="1.0" encoding="UTF-8"?>
<Database xmlns="http://www.example.com/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Datas>
<Data name="sMsg" access="private" xsi:type="collection" type="string">
<Value key="Cycle" value="deger1" />
<Value key="Error" value="deger2" />
<Value key="Info" value="deger3" />
<Value key="Jog" />
<Value key="Warning" />
</Data>
<Data name="tTabla" access="private" xsi:type="array" type="tabla" size="1">
<Value key="Cycle" value="deger4" />
<Value key="Error" value="deger5" />
<Value key="Info" value="deger6" />
<Value key="Jog" />
<Value key="Warning" />
</Data>
</Datas>
</Database>
You should take the xml namespace into considiration. Using Linq to Xml
var xDoc = XDocument.Parse(xmlstring); //XDocument.Load(filename)
XNamespace ns = "http://www.example.com/2";
var errors = xDoc.Descendants(ns + "Value") //<-- See the usage of ns
.Where(d => (string)d.Attribute("key") == "Error")
.Select(d => (string)d.Attribute("value"))
.ToList();
EDIT
Is there any way to select like this: select error value where Data name="tTabla"?
var errors = xDoc.Descendants(ns + "Data")
.First(d => (string)d.Attribute("name") == "tTabla")
.Descendants(ns + "Value")
.First(d => (string)d.Attribute("key") == "Error")
.Attribute("value")
.Value;
EDIT2
You can also use XPATH
var nsmgr = new XmlNamespaceManager(xDoc.CreateNavigator().NameTable);
nsmgr.AddNamespace("ns", "http://www.example.com/2");
var errors = xDoc.XPathSelectElement("//ns:Data[#name='tTabla']/ns:Value[#key='Error']", nsmgr)
.Attribute("value")
.Value;
U can also use XPath to select them:
XmlNodeList nodeList = root.SelectNodes("//Data/Value[#key="Error"]");
I want to get data from xml but there are lots of tags, fields and value keys. I couldn't select the value which I want. How can I select the "Error" value from this XML with C#?
<?xml version="1.0" encoding="UTF-8"?>
<Database xmlns="http://www.example.com/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Datas>
<Data name="sMsg" access="private" xsi:type="collection" type="string">
<Value key="Cycle" value="deger1" />
<Value key="Error" value="deger2" />
<Value key="Info" value="deger3" />
<Value key="Jog" />
<Value key="Warning" />
</Data>
<Data name="tTabla" access="private" xsi:type="array" type="tabla" size="1">
<Field name="dddd" xsi:type="array" type="bool" size="1" />
<Field name="ssss" xsi:type="array" type="bool" size="1" />
<Field name="aaaa" xsi:type="array" type="num" size="1" />
<Field name="rrrr" xsi:type="collection" type="num">
<Value key="Actuel" />
<Value key="Expected" />
</Field>
</Data>
</Datas>
</Database>
You can try this way :
var doc = XDocument.Parse(xml);
XNamespace d = doc.Root.GetDefaultNamespace();
var result = (string)
doc.Descendants(d + "Data")
.Elements(d + "Value")
.FirstOrDefault(o => (string) o.Attribute("key") == "Error")
.Attribute("value");
Console.WriteLine(result);
Try this it returns the XElement with Key equal to Error
XDocument m = XDocument.Load(#"Path");
var res = m.Descendants().Where(x => x.Name.LocalName.Equals("Value") && x.Attribute("key") != null && x.Attribute("key").Value.Equals("Error")).FirstOrDefault();
If there are multiple "Error" values for your attributes, you can do:
IEnumerable<XAttribute> answer = xml.Descendants().Attributes().Where(node => node.Value == "Error");
foreach (var xAttribute in answer)
{
Console.WriteLine(xAttribute.Value);
}
If you only want the first or there is only one:
string answer = xml.Descendants().Attributes().FirstOrDefault(node => node.Value == "Error");
Note FirstOrDefault may yield null if it doesn't find any "Error" values inside your xml.
These queries are done using LINQ To XML, i strongly encourage you to read up.
I have the following XML snippet-
-<Row>
<RowType Id="1"Label="Scotland">1985</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="England">1986</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="Wales">1987</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
I am using XmlReader to retrieve specific data from it like so -
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
{
string country = "";
string Year = "";
string count = "";
string tss= "";
string tss2 = "";
reader.MoveToContent();
while (reader.Read())
{
reader.ReadToFollowing("RowType");
country = reader.GetAttribute("Label");
country = country.Replace("'", "");
reader.ReadToFollowing("Year");
Year = reader.GetAttribute("Label");
reader.ReadToFollowing("Value");
count = reader.ReadElementContentAsString();
reader.ReadToFollowing("Field");
tss = reader.GetAttribute("Label");
reader.ReadToFollowing("Field");
tss2 = reader.GetAttribute("Label");
}
}
This is working fine for the first iteration, however on the second, it retrieves the values from the third row in the XML, and continues to skip to the next row after the one it should be parsing.
How can I resolve this?
Actually, your code is right; what is not right is the structure of the document. Or better, your code does not account for the specific structure of the document.
You can change that by adding the following bit:
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(new StringReader(xml), settings))
By default the XMLReader expects ConformanceLevel.Document and thus the file should have a structure like the following one:
<main>
<Row id="5">
<RowType Id="1" Label="Scotland">1985</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="1">
<RowType Id="1" Label="England">1986</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="4">
<RowType Id="1" Label="Wales">1987</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
</main>
I understand that the lack of separation between elements (e.g., Id="1"Label="Scotland" instead of Id="1" Label="Scotland") is a typo because separations have to exist in any case.
------------------- UPDATE
You report that your code does not deliver the expected result even after changing the conformance level. I have done a new test of your code and it works fine; at least, it iterates correctly. Thus, what I understand is that you want to retrieve different values than what you code does (it mixes app names, attributes and content).
Below you can see my own code (although I insist that yours iterates through the given information OK, too), which is more adaptable than yours; I am also including some comments in the parts where I think that you want to retrieve different information than what your code does. The basic idea is just retrieving information from the content (content), but your code takes it from anywhere.
string path = #"XML file";
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(path, settings))
{
string country = "";
string Year = "";
string count = "";
string tss = "";
string tss2 = "";
while (reader.ReadToFollowing("Row"))
{
XmlReader reader2 = reader.ReadSubtree();
while (reader2.Read())
{
if (reader2.NodeType == XmlNodeType.Element)
{
if (reader2.Name == "RowType")
{
country = reader2.GetAttribute("Label");
country = country.Replace("'", ""); //country_year = reader.ReadElementContentAsString(); -> "Scotland" -> 1985
}
else if (reader2.Name == "Year")
{
//IF XML IS -> <Year Id="11">1994<Year/>
//Then -> Year = reader2.GetAttribute("Label")
Year = reader2.GetAttribute("Label"); //-> 1994
}
else if (reader2.Name == "Value")
{
count = reader2.ReadElementContentAsString();
}
else if (reader2.Name == "Field")
{
if (reader2.GetAttribute("Label") == "Country")
{
tss = reader2.ReadElementContentAsString(); //I understand that this is what you want to read, instead the Label name
}
else if (reader2.GetAttribute("Label") == "Soccer")
{
tss2 = reader2.ReadElementContentAsString();//I understand that this is what you want to read, instead the Label name
}
}
}
}
}
}
This should deliver what you are looking for; or, in the worst scenario, a much clear idea about how to deal with the XML reading. Also it might be a good thing to include a try...catch just in case; note that any error while reading/dealing with the variables would provoke the reading process to be immediately stopped.
We can use LINQ to get this done if you want.
If you really want to read all the values from Xml into some variable....you can try something in similar lines...
XElement po = XElement.Load(#"SoccerCup.xml");
IEnumerable<XElement> childElements =
from el in po.Elements()
select el;
foreach (XElement el in childElements)
{
var Year=el.Element("Year").Value;
var country = el.Element("country").Value;
var count =el.Elemet("Value").Value;
Console.WriteLine("Year: " + Year);
Console.WriteLine("Country: " + country);
Console.WriteLine("Count: " + count);
}
Hope this helps...
I need to convert all attributes to nodes in an XML file, with the exception of attributes in the root node.
I found a similar question here: xquery to convert attributes to tags, but I need to do the conversion in C#.
I have also found a possible solution using XLS here: Convert attribute value into element. However, that solution essentially changes the node name to the attribute name and removes the attribute.
I need to create new sibling nodes with the name and value of the attributes and remove the attributes, but still preserve the node that contained the attributes.
Given the following XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment Name="Test">
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value Source="Literal">O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value Source="Calculated" Initial="1">Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value Source="Property">BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
I need to produce the following XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
</Segments>
</Something>
I have written the following method to create a new XML object, creating new elements from the source element's attributes:
private static XElement ConvertAttribToElement(XElement source)
{
var result = new XElement(source.Name.LocalName);
if (source.HasElements)
{
foreach (var element in source.Elements())
{
var orphan = ConvertAttribToElement(element);
result.Add(orphan);
}
}
else
{
result.Value = source.Value.Trim();
}
if (source.Parent == null)
{
// ERROR: The prefix '' cannot be redefined from '' to 'http://www.something.com' within the same start element tag.
//foreach (var attrib in source.Attributes())
//{
// result.SetAttributeValue(attrib.Name.LocalName, attrib.Value);
//}
}
else
{
while (source.HasAttributes)
{
var attrib = source.LastAttribute;
result.AddFirst(new XElement(attrib.Name.LocalName, attrib.Value.Trim()));
attrib.Remove();
}
}
return result;
}
This method produces the following XML:
<Something>
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>
<Source>Literal</Source>O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>
<Source>Calculated</Source>
<Initial>1</Initial>Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>
<Source>Property</Source>BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
There are two immediate problems with the output:
1) The attributes in the root element are lost.
2) The attributes from the 'Value' element are created as child element instead of siblings.
To address the first issue, I tried to assign the attributes of the source element to the result element, but had that caused a "prefix '' cannot be redefined from '' to 'http://www.something.com' within the same start element tag" error. I commented out the code that caused the error for illustration.
To address the second issue, I attempted to add the element created from the attribute to the source.Parent element, but that resulted in the new element not appearing at all.
I also rewrote the method to operate directly on the source element:
private static void ConvertAttribToElement2(XElement source)
{
if (source.HasElements)
{
foreach (var element in source.Elements())
{
ConvertAttribToElement2(element);
}
}
if (source.Parent != null)
{
while (source.HasAttributes)
{
var attrib = source.LastAttribute;
source.Parent.AddFirst(new XElement(attrib.Name.LocalName, attrib.Value.Trim()));
attrib.Remove();
}
}
}
The rewrite produced the following XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Name xmlns="">Test</Name>
<Segment>
<SegmentField>
<Source xmlns="">Literal</Source>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
</SegmentField>
<SegmentField>
<Source xmlns="">Calculated</Source>
<Initial xmlns="">1</Initial>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
</SegmentField>
<SegmentField>
<Source xmlns="">Property</Source>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
The rewrite did resolve the first issue of preserving the attributes of the root element. It also partially addressed the second issue, but has produced a new problem: the new elements have a blank xmlns attribute.
Use this method to convert Xml attributes to xml nodes:
public static void ReplaceAttributesByNodes(XmlDocument document, XmlNode node)
{
if (document == null)
{
throw new ArgumentNullException("document");
}
if (node == null)
{
throw new ArgumentNullException("node");
}
if (node.HasChildNodes)
{
foreach (XmlNode tempNode in node.ChildNodes)
{
ReplaceAttributesByNodes(document, tempNode);
}
}
if (node.Attributes != null)
{
foreach (XmlAttribute attribute in node.Attributes)
{
XmlNode element = document.CreateNode(XmlNodeType.Element, attribute.Name, null);
element.InnerText = attribute.InnerText;
node.AppendChild(element);
}
node.Attributes.RemoveAll();
}
}
//how to use it
static void Main()
{
string eventNodeXPath = "Something/Segments/Segment";//your segments nodes only
XmlDocument document = new XmlDocument();
document.Load(#"your playlist file full path");//your input playlist file
XmlNodeList nodes = document.SelectNodes(eventNodeXPath);
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
ReplaceAttributesByNodes(document, node);
}
}
doc.Save("your output file full path");
}
This XSLT transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.something.com">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vNamespace" select="namespace-uri(/*)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*/#*">
<xsl:element name="{name()}" namespace="{$vNamespace}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="x:Value">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
<xsl:apply-templates select="#*"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment Name="Test">
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value Source="Literal">O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value Source="Calculated" Initial="1">Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value Source="Property">BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
produces exactly the wanted, correct result:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
</Segments>
</Something>
Explanation:
The identity rule/template copies every node "as is".
The identity rule is overriden by two templates -- one matching any attribute of any element that is not the top element of the document, another matching any Value element.
The template matching attributes (the first overriding template) creates in place of the attribute an element with the same local name and value as the matched attribute. In addition, the element name is put in the same namespace as the one that the top element of the document belongs to (this avoids the xmlns="").
The template matching any Value element copies it and processes al of its subtree (descendent nodes), then processes its attributes. In this way the elements generated from the attributes become siblings and not children of the Value element.
You could build an extension method to flatten each element:
public static IEnumerable<XElement> Flatten(this XElement element)
{
// first return ourselves
yield return new XElement(
element.Name,
// Output our text if we have no elements
!element.HasElements ? element.Value : null,
// Or the flattened sequence of our children if they exist
element.Elements().SelectMany(el => el.Flatten()));
// Then return our own attributes (that aren't xmlns related)
foreach (var attribute in element.Attributes()
.Where(aa => !aa.IsNamespaceDeclaration))
{
// check if the attribute has a namespace,
// if not we "borrow" our element's
var isNone = attribute.Name.Namespace == XNamespace.None;
yield return new XElement(
!isNone ? attribute.Name
: element.Name.Namespace + attribute.Name.LocalName,
attribute.Value);
}
}
You would use this like:
public static XElement Flatten(this XDocument document)
{
// used to fix the naming of the namespaces
var ns = document.Root.Attributes()
.Where(aa => aa.IsNamespaceDeclaration
&& aa.Name.LocalName != "xmlns")
.Select(aa => new { aa.Name.LocalName, aa.Value });
return new XElement(
document.Root.Name,
// preserve "specific" xml namespaces
ns.Select(n => new XAttribute(XNamespace.Xmlns + n.LocalName, n.Value)),
// place root attributes right after the root element
document.Root.Attributes()
.Where(aa => !aa.IsNamespaceDeclaration)
.Select(aa => new XAttribute(aa.Name, aa.Value)),
// then flatten our children
document.Root.Elements().SelectMany(el => el.Flatten()));
}
This produces output as you have indicated, except for the xsi:schemaLocation attribute, which is problematic as I've found. It selects a default namespace name (p1), but ultimately it works.
Produces the following:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
<Name>Test</Name>
</Segments>
</Something>