-1

As a preface, I have very little knowledge of XSLT-1.0. I feel as though what I am trying to accomplish is beyond that of a beginner.

Through other members of the community here, I have been able to come up with the following stylesheet:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/LaborTaskInterface" >
    <xsl:copy>
        <xsl:for-each select="LaborTask/ltOverride">
            <xsl:copy>
                <xsl:for-each-group select="../@* | @*" group-by="name()">
                    <xsl:for-each select="current-group()">
                        <xsl:element name="{name()}{if (position() > 1) then concat('.', position()) else ''}">
                            <xsl:value-of select="." />
                        </xsl:element>
                    </xsl:for-each>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

I have scoured other posts within the community over the years and I haven't found anything that I would claim is comparable.

I do however believe that XSLT-1.0 uses Muenchian Grouping. I am not however sure of how to apply that to the above stylesheet.

I have this XSLT-1.0 which converts the included XML(seen below) from attribute-centric to element-centric:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="@*" >
    <xsl:element name="{name()}">
        <xsl:value-of select="."/>
    </xsl:element>
</xsl:template>

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

</xsl:stylesheet>

I believe through applying the Muenchian method, I may be able to imitate what the XSLT-2.0 above is doing, I am just unaware of how to apply it.

What would be my key in the following:

<?xml version="1.0" encoding="UTF-8"?>
<LaborTaskInterface>
      <LaborTask thing1="a" thing2="c" thing3="d" thing4="e" thing5="f" 
      thing6="g" thing7="h" thing8="i" thing9="j">
            <ltOverride unit_id="1" value="1" thing2="k" thing3="c" thing4="d" thing10="o"/>
            <ltOverride unit_id="2" value="1" thing2="l" thing3="c" thing4="d" thing11="p"/>
            <ltOverride unit_id="3" value="1" thing2="m" thing3="c" thing4="d" thing12="q"/>
            <ltOverride unit_id="4" value="1" thing2="n" thing3="c" thing4="d" thing13="r"/>
      </LaborTask>
</LaborTaskInterface>

When applying the XSLT-1.0 above to my XML, I get the following:

<LaborTaskInterface>
   <LaborTask>
      <thing1>a</thing1>
      <thing2>c</thing2>
      <thing3>d</thing3>
      <thing4>e</thing4>
      <thing5>f</thing5>
      <thing6>g</thing6>
      <thing7>h</thing7>
      <thing8>i</thing8>
      <thing9>j</thing9>
      <ltOverride>
         <unit_id>1</unit_id>
         <value>1</value>
         <thing2>k</thing2>
         <thing3>c</thing3>
         <thing4>d</thing4>
         <thing10>o</thing10>
      </ltOverride>
      <ltOverride>
         <unit_id>2</unit_id>
         <value>1</value>
         <thing2>l</thing2>
         <thing3>c</thing3>
         <thing4>d</thing4>
         <thing11>p</thing11>
      </ltOverride>
      <ltOverride>
         <unit_id>3</unit_id>
         <value>1</value>
         <thing2>m</thing2>
         <thing3>c</thing3>
         <thing4>d</thing4>
         <thing12>q</thing12>
      </ltOverride>
      <ltOverride>
         <unit_id>4</unit_id>
         <value>1</value>
         <thing2>n</thing2>
         <thing3>c</thing3>
         <thing4>d</thing4>
         <thing13>r</thing13>
      </ltOverride>
   </LaborTask>
</LaborTaskInterface>

As you can see, my LaborTask is not repeated with each ItOverride and the "empty" nodes are not represented within each of instance of LaborTask or ItOverride.

Can someone assist in explaining the inputs of the Muenchian Grouping method and assist me in understanding the differences to the above stylesheet that I may need to apply to achieve the conversion of this stylesheet to XSLT-1.0 or how to marry my XSLT-1.0 with the included above XSLT-2.0?

Edit, I have further attempted to utilize Muenchian Method

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

<xsl:key name="thing1_value" match="ItOverride" use="@thing1" />

<xsl:template match="LaborTask">
        <AllLaborTask>
            <xsl:apply-templates select="ItOverride[generate-id(.)=generate-id(key('thing1',@thing1)[1])]"/>
        </AllLaborTask>
</xsl:template>

