3

I would like to automate the elements based on input file expression.

My input file looks like

<?xml version="1.0" encoding="UTF-8"?>
<mappings>
    <mapping inputContext="InputRoot" outputContext="outputRoot">
        <input>InputParent/InputChild/InputSubChild</input>
        <output>OutputParent/OPChild</output>
    </mapping>
</mappings>

Based on above XML I had created the below XSLT

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns="http://www.testmapping.org/mapping">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <xsl:variable name="outputCtxt" select="mappings/mapping/output"/>
        <xsl:call-template name="contextGenerator">
            <xsl:with-param name="contextPath" select="$outputCtxt"/>
        </xsl:call-template>
    </xsl:template>
    <xsl:template name="contextGenerator">
        <xsl:param name="contextPath" as="xs:string?"/>
        <xsl:variable name="currentContext" select="substring-before($contextPath,'/')"/>
        <xsl:variable name="subContext" select="substring-after($contextPath,'/')"/>
        <xsl:element name="{$currentContext}">
            <xsl:call-template name="contextGenerator">
                <xsl:with-param name="contextPath" select="$subContext"/>
            </xsl:call-template>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

I am expecting the output with below format

<outputRoot>
   <OutputParent>
      <OPChild></OPChild>
   </OutputParent>
</outputRoot>

when I am trying to transform based on the input i am ending up with Expected QName error. Can I have the suggestions to address this issue.

3 Answers 3

3

The contextGenerator template is not properly splitting and recursing. (There is no / in the argument to contextGenerator on the second call, so the splits fail.)

Adding the following to the template helps show the problem:

<xsl:message>
    [<xsl:value-of select="$currentContext"/>] 
    [<xsl:value-of select="$subContext"/>]
</xsl:message>

Output:

[OutputParent] 
[OPChild]
[] 
[]

The following replacement template produces the correct output:

<xsl:template name="contextGenerator">
    <xsl:param name="contextPath" as="xs:string?"/>
    <xsl:choose>
        <xsl:when test="contains($contextPath, '/')">
            <xsl:element name="{substring-before($contextPath, '/')}">
                <xsl:variable name="subContext" 
                              select="substring-after($contextPath, '/')"/>
                <xsl:if test="$subContext">
                    <xsl:call-template name="contextGenerator">
                        <xsl:with-param name="contextPath" select="$subContext"/>
                    </xsl:call-template>
                </xsl:if>
            </xsl:element>
        </xsl:when>
        <xsl:otherwise>
            <xsl:element name="{$contextPath}"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Result:

<OutputParent>
   <OPChild/>
</OutputParent>
Sign up to request clarification or add additional context in comments.

1 Comment

+1 good answer. @OP, your recursive template has no termination condition, so if you hadn't gotten an error, you would have had an infinite recursion (well actually a stack overflow...)
1

Using XSLT 2.0 allows for a shorter, easier and more efficient solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vOutNames" select=
  "tokenize(/*/*/output, '/')"/>

 <xsl:template match="/">
  <xsl:sequence select="my:gen($vOutNames)"/>
 </xsl:template>

 <xsl:function name="my:gen" as="element()?">
  <xsl:param name="pNames" as="xs:string*"/>

  <xsl:if test="$pNames[1]">
   <xsl:element name="{$pNames[1]}">
    <xsl:sequence select="my:gen($pNames[position() >1])"/>
   </xsl:element>
  </xsl:if>
 </xsl:function>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<mappings>
    <mapping inputContext="InputRoot" outputContext="outputRoot">
        <input>InputParent/InputChild/InputSubChild</input>
        <output>OutputParent/OPChild</output>
    </mapping>
</mappings>

the wanted, correct result is produced:

<OutputParent>
   <OPChild/>
</OutputParent>

3 Comments

If <mappings> <mapping inputContext="InputRoot" outputContext="outputRoot"> <input>InputParent/InputChild/InputSubChild</input> <output>OutputParent/OPChild</output> </mapping> </mappings>
@LaxmikanthSamudrala: Sorry, what are you trying to say?
Sorry, that was my mistake on other XSLT problem I was working on; but I had resolved that.
0

In XML, element names have to conform to the lexical structure of QNames. You are trying to create an element called InputParent/InputChild/InputSubChild. This contains characters (/) that are not allowed in QNames.

To fix the error, you could substitute _ for /... depending on what your requirements are.

E.g.

    <xsl:element name="{replace($currentContext, '/', '_')}">

3 Comments

I am using substring-before($contextPath,'/'); which will pull the first occurrence without / part of the result
The expected output does not contain slashes.
@lwburk: quite right. I had read the XSLT too fast. I wondered why you hadn't already posted this answer, since you'd been working on the question a while before I got here. Now I know!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.