[英]C# to convert xml attributes to elements
我需要將所有屬性轉換為XML文件中的節點,但根節點中的屬性除外。
我在這里找到了一個類似的問題: xquery將屬性轉換為標簽 ,但我需要在C#中進行轉換。
我還在這里找到了一個使用XLS的可能解決方案: 將屬性值轉換為元素 。 但是,該解決方案實際上將節點名稱更改為屬性名稱並刪除該屬性。
我需要使用屬性的名稱和值創建新的兄弟節點並刪除屬性,但仍保留包含屬性的節點。
給出以下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>
我需要生成以下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>
我編寫了以下方法來創建一個新的XML對象,從source
元素的屬性創建新元素:
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;
}
此方法生成以下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>
輸出有兩個直接問題:
1)根元素中的屬性丟失。
2)'Value'元素的屬性被創建為子元素而不是兄弟元素。
為了解決第一個問題,我嘗試將source
元素的屬性分配給result
元素,但是這導致了“ 前綴''無法從''重新定義為''到http://www.something.com '相同的開始元素標記 “錯誤。 我注釋掉了導致錯誤的代碼。
為了解決第二個問題,我嘗試將從屬性創建的元素添加到source.Parent
元素,但這導致新元素根本不顯示。
我還重寫了直接在source
元素上操作的方法:
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();
}
}
}
重寫產生了以下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>
重寫確實解決了保留根元素屬性的第一個問題。 它還部分地解決了第二個問題,但產生了一個新問題:新元素具有空白的xmlns屬性。
使用此方法將Xml屬性轉換為xml節點:
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");
}
您可以構建一個擴展方法來展平每個元素:
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);
}
}
你可以這樣使用:
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()));
}
這會產生你所指示的輸出,除了xsi:schemaLocation
屬性,這是我發現的問題。 它選擇一個默認的命名空間名稱( p1
),但最終它可以工作。
產生以下內容:
<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>
這個XSLT轉換 :
<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>
當應用於提供的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>
產生完全想要的,正確的結果 :
<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>
說明 :
身份規則/模板“按原樣”復制每個節點。
身份規則由兩個模板覆蓋 - 一個匹配任何不是文檔頂部元素的元素的任何屬性,另一個匹配任何Value
元素。
模板匹配屬性(第一個覆蓋模板)代替屬性創建一個與匹配屬性具有相同本地名稱和值的元素。 此外,元素名稱與文檔頂部元素所屬的名稱空間放在同一名稱空間中(這樣可以避免使用xmlns=""
)。
匹配任何Value
元素的模板復制它並處理其子樹(后代節點),然后處理其屬性。 通過這種方式,從屬性生成的元素將成為兄弟,而不是Value
元素的子元素。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.