<xsl:template match="ItOverride">
            <xsl:for-each select="key('task_id', @task_id)">
                <ItOverride>
            <thing1><xsl:value-of select="@thing1" /></thing1>
                    <unit_id><xsl:value-of select="@unit_id" /></unit_id>
                    <thing2><xsl:value-of select="@thing2" /></thing2>
                    <value><xsl:value-of select="@value" /></value>
            <thing3><xsl:value-of select="@thing3" /></thing3>
            <thing4><xsl:value-of select="@thing4" /></thing4>
            <thing5><xsl:value-of select="@thing5" /></thing5>
            <thing6><xsl:value-of select="@thing6" /></thing6>
            <thing7><xsl:value-of select="@thing7" /></thing7>
            <thing8><xsl:value-of select="@thing8" /></thing8>
            <thing9><xsl:value-of select="@thing9" /></thing9>
            <thing10><xsl:value-of select="@thing10" /></thing10>
            <thing11><xsl:value-of select="@thing11" /></thing11>
            <thing12><xsl:value-of select="@thing12" /></thing12>
            <thing13><xsl:value-of select="@thing13" /></thing13>
                </ItOverride>
            </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

This is the output that the above provides:

  <AllLaborTask/>

For my full XML (in which I have more than one LaborTask, it just repeats in this manner:

<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>
<AllLaborTask/>

Any and all help is greatly appreciated! TYIA!

When running this XSLT with the msxml edits, I am getting an error in both Access and the online converter tool I am using to debug:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0"
  xmlns:msxml="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="msxml">

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

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:key name="group" match="ltOverride/*" use="concat(generate-id(..), '|', name())"/>
  
  <xsl:template match="LaborTask">
    <xsl:variable name="temp-data">
      <xsl:apply-templates select="ltOverride" mode="attributes-to-elements"/>
    </xsl:variable>
    <xsl:for-each select="mxsxml:node-set($temp-data)/ltOverride">
      <xsl:copy>
        <xsl:for-each select="*[generate-id() = generate-id(key('group', concat(generate-id(..), '|', name()))[1])]">
          <xsl:for-each select="key('group', concat(generate-id(..), '|', name()))">
            <xsl:variable name="index">
              <xsl:if test="position() > 1">
                <xsl:value-of select="concat('.', position())"/>
              </xsl:if>
            </xsl:variable>
            <xsl:element name="{name()}{$index}">
              <xsl:value-of select="."/>
            </xsl:element>
          </xsl:for-each>
        </xsl:for-each>        
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template match="ltOverride" mode="attributes-to-elements">
    <xsl:copy>
      <xsl:apply-templates select="../@* | @*" mode="attributes-to-elements"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="@*" mode="attributes-to-elements">
    <xsl:element name="{name()}">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>
Justin W
  • 5
  • 4
  • 1
    Please ask a **specific** question about a difficulty you encountered when trying to accomplish this. Otherwise it looks like you're just looking for someone to write your code for you. – michael.hor257k Feb 18 '22 at 18:58
  • I have attempted to elaborate on the topic with the edits I have made. I appreciate any explanation and help you can provide. The main issues I am running into is that Access is not loading all headers within my xml, only the nodes located within the first element-node grouping. I believe I know what my final XML should look like, however the two walls I am up against are the fact that 1. Access uses .NET which only supports XSLT-1.0 and 2. it isn't reading my file correctly. The only options I see are passing null nodes through to all instances of ItOverride in cases where there is no value. – Justin W Feb 18 '22 at 19:12
  • 1
    Try yourself before asking such a broad question that looks like a request for code to be written for you. See [ask] and [Why is "Can someone help me?" not an actual question?](https://meta.stackoverflow.com/q/284236/290085) – kjhughes Feb 18 '22 at 19:16
  • Do you happen to have any recommendations for beginners to understand how to formulate and write in XSLT-1.0? Microsoft probably doesn't support the upgrades because the audience isn't very large. – Justin W Feb 18 '22 at 19:49
  • Also, can anyone explain to me which portions of the transform are responsible for the attribute -> element transform and which portions are responsible for the organization? – Justin W Feb 18 '22 at 19:51
  • I think you're in the right direction with Muenchian Grouping. Try it out by yourself http://www.jenitennison.com/xslt/grouping/muenchian.html. If you can't get it to work post the code you came up with and a sample input if you want someone to be able to help you out. – Sebastien Feb 18 '22 at 20:27
  • Thank you for that link! I believe it provides a great explanation and I can hopefully marry it to the already provided solution above. Do you happen to know which pieces of the above xslt-2.0 make it version 2 vs version 1? I just need to know which pieces of the transform are still "kosher" for version 1. Thank you in advance! – Justin W Feb 18 '22 at 20:36
  • It's the xsl:for-each-group that's not available in XSLT 1.0. – Sebastien Feb 18 '22 at 20:54
  • I'm lost on what the key would be for the Muenchian method. I think it would be a version of LaborTask in conjunction with one of the attributes. Can you confirm if that makes sense? I'm just having a difficult time grasping the concept and need someone that can explain it in Lamen's terms. – Justin W Feb 18 '22 at 21:11
  • I have added a further attempt to get to the correct XSLT-1.0. I believe I am close on the Muenchian Grouping, but not sure where to go next. Again, all help is greatly appreciated! – Justin W Feb 18 '22 at 21:45

1 Answers1

0

I think for XSLT 1 it is easier to flatten first and then to group:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0"
  xmlns:exsl="http://exslt.org/common"
  exclude-result-prefixes="exsl">

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

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:key name="group" match="ltOverride/*" use="concat(generate-id(..), '|', name())"/>
  
  <xsl:template match="LaborTask">
    <xsl:variable name="temp-data">
      <xsl:apply-templates select="ltOverride" mode="attributes-to-elements"/>
    </xsl:variable>
    <xsl:for-each select="exsl:node-set($temp-data)/ltOverride">
      <xsl:copy>
        <xsl:for-each select="*[generate-id() = generate-id(key('group', concat(generate-id(..), '|', name()))[1])]">
          <xsl:for-each select="key('group', concat(generate-id(..), '|', name()))">
            <xsl:variable name="index">
              <xsl:if test="position() > 1">
                <xsl:value-of select="concat('.', position())"/>
              </xsl:if>
            </xsl:variable>
            <xsl:element name="{name()}{$index}">
              <xsl:value-of select="."/>
            </xsl:element>
          </xsl:for-each>
        </xsl:for-each>        
      </xsl:copy>
    </xsl:for-each>
  </xsl:template>
  
  <xsl:template match="ltOverride" mode="attributes-to-elements">
    <xsl:copy>
      <xsl:apply-templates select="../@* | @*" mode="attributes-to-elements"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="@*" mode="attributes-to-elements">
    <xsl:element name="{name()}">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

XSLT 1 to do that needs an extension function like exsl:node-set, depending on the XSLT processor Access uses (earlier versions of MS Office used MSXML, there you would have to use xmlns:msxml="urn:schemas-microsoft-com:xslt" and mxsxml:node-set instead of exsl:node-set) you might have to adapt that, you say Access uses .NET, so XslCompiledTransform in .NET does support exsl:node-set.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thank you Martin! For the mxsxml, is anything required other than replacing those instances in the included XSLT above with the mxsxml portions. I'm not 100% sure what you mean when you say adapt that. – Justin W Feb 21 '22 at 12:56
  • If you use the MSXML 3/4/5/6 (C/C++ based, Microsoft XSLT 1 processor) or .NET XslTransform, replace `xmlns:exsl="http://exslt.org/common"` with `xmlns:msxml="urn:schemas-microsoft-com:xslt"` and `exclude-result-prefixes="exsl"` with `exclude-result-prefixes="msxml"` and `exsl:node-set` with `mxsxml:node-set`. For .NET's XslCompiledTransform the posted code using `exsl:node-set` should work. – Martin Honnen Feb 21 '22 at 13:16
  • Is there a difference between msxml and msxsl? And mxsxml? – Justin W Feb 21 '22 at 13:24
  • I posted the exact edits I made following your above suggestion and am getting an error. I did try a few alternatives, one being changing all instances of exsl to msxsl and the error I got was: "Cannot find a matching 1-argument function named {urn:schemas-microsoft-com:xslt}node-set()" – Justin W Feb 21 '22 at 13:30
  • I got it!! The most odd thing, but for some reason, Access's built in XML import and transform wouldn't take it. I however created a macro that saves it as a new XML (in the desired format) and it works. I have one last question, but is there a way that I could add blank subnodes for instances that may not contain a value in parent nodes? – Justin W Feb 21 '22 at 14:17
  • @JustinW, I would suggest you raise the new issue as a separate question with the necessary details of demonstrative input output samples and what you have tried and how it failed. – Martin Honnen Feb 21 '22 at 14:28