如何使用 xslt 转换将多个子元素添加到新的父元素中

how to add multiple subelements into new parent element with xslt transform

我在 C# 中使用 ExtendedXmlSerializer,一个已知的限制是添加序列化对象列表。当这样做时,它会产生一个单独的元素(列表本身),其中包含列表的项目。 由于我正在反序列化来自单独应用程序的外部 xml,因此我无法控制 xml 的布局,因此需要在 C# 中反序列化之前转换 xml。

我在 Whosebug 的其他地方找到的转换在将单一类型的元素添加到新的(不存在的)元素中时工作正常,但在尝试使用多种不同类型时失败。

我已尝试尽可能简化源代码xml:

<?xml version="1.0" encoding="utf-8"?>
<Document>
  <Engineering version="V16" /> <!-- some other elements besides Engineering as well -->
  <SW.Blocks.FB ID="0">
    <AttributeList>
      <AutoNumber>true</AutoNumber>
      <!--more elements including where "Member" is eventually -->
    </AttributeList>
    <ObjectList>
      <MultilingualText ID="1" CompositionName="Comment"/>
      <SW.Blocks.CompileUnit ID="D" CompositionName="CompileUnits"/>
      <SW.Blocks.CompileUnit ID="26" CompositionName="CompileUnits"/>
      <SW.Blocks.CompileUnit ID="3F" CompositionName="CompileUnits"/>
      <SW.Blocks.CompileUnit ID="58" CompositionName="CompileUnits"/>
      <MultilingualText ID="71" CompositionName="Title"/>
    </ObjectList>
  </SW.Blocks.FB>
</Document>

首选输出如下:

<Document>
  <Engineering version="V16" />
  <!-- some other elements besides Engineering as well -->
  <SW.Blocks.FB ID="0">
    <AttributeList>
      <AutoNumber>true</AutoNumber>
      <!--more elements including where "Member" is eventually -->
    </AttributeList>
    <ObjectList>
      <CompileUnitToRemove>
        <SW.Blocks.CompileUnit ID="D" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="26" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="3F" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="58" CompositionName="CompileUnits" />
      </CompileUnitToRemove>
      <MultilingualTextToRemove>
        <MultilingualText ID="1" CompositionName="Comment" />
        <MultilingualText ID="71" CompositionName="Title" />
      </MultilingualTextToRemove>
    </ObjectList>
  </SW.Blocks.FB>
</Document>

就像我上面说的,元素 CompileUnitToRemove 和 MultilingualTextToRemove 可以使 ExtendedXmlSerializer 与列表一起工作。

我目前的xslt如下:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*"/>

<!-- copy all elements over, except what falls into other template -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<!-- if local name (namespace independent) is 'Member': -->
<xsl:template match="*[local-name(.)='Member']">
<!-- store namespace of Member in variable-->
 <xsl:variable name="ns" select="namespace-uri()" />
    <xsl:copy>
        <xsl:apply-templates select="@*|node()[not(self::*[local-name(.)='SubElement'])]"/>
        <!-- copy everything (except Subelements) over and create a new element "SubElements" with same nameSpace as parent (Member) -->
        <xsl:element name="SubElements" namespace="{$ns}">
        <!-- copy all Subelements over into new element -->
            <xsl:apply-templates select="*[local-name(.)='SubElement']"/>        
        </xsl:element>
    </xsl:copy>
</xsl:template>

<!-- works the same as above, but trying with 2 items within ObjectList: -->
<xsl:template match="*[local-name(.)='ObjectList']">
 <xsl:variable name="ns" select="namespace-uri()" />
 <xsl:copy>
    <xsl:apply-templates select="@*|node()[not(self::*[local-name(.)='SW.Blocks.CompileUnit'])]"/>
        <xsl:element name="CompileUnitToRemove" namespace="{$ns}">
            <xsl:apply-templates select="*[local-name(.)='SW.Blocks.CompileUnit']"/>        
        </xsl:element>
        
        <xsl:apply-templates select="@*|node()[not(self::*[local-name(.)='MultilingualText'])]"/>
        <xsl:element name="MultilingualTextToRemove" namespace="{$ns}">
            <xsl:apply-templates select="*[local-name(.)='MultilingualText']"/>        
        </xsl:element>
    </xsl:copy>
</xsl:template>
</xsl:stylesheet>

但是,这会创建以下 xml:

<Document>
  <Engineering version="V16" />
  <!-- some other elements besides Engineering as well -->
  <SW.Blocks.FB ID="0">
    <AttributeList>
      <AutoNumber>true</AutoNumber>
      <!--more elements including where "Member" is eventually -->
    </AttributeList>
    <ObjectList>
      <MultilingualText ID="1" CompositionName="Comment" />
      <MultilingualText ID="71" CompositionName="Title" />
      <CompileUnitToRemove>
        <SW.Blocks.CompileUnit ID="D" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="26" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="3F" CompositionName="CompileUnits" />
        <SW.Blocks.CompileUnit ID="58" CompositionName="CompileUnits" />
      </CompileUnitToRemove>
      <SW.Blocks.CompileUnit ID="D" CompositionName="CompileUnits" />
      <SW.Blocks.CompileUnit ID="26" CompositionName="CompileUnits" />
      <SW.Blocks.CompileUnit ID="3F" CompositionName="CompileUnits" />
      <SW.Blocks.CompileUnit ID="58" CompositionName="CompileUnits" />
      <MultilingualTextToRemove>
        <MultilingualText ID="1" CompositionName="Comment" />
        <MultilingualText ID="71" CompositionName="Title" />
      </MultilingualTextToRemove>
    </ObjectList>
  </SW.Blocks.FB>
</Document>

如您所见,其中的元素是重复的。

我也试过将“xsl:apply-templates”部分放在它自己的“xsl:copy”部分,以及它自己的“" 但它要么导致更多重复,要么只是跳过第一个列出的。

如果有人能帮我弄清楚正确的 xslt 布局需要是什么,我将不胜感激。 对于 ExtendedXmlSerializer 列表问题,我也可以使用另一种解决方法,但出于其他原因我确实需要 ExtendedXmlSerializer 并且使用 xslt 是开发人员推荐的解决方案。

以下模板是否满足您的要求?

<xsl:template match="ObjectList">
  <ObjectList>
    <CompileUnitToRemove>
      <xsl:apply-templates select="SW.Blocks.CompileUnit"/>
    </CompileUnitToRemove>
    <MultilingualTextToRemove>
      <xsl:apply-templates select="MultilingualText"/>
    </MultilingualTextToRemove>
  </ObjectList>
</xsl:template>