C# to convert xml attributes to elements - c#

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>

Related

How do I filter XML nodes and its child nodes in a XML document?

So I have a XML document in which I get as a HttpWebResponse from an API I'm working with. However, the document in which I get is about 166 thousand rows long and includes a lot of unnecessary data in which I don't need. I've read the API documentation and it doesn't seem that there is a filtering attribute in which suits my needs. So I figured that I would filter it programmatically but I haven't been able to succeed on that part. So the XML document looks a bit like this:
<sensortree>
<nodes>
<group id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<status_raw></status_raw>
<active></active>
<probenode id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<status_raw></status_raw>
<active></active>
<group id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<status_raw></status_raw>
<active></active>
<group id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<status_raw></status_raw>
<active></active>
<group id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<status_raw></status_raw>
<active></active>
<device id="">
<summary></summary>
<name></name>
<deviceicon></deviceicon>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<host></host>
<status_raw></status_raw>
<active></active>
<sensor id="">
<name></name>
<id></id>
<url></url>
<tags></tags>
<priority></priority>
<fixed></fixed>
<hascomment></hascomment>
<sensortype></sensortype>
<sensorkind></sensorkind>
<interval></interval>
<status_raw></status_raw>
<status></status>
<datamode></datamode>
<lastvalue></lastvalue>
<lastvalue_raw></lastvalue_raw>
<statusmessage></statusmessage>
<active></active>
</sensor>
</device>
</group>
</group>
</group>
</probenode>
</group>
</nodes>
</sensortree>
Now you might notice that all elements are empty, that is because I have deleted all inner texts of the elements as I cannot share that information with the public, although I asure you that all elements have inner texts in reality. Now each device also have multiple sensors but they all look the same so I only included one. All the group elements also have a different structure of child nodes so each group is not like the other. Now I only need the elements "nodes", "group", "probenode", "device", "sensor", "name" and "id". The rest of the elements needs to be deleted from the document while I still keep the hierarchical structure of the document. So in short what I want to do is loop through all the nodes and each nodes child nodes, see if the element is an element I want to keep and delete it if it isn't. Another twist is that I don't know if XPath is an option because the structure is not the same everywhere. Things I have tried are the following.
//Method 1 - It doesn't delete the nodes that are not specified, I think it's because it doesn't check the childnodes because it does delete other nodes.
foreach (XmlNode node in xDoc)
{
if (node.Name == "group" || node.Name == "probenode" || node.Name == "device" || node.Name == "sensor")
{
}
else
{
node.ParentNode.RemoveChild(node);
}
}
//Method 2 - Not sure how to delete a node from a XmlReader instance
StringWriter sw = new StringWriter();
XmlTextWriter xw = new XmlTextWriter(sw);
xDoc.WriteTo(xw);
String xmlString = sw.ToString();
XmlReader rdr = XmlReader.Create(new System.IO.StringReader(xmlString));
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
if (rdr.LocalName == "group" || rdr.LocalName == "probenode" || rdr.LocalName == "device" || rdr.LocalName == "sensor"
|| rdr.LocalName == "name" || rdr.LocalName == "lastvalue" || rdr.LocalName == "status")
{
}
else
{
}
}
}
Now this is what I want my xml document to look like after it's formatted:
<sensortree>
<nodes>
<group id="">
<name></name>
<id></id>
<probenode id="">
<name></name>
<id></id>
<group id="">
<name></name>
<id></id>
<name></name>
<id></id>
<group id="">
<name></name>
<id></id>
<device id="">
<name></name>
<id></id>
<sensor id="">
<name></name>
<id></id>
</sensor>
</device>
</group>
</group>
</group>
</probenode>
</group>
</nodes>
</sensortree>
Use System.Xml.Linq:
// first get System.Xml.Linq.XDocument from XmlDocument
var xDoc = XDocument.Parse(xmlDocument.OuterXml);
// remove elements from xDoc that do not match one of your expected tags
var tags = new string[] { "group", "sensor", ... }
xDoc.Root.Descendants().Where(d => !tags.Contains(d.Name.LocalName)).Remove();
See if following recursive algorithm works :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument oldDoc = XDocument.Load(FILENAME);
XElement oldNodes = oldDoc.Descendants("nodes").FirstOrDefault();
string header = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><sensortree><nodes></nodes></sensortree>";
XDocument newDoc = XDocument.Parse(header);
XElement newNodes = newDoc.Descendants("nodes").FirstOrDefault();
GetTreeRecursively(oldNodes, newNodes);
}
static void GetTreeRecursively(XElement oldElement, XElement newElement)
{
string[] findTags = { "group", "probenode", "device", "sensor" };
List<XElement> oldChildren = oldElement.Elements().Where(x => findTags.Contains(x.Name.LocalName)).ToList();
foreach (XElement oldChild in oldChildren)
{
XElement newChild = new XElement(oldChild.Name.LocalName, new XAttribute("id", (string)oldChild.Attribute("id")));
newChild.Add(oldChild.Element("name"));
newChild.Add(oldChild.Element("id"));
newElement.Add(newChild);
GetTreeRecursively(oldChild, newChild);
}
}
}
}

Get last child element using XmlReader

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.

Change XML element value in c#

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");

How to turn one XML record with sub nodes into multiple records without XSLT or SQL

