XSLT: Converting a node with a child who doesn'

2019-08-10 22:28发布

I have an XML document I need to generate to integrate with a 3rd party vendor. Their paradigm is that any string elements are set as attributes, while any complex objects are separate nodes with the same rule.

I'm trying to use XSLT to convert our serialized representation of this object into their document format based on their .dtd. Unfortunately, I can't decorate these particular elements with [XmlAttribute] because their value could be null (I'm using XSLT to remove these nodes before sending the request).

Basically, I'm trying to figure out in XSLT how to do the following: "For each child of my element, if that child has no children, convert that element to a property on my element."

I'm working with the following snippet I've found that appears to do what I need:

<!-- Match elements that are parents -->
<xsl:template match="*[*]">
  <xsl:choose>
    <!-- Only convert children if this element has no attributes -->
    <!-- of its own -->
    <xsl:when test="not(@*)">
      <xsl:copy>
        <!-- Convert children to attributes if the child has -->
        <!-- no children or attributes and has a unique name -->
        <!-- amoung its siblings -->
        <xsl:for-each select="*">
          <xsl:choose>
            <xsl:when test="not(*) and not(@*) and
                            not(preceding-sibling::*[name( ) =
                                                     name(current( ))]) 
                            and 
                            not(following-sibling::*[name( ) = 
                                                     name(current( ))])">
              <xsl:attribute name="{local-name(.)}">
                <xsl:value-of select="."/>
              </xsl:attribute>  
            </xsl:when>
            <xsl:otherwise>
              <xsl:apply-templates select="."/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>
      </xsl:copy>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy>
        <xsl:apply-templates/>
      </xsl:copy>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

However, this errors with:

Exception::System.Xml.Xsl.XslTransformException: Attribute and namespace nodes cannot be added to the parent element after a text, comment, pi, or sub-element node has already been added.

My XSLT skills are a bit weak. Any help would be appreciated!

For more info, say the doc looks like this:

<Root>
    <type>Foo</type>
    <includeHTML>Yes</includeHTML>
    <SubRoot>
        <SubSubRoot>
            <ID>2.4</ID>
        </SubSubRoot>
    </SubRoot>
</Root>

I want the result to look like this:

<Root type="foo" includeHTML="Yes">
    <SubRoot>
        <SubSubRoot ID="2.4" />
    </SubRoot>
</Root>

EDIT EDIT: I'm using .NET 4.5.1, XSLT 1.0 only. Doh!

标签: xml xslt
1条回答
可以哭但决不认输i
2楼-- · 2019-08-10 23:18

I am not entirely sure what you are asking, but I'll give it a try.

This stylesheet assumes that elements containing only text nodes are subsumed as an attribute of the parent element - even if text nodes are children of an element, in a sense.

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="/*|*[*]">
      <xsl:copy>
         <xsl:apply-templates select="@*"/>
         <xsl:choose>
            <xsl:when test="*[not(./*) and not(./@*)]">
               <xsl:for-each select="*[not(./*) and not(./@*)]">
                  <xsl:attribute name="{current()/local-name()}">
                    <xsl:value-of select="."/>
                  </xsl:attribute>
               </xsl:for-each>
            </xsl:when>
            <xsl:otherwise/>
         </xsl:choose>                
         <xsl:apply-templates select="*[*]"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="*[not(./*) and not(./@*)]"/>

   <xsl:template match="@*|text()">
      <xsl:copy>
         <xsl:apply-templates/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

Applied to a sample input:

<?xml version="1.0" encoding="utf-8"?>

<root>
   <car nr="1">
      <color>blue</color>
      <speed>210</speed>
   </car>
   <car nr="2">
      <color>red</color>
      <speed>100</speed>
   </car>
</root>

You get the following result:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <car nr="1" color="blue" speed="210"/>
   <car nr="2" color="red" speed="100"/>
</root>

EDIT: Applied to your updated input, I get:

<?xml version="1.0" encoding="UTF-8"?>
<Root type="Foo" includeHTML="Yes">
   <SubRoot>
      <SubSubRoot ID="2.4"/>
   </SubRoot>
</Root>
查看更多
登录 后发表回答