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.