1

Input xml data looks like this:

<Item key="">
  <Adress>
    <Template key="01,09-10,21">
      <Channel>
        <Template key="1-3"/>
      </Channel>
    </Template>
  </Adress>
</Item> 

First, I wrote an Identity template that copies all the tags and their attributes sequentially to the output xml (the input and output file are the same). Then I renamed the 'Template' tag to 'Item' so the stylesheet looks like this:

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

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Template">
        <Item>
            <xsl:apply-templates select="@*|node()"/>
        </Item>
    </xsl:template>

</xsl:stylesheet>

How do I write the style file so that the output xml looks like this:

<Item key="">
  <Adress>
    <Item key="01">
      <Channel>
        <Item key="1"/>
        <Item key="2"/>
        <Item key="3"/>
      </Channel>
    </Item>
    <Item key="09">
      <Channel>
        <Item key="1"/>
        <Item key="2"/>
        <Item key="3"/>
      </Channel>
    </Item> 
    <Item key="10">
      <Channel>
        <Item key="1"/>
        <Item key="2"/>
        <Item key="3"/>
      </Channel>
    </Item>
    <Item key="21">
      <Channel>
        <Item key="1"/>
        <Item key="2"/>
        <Item key="3"/>
      </Channel>
    </Item>
  </Adress>
</Item>

XSLT version 1.0, Visual Studio 2017

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
eliasum
  • 135
  • 6
  • 1
    While asking an XSLT question you need to provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example): (1) Input XML. (2) Your logic, and XSLT that tries to implement it. (3) Desired output, based on the sample XML in the #1 above. (4) XSLT processor and its conformance with the XSLT standards: 1.0, 2.0, or 3.0. All within the question, no images. – Yitzhak Khabinsky Feb 01 '22 at 02:45
  • You are asking multiple questions at once. Your input needs to be tokenized and some tokens are ranges that need to be enumerated. For tokenizing, see: https://stackoverflow.com/a/31593480/3016153. For enumerating, see: https://stackoverflow.com/a/66568957/3016153. – michael.hor257k Feb 01 '22 at 06:28
  • P.S. Visual Studio us not an XSLT processor. In order to get the most relevant help. always state which XSLT processor you will be using. Then we'll know which extensions you can use (some would be very useful here). See here how to identify your processor: https://stackoverflow.com/a/25245033/3016153 – michael.hor257k Feb 01 '22 at 06:32
  • XSLT Version:1.0, XSLT Processor:Microsoft – eliasum Feb 01 '22 at 08:16

1 Answers1

1

You said you wanted XSLT 1.0, but your example stylesheet was XSLT 2.0 (which you need for regex based functions)

This is one possible XSLT 2+ solution that first tokenize() by the , and then evaluates whether the values are a range of numbers that need expanding.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="Template">
        <xsl:variable name="context" select="*"/>
        <xsl:copy>
            <!-- first split the comma separated values -->
            <xsl:for-each select="tokenize(@key, ',')">
                <!-- determine whether there is a range of key values or a single value -->
                <xsl:variable name="keys" as="item()*">  
                    <xsl:choose>
                        <xsl:when test="matches(., '\d+-\d+')">
                            <xsl:variable name="range" select="tokenize(., '-')"/>
                            <xsl:sequence select="xs:integer(head($range)) to xs:integer(tail($range))"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:sequence select="."/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:variable>
     
                <xsl:for-each select="$keys">
                    <Item key="{xs:integer(.)}">
                        <xsl:apply-templates select="$context"/>
                    </Item>
                </xsl:for-each>
                
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

The only thing that isn't clear is whether or not you would want the leading zeros preserved for the first level and not the second. This output turns them all into integers and will have Item/@key values without a leading zero. Additional logic could be applied, but would need to know when to preserve and format with leading zeros and when not to do so.

Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
  • sorry, I'm working in Visual Studio 2017/19 and just now found out that only XSLT 1.0 works. Also, the fact that the style file specifies version 2.0 is my mistake, because I thought regex was supported regardless of the XSLT version. Is it possible to solve this problem in XSLT 1.0 version in order to work in Visual Studio 2017/19? Yes, leading zeros are needed where they occur in the value of the key attribute in the input xml file. – eliasum Feb 01 '22 at 04:55
  • Microsoft haven't improved their XML tools for about 20 years. You need to think about using third parties. – Michael Kay Feb 01 '22 at 09:08