views:

57

answers:

2

Hi, All,

Next question: I have "InputDoc" xml document and xslt file in order to transform into other "OutputDoc" xml document. You can find examples xslt and xml documents below.

<?xml version="1.0" encoding="UTF-8"?>
<InputDoc>
  <InputCollection>
    <InputItem>
      <InputValue>Value_1</InputValue>
    </InputItem>
  </InputCollection>
</InputDoc>

<?xml version="1.0" encoding="utf-16"?>
<OutputDoc>
  <OutputElement>
    <OutputItem>
      <OutputValue>Value_1</OutputValue>
    </OutputItem>
    <OutputDescription>Description_1</OutputDescription>
  </OutputElement>
</OutputDoc>

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="//InputCollection">
    <OutputDoc>
      <xsl:for-each select="InputItem">
        <OutputElement>
          <OutputItem>
            <OutputValue><xsl:value-of select="InputValue" /></OutputValue>
          </OutputItem>
          <OutputDescription>
            <xsl:call-template name="InputValue2OutputDescriptionMappings">
              <xsl:with-param name="InputValueParam" select="InputValue" />
            </xsl:call-template>
          </OutputDescription>
        </OutputElement>
      </xsl:for-each>
    </OutputDoc>
  </xsl:template>

  <xsl:template name="InputValue2OutputDescriptionMappings">
    <xsl:param name="InputValueParam" />
        <xsl:choose>
            <xsl:when test="$InputValueParam='Value_1'">Description_1</xsl:when>
            <xsl:when test="$InputValueParam='Value_2'">Description_2</xsl:when>
            <xsl:otherwise></xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

It works. But It would be nice if output xml document contained just "OutputElement" nodes which contains values into "InputValue2OutputDescriptionMappings", i.e. if value of "OutputDescription" node is empty then "OutputElement" node won't be included into "OutputDoc".

How I can do it using XSL transformation above?

+1  A: 

You have to get the result of the mapping first, THEN test it for empty and omit the <OutputDescription> tag, as in:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="//InputCollection">
    <OutputDoc>
      <xsl:for-each select="InputItem">
        <OutputElement>
          <OutputItem>
            <OutputValue><xsl:value-of select="InputValue" /></OutputValue>
          </OutputItem>
          <xsl:variable name="desc">
            <xsl:call-template name="InputValue2OutputDescriptionMappings">
              <xsl:with-param name="InputValueParam" select="InputValue" />
            </xsl:call-template>
          </xsl:variable>
          <xsl:if test="string-length($desc/text())">
            <OutputDescription><xsl:value-of select="$desc"/></OutputDescription>
          </xsl:if>
        </OutputElement>
      </xsl:for-each>
    </OutputDoc>
  </xsl:template>

  <xsl:template name="InputValue2OutputDescriptionMappings">
    <xsl:param name="InputValueParam" />
    <xsl:choose>
      <xsl:when test="$InputValueParam='Value_1'">Description_1</xsl:when>
      <xsl:when test="$InputValueParam='Value_2'">Description_2</xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

However, here is a much more XSL-esque version that stores the mappings in a variable to dispense with the mapping template, and uses "push" processing (apply-templates) instead of "pull" processing.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="//InputCollection">
    <OutputDoc>
      <xsl:apply-templates select="InputItem"/>
    </OutputDoc>
  </xsl:template>

  <xsl:variable name="mapping">
    <map input="Value_1" output="Description_1"/>
    <map input="Value_2" output="Description_2"/>
  </xsl:variable>

  <xsl:template match="InputItem">
    <OutputElement>
      <OutputItem>
        <OutputValue><xsl:value-of select="InputValue" /></OutputValue>
      </OutputItem>
      <xsl:variable name="input" select="InputValue/text()"/>
      <xsl:variable name="desc" select="$mapping/map[@input=$input]/@output"/>
      <xsl:if test="$desc">
        <OutputDescription><xsl:value-of select="$desc"/></OutputDescription>
      </xsl:if>
    </OutputElement>
  </xsl:template>

</xsl:stylesheet>
Jim Garrison
@Jim Garrison: In XSLT 1.0, non-empty variables are Result Tree Fragment and you can't use `/` operator with them. So, you are running the stylesheet with XLST 2.0 processor or with a non-standar XSLT 1.0 processor.
Alejandro
The same result as into xslt below. <?xml version="1.0" encoding="UTF-8"?> <InputDoc> <InputCollection> <InputItem> <InputValue>Value_3</InputValue> </InputItem> </InputCollection> </InputDoc>Output xml should be empty after transformation if value ("Value_3" in example) doesn't exist into variable list, but output document contains nodes with "Value_3" value.
Maxim Polishchuk
@Alejandro: XSL processor is Saxon 6.5 under Oxygen/XML. I didn't use EXSLT.
Jim Garrison
+1  A: 

This is an standard XSLT 1.0 stylesheet optimized with key():

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:key name="description" match="description" use="@value"/>
    <xsl:variable name="map">
        <description value="Value_1">Description_1</description>
        <description value="Value_2">Description_2</description>
    </xsl:variable>
    <xsl:template match="InputDoc">
        <OutputDoc>
            <xsl:apply-templates/>
        </OutputDoc>
    </xsl:template>
    <xsl:template match="InputCollection">
        <xsl:variable name="me" select="."/>
        <xsl:for-each select="document('')">
            <xsl:if test="key('description',$me/InputItem/InputValue)">
                <OutputElement>
                    <xsl:apply-templates select="$me/*"/>
                    <xsl:apply-templates select="key('description',$me/InputItem/InputValue)"/>
                </OutputElement>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="InputItem">
        <OutputItem>
            <xsl:apply-templates/>
        </OutputItem>
    </xsl:template>
    <xsl:template match="InputValue">
        <OutputValue>
            <xsl:apply-templates/>
        </OutputValue>
    </xsl:template>
    <xsl:template match="description">
        <OutputDescription>
            <xsl:apply-templates/>
        </OutputDescription>
    </xsl:template>
</xsl:stylesheet>

Result:

<OutputDoc>
    <OutputElement>
        <OutputItem>
            <OutputValue>Value_1</OutputValue>
        </OutputItem>
        <OutputDescription>Description_1</OutputDescription>
    </OutputElement>
</OutputDoc>

Note: "Pattern matching". key() with multiple documents (this case, the stylesheet).

Edit: Miss strip OutputElement for non matching value request, sorry.

Now, this input:

<InputDoc>
  <InputCollection>
    <InputItem>
      <InputValue>Value_3</InputValue>
    </InputItem>
  </InputCollection>
</InputDoc>

Output:

<OutputDoc></OutputDoc>

Note: This is all template driven. So, you can apply more logic even to description content.

Alejandro
Thank you very much !!!
Maxim Polishchuk
<?xml version="1.0" encoding="UTF-8"?><InputDoc> <InputCollection> <InputItem> <InputValue>Value_3</InputValue> </InputItem> </InputCollection></InputDoc>Output xml should be empty after transformation because "Value_3" value doesn't exist into variable list, but output document contains nodes with "Value_3".
Maxim Polishchuk
@Maxim Polishchuk: Reply amended. Sorry.
Alejandro