I have a question) how can I get value from specific node(info/weather/day/day_part type="День" /tempreture )
<?xml version="1.0" encoding="UTF-8"?>
<info lang="ru" xmlns:x="http://www.yandex.ru/xscript">
+<region id="213" lon="37.617671" lat="55.755768" zoom="10"/>
<traffic lon="37.617671" lat="55.755768" zoom="10" region="213"></traffic>
<weather region="213" climate="1">
<source xmlns:xi="http://www.w3.org/2001/XInclude">mb3d</source>
<day xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Москва</title>
<country>Россия</country>
<time_zone>Europe/Moscow</time_zone>
<summer-time>0</summer-time>
<sun_rise>06:51</sun_rise>
<sunset>20:14</sunset>
<daytime>d</daytime>+<date date="2013-04-05T00:00:00Z"/>
<day_part type="день" typeid="2">
<weather_type>облачно</weather_type>
<weather_code>overcast</weather_code>
<image>http://weather.yandex.ru/i/5.png</image>
<image-v2 size="22x22">http://yandex.st/weather/v-1/i/icons/22x22/ovc_+6.png</image-v2>
<image-v3 size="48x48">http://yandex.st/weather/v-1/i/icons/48x48/ovc.png</image-v3>
<image_number>5</image_number>
<wind_speed>5.0</wind_speed>
<wind_direction id="se">юв</wind_direction>
<dampness>70</dampness>
<pressure>743</pressure>
<temperature color="F2F0E6" class_name="t6">+5</temperature>
<time_zone>Europe/Moscow</time_zone>
<observation_time>17:30</observation_time>
<observation>2013-04-05T17:30:00</observation>
</day_part>
<day_part type="вечер" typeid="3"></day_part>
<day_part type="ночь" typeid="4"></day_part>
<day_part type="утро" typeid="1"></day_part>
<day_part type="день" typeid="2"></day_part>
<night_short></night_short>
<tomorrow></tomorrow>
</day>
<url xmlns:xi="http://www.w3.org/2001/XInclude">http://pogoda.yandex.ru/moscow/</url>
</weather>
</info>
I have code, but how can I specify that I need node with type="День"
XElement elem = xDocument.Element("info");
if (elem != null)
foreach (var el in elem.Elements("weather").Elements("day").Elements("day_part"))
{
var level = el.Element("temperature").Value;
listBox1.Items.Add(level);
};
This block of code returns two values(+5, +6) but I need only one(+5) and throw exception.
First of all - your input XML is not valid and it can't be loaded into XDocument. I fixed it to be:
<?xml version="1.0" encoding="UTF-8"?>
<info lang="ru" xmlns:x="http://www.yandex.ru/xscript">+<region id="213" lon="37.617671" lat="55.755768" zoom="10" />
<traffic lon="37.617671" lat="55.755768" zoom="10" region="213"></traffic>
<weather region="213" climate="1">
<source xmlns:xi="http://www.w3.org/2001/XInclude">mb3d</source>
<day xmlns:xi="http://www.w3.org/2001/XInclude">
<title>Москва</title>
<country>Россия</country>
<time_zone>Europe/Moscow</time_zone>
<summer-time>0</summer-time>
<sun_rise>06:51</sun_rise>
<sunset>20:14</sunset>
<daytime>d</daytime>
<date date="2013-04-05T00:00:00Z"></date>
<day_part type="день" typeid="2">
<weather_type>облачно</weather_type>
<weather_code>overcast</weather_code>
<image>http://weather.yandex.ru/i/5.png</image>
<image-v2 size="22x22">http://yandex.st/weather/v-1/i/icons/22x22/ovc_+6.png</image-v2>
<image-v3 size="48x48">http://yandex.st/weather/v-1/i/icons/48x48/ovc.png</image-v3>
<image_number>5</image_number>
<wind_speed>5.0</wind_speed>
<wind_direction id="se">юв</wind_direction>
<dampness>70</dampness>
<pressure>743</pressure>
<temperature color="F2F0E6" class_name="t6">+5</temperature>
<time_zone>Europe/Moscow</time_zone>
<observation_time>17:30</observation_time>
<observation>2013-04-05T17:30:00</observation>
</day_part>
<day_part type="вечер" typeid="3"></day_part>
<day_part type="ночь" typeid="4"></day_part>
<day_part type="утро" typeid="1"></day_part>
<day_part type="день" typeid="2"></day_part>
<night_short></night_short>
<tomorrow></tomorrow>
</day>
<url xmlns:xi="http://www.w3.org/2001/XInclude">http://pogoda.yandex.ru/moscow/</url>
</weather>
</info>
With that kind of input you can get desired nodes values using XPathSelectElements extension method:
var results = xDocument.XPathSelectElements("info/weather/day/day_part[#type='день']/temperature")
.Select(e => (string)e)
.ToList();
For XML document shown above results contains one string value: +5.
However, I would suggest searching day_part element using typeid attribute value instead of type:
var results = xDocument.XPathSelectElements("info/weather/day/day_part[#typeid=2]/temperature")
.Select(e => (string)e)
.ToList();
The same results, but less chance to fail because of encoding.
You can then fill your ListBox:
foreach (var value in results))
{
listBox1.Items.Add(value);
};
Try something like this
if (elem != null)
elem.Descendants("day_part").Where(el => el.Attribute("type").Value == "день")
.ToList().ForEach(el => listBox1.Items.Add(el.Element("temperature").Value));
You can use XPath extension for XLinq:
using System.Xml.Linq;
using System.Xml.XPath;
...
var doc = XDocument.Load("test.xml");
IEnumerable<XElement> dayElements = doc.XPathSelectElements("/info/weather/day/day_part[#type=\"день\"]");
Related
I'm having trouble using XElement to parse multiple elements through an XUnit XML file and return the value.
Here is the XML File
<assemblies timestamp="07/31/2018 14:58:48">
<assembly name="C:\Users\bf\Documents\Visual Studio 2015\Projects\xUnitDemo\xUnitDemo\bin\Debug\xUnitDemo.DLL" environment="64-bit .NET 4.0.30319.42000 [collection-per-class, parallel (1 threads)]" test-framework="xUnit.net 2.3.1.3858" run-date="2018-07-31" run-time="14:58:47" config-file="C:\Users\bf\Documents\Visual Studio 2015\Projects\xUnitDemo\packages\xunit.runner.console.2.4.0\tools\net452\xunit.console.exe.Config" total="15" passed="14" failed="1" skipped="0" time="0.257" errors="0">
<errors />
<collection total="2" passed="1" failed="1" skipped="0" name="Test collection for xUnitDemo.SimpleTests" time="0.070">
<test name="xUnitDemo.SimpleTests.PassingTest" type="xUnitDemo.SimpleTests" method="PassingTest" time="0.0636741" result="Pass">
<traits>
<trait name="test" value="test" />
<trait name="requirement" value="test" />
<trait name="labels" value="test" />
</traits>
</test>
<test name="xUnitDemo.SimpleTests.FailingTest" type="xUnitDemo.SimpleTests" method="FailingTest" time="0.0059474" result="Fail">
<failure exception-type="Xunit.Sdk.EqualException">
<message><![CDATA[Assert.Equal() Failure\r\nExpected: 5\r\nActual: 4]]></message>
<stack-trace><![CDATA[ at xUnitDemo.SimpleTests.FailingTest() in C:\Users\smsf\documents\visual studio 2015\Projects\xUnitDemo\xUnitDemo\SimpleTests.cs:line 30]]></stack-trace>
</failure>
</test>
</collection>
</assembly>
</assemblies>
I'm able to parse through test element using this code.
private static List<TestResults> GetTestAutomationExecutionResult(string filePath)
{
List<TestResults> testResults = new List<TestResults>();
XElement xelement = XElement.Load(filePath);
IEnumerable<XElement> results = xelement.Elements().Where(e => e.Name.LocalName == "test");
foreach (var result in results)
{
if (result.Attribute("result").Value == "Fail")
{
testResults.Add(new TestResults(result.Attribute("result").Value, "this is where the failure message would go"));
}
else
{
testResults.Add(new TestResults(result.Attribute("result").Value, ""));
}
}
But I'm having a hard time trying to find and add message inside of failure element in the foreach.
result.Attribute("message").Value
Your code has a couple problems:
The <result> elements are not direct children of the root element, so xelement.Elements().Where(e => e.Name.LocalName == "test") does not select anything. You need to descend deeper into the hierarchy, e.g. with Descendants().
The message text is contained in an indirect child element of the <test> node, specifically failure/message. You need to select this element to get the message.
result.Attribute("message").Value will not work because the XElement.Attribute(XName) method selects an XML attribute rather than an element.
See: XML attribute vs XML element.
Putting those two points together, your code should look like:
private static List<TestResults> GetTestAutomationExecutionResult(string filePath)
=> GetTestAutomationExecutionResult(XElement.Load(filePath));
private static List<TestResults> GetTestAutomationExecutionResult(XElement xelement)
{
var query = from e in xelement.Descendants()
where e.Name.LocalName == "test"
let r = e.Attribute("result").Value
let m = r == "Fail" ? e.Elements("failure").Elements("message").FirstOrDefault()?.Value : ""
select new TestResults(r, m);
return query.ToList();
}
Demo fiddle here.
My C# code:
XDocument doc = XDocument.Load(filename);
IEnumerable<XElement> collection =
doc.Elements("BCIRequest").Elements("Card").Elements("SelectedPIN");
My XML document:
<?xml version="1.0" encoding="utf-8"?>
<BCIRequest Version="2.0"
xmlns="urn:xxxxxx:bci:request">
<Header>
<SenderCode>XX99</SenderCode>
<SenderID>9999</SenderID>
<SequenceNumber>123</SequenceNumber>
<CardGroupCount>2</CardGroupCount>
<CardCount>4</CardCount>
<BlockCount>2</BlockCount>
</Header>
<!--card groups (must precede cards and blocks)-->
<CardGroup RequestID="1">
<CustomerNumber>XX01234567</CustomerNumber>
<CardGroupName Emboss="true">GROUP ONE</CardGroupName>
</CardGroup>
<CardGroup RequestID="2"
RequestRef="87416CB7-DAEF-483A-BD08-1A885531D958">
<CustomerNumber>XX12345678</CustomerNumber>
<CardGroupName Emboss="false">GROUP TWO</CardGroupName>
</CardGroup>
<Card RequestID="3">
<CustomerNumber>XX01234567</CustomerNumber>
<DriverCard>
<Driver Emboss="true">MARGE SIMPSON</Driver>
</DriverCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<GeneratedPIN/>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Card RequestID="4">
<CustomerNumber>XX12345678</CustomerNumber>
<VehicleCard>
<VRN Emboss="true">KYI 830</VRN>
</VehicleCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<SelectedPIN>0123</SelectedPIN>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Card RequestID="5">
<CustomerNumber>XX01234567</CustomerNumber>
<BearerCard>
<Bearer Emboss="true">OPEN XXXXXX</Bearer>
</BearerCard>
<CardTypeID>10</CardTypeID>
<PurchaseCategoryID>11</PurchaseCategoryID>
<Reissue>false</Reissue>
<FleetPIN/>
<OdoPrompt>false</OdoPrompt>
<CRNPrompt>false</CRNPrompt>
</Card>
<Block RequestID="6">
<CustomerNumber>XX01234567</CustomerNumber>
<PAN>7002999999999999991</PAN>
</Block>
<Card RequestID="7"
RequestRef="956EA6C5-7D7E-4622-94D0-38CAD9FCC8DF">
<CustomerNumber>XX01234567</CustomerNumber>
<DriverCard>
<Driver Emboss="true">HOMER SIMPSON</Driver>
<VRN Emboss="true">795 DVI</VRN>
</DriverCard>
<EmbossText>SPRINGFIELD POWER</EmbossText>
<CardTypeID>10</CardTypeID>
<TokenTypeID>20</TokenTypeID>
<PurchaseCategoryID>30</PurchaseCategoryID>
<ExpiryDate>2018-12</ExpiryDate>
<Reissue>true</Reissue>
<SelectedPIN>0123</SelectedPIN>
<OdoPrompt>true</OdoPrompt>
<CRNPrompt>true</CRNPrompt>
<!--address with optional fields specified-->
<CardDeliveryAddress OneTimeUse="false">
<ContactName>M xxxx</ContactName>
<ContactTitle>Mr</ContactTitle>
<CompanyName>Sxxxx</CompanyName>
<Line1>Sector 22-F</Line1>
<Line2>Springfield Power Plant</Line2>
<Line3>xxx Road</Line3>
<City>xxxx</City>
<Zipcode>xxxx</Zipcode>
<CountryCode>xxx</CountryCode>
</CardDeliveryAddress>
<!--address with only required fields-->
<PINDeliveryAddress OneTimeUse="true">
<Line1>xxxx</Line1>
<City>xxx</City>
<Zipcode>xxxx</Zipcode>
<CountryCode>xxxx</CountryCode>
</PINDeliveryAddress>
<Limits>
<Value Transaction="unlimited" Daily="200" Weekly="unlimited" Monthly="400"/>
<Volume Transaction="100" Daily="unlimited" Weekly="unlimited" Monthly="unlimited"/>
<Transactions Daily="unlimited" Weekly="unlimited" Monthly="unlimited"/>
<Day Monday="true" Tuesday="true" Wednesday="true" Thursday="true" Friday="true" Saturday="false" Sunday="false"/>
<Time Start="unlimited" End="17:00:00"/>
</Limits>
<Products>
<FuelProductRestrictionID>40</FuelProductRestrictionID>
<NonFuelProductRestrictionID>51</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>52</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>53</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>54</NonFuelProductRestrictionID>
<NonFuelProductRestrictionID>55</NonFuelProductRestrictionID>
</Products>
</Card>
<Block RequestID="8"
RequestRef="69A3E44D-DC10-4BEE-9249-1FC3C651BA0E">
<CustomerNumber>xxxxx</CustomerNumber>
<PAN>xxxxxx</PAN>
</Block>
</BCIRequest>
I need to update the element value in the above values. The old value is:
<SelectedPIN>0123</SelectedPIN>
And the new value should be:
<SelectedPIN EncryptedPIN="TKDS" FormNumber="000793906306">****</SelectedPIN>
Can anyone can help me on this?
If I selected the BCIRequest element, it's returning a null value. I've tried many solutions but unable to get one working on this XML file.
There many ways an Xml can be be modified, I prefer XDocument
XDocument doc = XDocument.Parse(input);
foreach (var element in doc.Descendants("SelectedPIN")) // filter if you want single element, in example I modifed for all elements.
{
element.Add(new XAttribute("EncryptedPIN", "TKDS"));
element.Add(new XAttribute("FormNumber", "000793906306"));
element.Value = "xxxxx"; //new value
}
and finally you can save the document using
doc.Save();
Take a look at this Demo
The root node (BCIRequest) contains a namespace so you need to include that into your query. Something like this should work:
XNamespace ns = "urn:xxxxxx:bci:request";
IEnumerable<XElement> collection = doc.Elements(ns + "BCIRequest").Elements(ns + "Card").Elements(ns + "SelectedPIN");
I have a snippet of an xml file that looks like this:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="wa_xml2html.xsl"?>
<computeraudit>
<title>Computer Audit :: 11/13/2012 10:43:22 AM</title>
<category title="Loaded Modules">
<subcategory title="">
<recordset title="">
<fieldname>Name</fieldname>
<fieldname>Version</fieldname>
<fieldname>Modified</fieldname>
<fieldname>Manufacturer</fieldname>
<datarow>
<fieldvalue>XmlLite.dll</fieldvalue>
<fieldvalue>1.3.1000.0</fieldvalue>
<fieldvalue>7/13/2009 8:16:21 PM</fieldvalue>
<fieldvalue>Microsoft Corporation</fieldvalue>
</datarow>
<datarow>
<fieldvalue>zip.dll</fieldvalue>
<fieldvalue>6.0.250.6</fieldvalue>
<fieldvalue>5/25/2011 8:30:12 AM</fieldvalue>
<fieldvalue>Sun Microsystems, Inc.</fieldvalue>
</datarow>
</recordset>
</subcategory>
</category>
</computeraudit>
I'm trying to parse it with C# XPath and get each of the fieldvalue element values.
The c# code looks like this:
XPathNavigator nav;
XPathDocument docNav;
XPathNodeIterator NodeIter;
String strExpression;
docNav = new XPathDocument(#"C:\TEMP\WinAudit\test.xml");
nav = docNav.CreateNavigator();
strExpression = "/computeraudit/category[#title=\"Loaded Modules\"]/subcategory/recordset/datarow";
NodeIter = nav.Select(strExpression);
while (NodeIter.MoveNext())
{
Console.WriteLine(NodeIter.Current.Value);
}
Console.ReadLine();
It returns one line for each datarow node with all of the fieldvalue element values concatenated together.
How do I return each of the distinct values for each fieldvalue element?
Change your XPath expression to:
"/computeraudit/category[#title=\"Loaded Modules\"]/subcategory/recordset/datarow/fieldvalue"
Your selector is only selecting the datarow elements.
Try strExpression = "/computeraudit/category[#title=\"Loaded Modules\"]/subcategory/recordset/datarow/fieldvalue";
i have a xml file like below
<root>
<Month name="Jan" index="1">
<Day index="2">
<event> sample 1</event>
</Day>
<Day index="3">
<event> sample 2 </event>
</Day>
</Month>
<Month name="Feb" index="2">
<Day index="5">
<event> sample 3 </event>
</Day>
<Day index="2">
<event> sample 4 </event>
</Day>
</Month>
</root>
how can i find special month and day event ?
for example i want getting "sample 2" when month is 1 and day is 2
XmlDocument doc = new XmlDocument();
doc.Load("EventsXML.xml");
XmlNode even= doc.SelectSingleNode("/root/Month[#index='1'] |/root/Month/day[#index='2']");
string str=even.InnerXml.ToString();
You need to modify your xpath to something like this:
XmlNode even= doc.SelectSingleNode("/root/Month[#index='1']/Day[#index='2']/event");
You can also use InnerText rather than InnerXml as you know the content is text, or you can modify the xPath to take this into account:
XmlNode even = doc.SelectSingleNode("/root/Month[#index='1']/Day[#index='2']/event/text()");
string str = even.Value;
An XDocument (Linq-to-XML) answer:
var doc = XDocument.Load(...);
var day = doc.Root
.Descendants("Month")
.Where(e => e.Attributes("index").Value == m)
.Descendants("Day")
.Where(e => e.Attributes("index").Value == d);
('m' and 'd' as string for simplicity)
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>