Home > Software design >  how to add multiple subelements into new parent element with xslt transform
how to add multiple subelements into new parent element with xslt transform

Time:06-01

I'm using ExtendedXmlSerializer with C# and one known limitation is adding serializing lists of objects. when doing this it results in a seperate element (the list itself) with the items of the list inside that. Since i'm deserializing an external xml that comes from a seperate application, i cannot control the layout of the xml and thus need to transform the xml before deserializing it in C#.

The transform i have found somewhere else on stackoverflow works fine when adding a single type of element into a new (non-existent) element, but fails when trying to do so with multiple different types.

I have tried to simplify the source xml as much as possible:

<?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>

where the preferred output is like this:

<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>

like i said above, the elements CompileUnitToRemove and MultilingualTextToRemove are there to make ExtendedXmlSerializer work with lists.

the current xslt i have is as follows:

<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>

however, this creates the following 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>

where, as you can see, the elements are duplicated.

I have also tried putting the "xsl:apply-templates" part within it's own "xsl:copy" part, and also within it's own "<xsl:template match="*[local-name(.)='ObjectList']">" but it either results on more duplicates or just skips the one that's listed first.

if anyone can help me figure out what the proper xslt layout needs to be, it'd be much appreciated. I'd also be fine with another workaround for the ExtendedXmlSerializer lists-issue, but i do need ExtendedXmlSerializer for other reasons and using xslt is the recommended solution from the developer.

CodePudding user response:

Does the following template fulfill your requirement?

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