I need to figure out how to turn one XML record with sub nodes into multiple records using C#. Yes, I know it would be easier to do this using XSLT but that isn't an option. I have an XML file that must be modified to be used by 5 different uses so I need a common starting point.
Forgive my lack of understanding but nothing I can find seems to go in this direction. Everything goes in the other direction. Here is a sample of my code and file.
The source file.
<inventoryitems>
<inventoryitem>
<id>11101</id>
<displayname>LG HAMBURGER PATTY</displayname>
<basemeasure>EACH</basemeasure>
<reportingmeasure>EACH</reportingmeasure>
<measures>
<measure>
<name>CS</name>
<factor>1.000000</factor>
<isactive>1</isactive>
</measure>
<measure>
<name>ST</name>
<factor>8.000000</factor>
<isactive>1</isactive>
</measure>
<measure>
<name>EACH</name>
<factor>120.000000</factor>
<isactive>1</isactive>
</measure>
</measures>
<categories>
<category>
<name>MEATS</name>
</category>
</categories>
<locations />
<skus />
</inventoryitem>
<inventoryitem>
<id>11102</id>
<displayname>SM HAMBURGER PATTY</displayname>
<basemeasure>EACH</basemeasure>
<reportingmeasure>EACH</reportingmeasure>
<measures>
<measure>
<name>ST</name>
<factor>6.000000</factor>
<isactive>1</isactive>
</measure>
<measure>
<name>CS</name>
<factor>1.000000</factor>
<isactive>1</isactive>
</measure>
<measure>
<name>EACH</name>
<factor>96.000000</factor>
<isactive>1</isactive>
</measure>
</measures>
<categories>
<category>
<name>MEATS</name>
</category>
</categories>
<locations />
<skus />
</inventoryitem>
<inventoryitem>
<id>11202</id>
<displayname>BREAD SM BUN 4</displayname>
<basemeasure>EACH</basemeasure>
<reportingmeasure>EACH</reportingmeasure>
<measures>
<measure>
<name>TR</name>
<factor>1.000000</factor>
<isactive>1</isactive>
</measure>
<measure>
<name>EACH</name>
<factor>30.000000</factor>
<isactive>1</isactive>
</measure>
</measures>
<categories>
<category>
<name>BAKERY</name>
</category>
</categories>
<locations />
<skus />
</inventoryitem>
</inventoryitems>
What I need to get would look something like this.
<data>
<row InventoryItemId="11201" ItemDescription="BREAD LG BUN 5" CategoryName="BAKERY" Measure="TR" />
<row InventoryItemId="11201" ItemDescription="BREAD LG BUN 5" CategoryName="BAKERY" Measure="EACH" />
</data>
I was able to write the code to mode the value to an attribute when the is only one node but I am at a loss on what to do when there is a sub node with multiple values.
invlist = results.Substring(results.IndexOf("<inventoryitems>"), (results.IndexOf("</inventoryitemsresponsedata>") - results.IndexOf("<inventoryitems>")));
XmlDocument doc = new XmlDocument();
XmlNode nd = doc.CreateNode("element", "data", "");
doc.AppendChild(nd);
//XmlNode rw = doc.CreateNode("element", "row", "");
//nd.AppendChild(rw);
var invitems = new XmlDocument { InnerXml = invlist };
XmlNode result = doc.ImportNode(invitems.DocumentElement, true);
nd.AppendChild(result);
XmlNodeList ndList = doc.SelectNodes("data/inventoryitems/inventoryitem");
foreach (XmlNode id in ndList)
{
XmlNode idnode = id.SelectSingleNode("id");
if (idnode != null)
{
XmlNode rw = doc.CreateNode("element", "row", "");
nd.AppendChild(rw);
var attribute = doc.CreateAttribute("InventoryItemId");
attribute.Value = idnode.InnerXml;
var Description = doc.CreateAttribute("ItemDescription");
Description.Value = id.SelectSingleNode("displayname").InnerXml;
rw.Attributes.Append(attribute);
rw.Attributes.Append(Description);
}
XmlNodeList msList = id.SelectNodes("measures/measure");
foreach (XmlNode mes in msList)
{
XmlNode msnode = mes.SelectSingleNode("name");
if (msnode != null)
{
var attribute = doc.CreateAttribute("Measure");
attribute.Value = msnode.InnerXml;
//rw.Attributes.Append(attribute);
mes.Attributes.Append(attribute);
}
}
}
Any help would be appreciated.
Update: This is what I am getting.
<data>
<row InventoryItemId="11201" ItemDescription="BREAD LG BUN 5" CategoryName="BAKERY" />
<inventoryitem>
<measures>
<measure Measure="TR"></measure>
<measure Measure="EACH"></measure>
</measures>
<locations />
<skus />
</inventoryitem>
</data>
I figured it out. By wrapping each node inside a row node loop I get the results I was looking for.
var item = idnode.InnerXml;
XmlNodeList rwList = doc.SelectNodes(String.Format("data/row[#InventoryItemId='{0}']",item));
var rwCount = rwList.Count;
foreach (XmlNode rw in rwList)
{
XmlNodeList msList = id.SelectNodes("measures/measure");
foreach (XmlNode mes in msList)
{
XmlNode msnode = mes.SelectSingleNode("name");
{
var attribute = doc.CreateAttribute("Measure");
attribute.Value = msnode.InnerXml;
if (rwCount > 0)
{
rw.Attributes.Append(attribute);
rwCount--;
}
else
{
XmlNode clonenode = rw.Clone();
clonenode.Attributes.Append(attribute);
nd.AppendChild(clonenode);
}
}
}
}

Get value from node

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=\"день\"]");

Categories