簡體   English   中英

C#將xml屬性轉換為元素

[英]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>

說明

  1. 身份規則/模板“按原樣”復制每個節點。

  2. 身份規則由兩個模板覆蓋 - 一個匹配任何不是文檔頂部元素的元素的任何屬性,另一個匹配任何Value元素。

  3. 模板匹配屬性(第一個覆蓋模板)代替屬性創建一個與匹配屬性具有相同本地名稱和值的元素。 此外,元素名稱與文檔頂部元素所屬的名稱空間放在同一名稱空間中(這樣可以避免使用xmlns="" )。

  4. 匹配任何Value元素的模板復制它並處理其子樹(后代節點),然后處理其屬性。 通過這種方式,從屬性生成的元素將成為兄弟,而不是Value元素的子元素。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM