0

I'm new to the forum and even (little bit) new to xsl; I need help to solve a little problem with an XML code: please take a look at the structure below.

*********************************
<ProductRevision id="id45" **************>
  <ApplicationRef **************/>
  <UserData *********>
  <UserValue title="title1" value="11111 000 000"/>
  <UserValue title="object_name" value="test name1"/>
  </UserData>
  </ProductRevision>
<ProductRevision id="id50" ***********>
  <ApplicationRef **************/>
  <UserData id="id46">
  <UserValue title="title1" value="22222 000 000"/>
  <UserValue title="object_name" value="test name2"/>
  </UserData>
  </ProductRevision>
*******************************************
<GeneralRelation id="id49" subType="TestType" relatedRefs="#id2 #id45">
  <ApplicationRef **************/>
  <UserData ********>
  <UserValue title="ds5_amont" type="int" value="3"/>
  <UserValue title="ds5_cavities" type="int" value="2"/>
  </UserData>
  </GeneralRelation>
<GeneralRelation id="id49" subType="TestType" relatedRefs="#id2 #id50">
  <ApplicationRef ***********/>
  <UserData **********>
  <UserValue title="ds5_amont" type="int" value="2"/>
  <UserValue title="ds5_cavities" type="int" value="3"/>
  </UserData>
  </GeneralRelation>

As you can see, the ProductRevision nodes contain an id value; this value identifies two corresponding GeneralRelation nodes, which contain the UserValues ds5_amont and ds5_cavities. I use the following piece of .xsl code to display the values of title1 and object_name of all the ProductRevision nodes:

<xsl:for-each select="//plm:ProductRevision[@subType = 'XXXXX']">
  <xsl:variable name="part" select="./plm:UserData/plm:UserValue[@title='object_name']/@value" />
  <xsl:variable name="identnr" select="./plm:UserData/plm:UserValue[@title='title1']/@value" />
<Row>
  <Cell ss:StyleID="s73">
  <Data ss:Type="String">
  <xsl:value-of select="$part"/>
  </Data>
  </Cell>
  <Cell ss:StyleID="s73">
  <Data ss:Type="String">
  <xsl:value-of select="$identnr"/>
  </Data> 
  </Cell> 
</Row> 

Now, for each ProductRevision, I need to display the corresponding ds5_amont and ds5_cavities values, contained in the corresponding nodes identified by the id45 and id50 values. These properties must be printed next to the cells which display the $part and $identnr variables. I've not been able to find the solution so far, any help will be highly appreciated! Thanks!

EDIT

Sorry guys, I cannot learn in 2 seconds how to reproduce an Excel table on your forum. The result should be simply like that: for each Product revision, there should be one row showing object_name****title1****ds5_amont****ds5_cavities Sorry again, hope it is clear enough ;-)

V4N3X
  • 1
  • 3
  • Please try to minimise your example, it's a lot of XML data, and probably most of them are not relevant to your problem. – Superlokkus Feb 21 '19 at 11:13
  • 1
    Can you show an example of output you expect ? – Vebbie Feb 21 '19 at 11:32
  • The table at the bottom can be rendered if you treat it as code in the question editor :) FTFY – tom redfern Feb 21 '19 at 11:42
  • The answer to your questions is to use a **key**. However, since the `relatedRefs` attribute in `GeneralRelation` contains multiple values, you would have to tokenize them first. How to do this depends on which XSLT processor you will be using. – michael.hor257k Feb 21 '19 at 12:01
  • Sorry, I'm not sure about the processor: I can say that the xml code is generated by Teamcenter (Siemens), and the transformation is executed via .xsl stylesheet stored in the system :-( – V4N3X Feb 21 '19 at 12:19
  • See here how to find out: https://stackoverflow.com/questions/25244370/how-can-i-check-which-xslt-processor-is-being-used-in-solr/25245033#25245033 – michael.hor257k Feb 21 '19 at 13:24
  • The processor I use to test is libxslt version 1.0. Since this is absolutely compatible with the Teamcenter system, I guess you could show me an example based on this processor ;-) P.S. thanks a lot! – V4N3X Feb 21 '19 at 13:46

3 Answers3

1

Using the libxslt processor, you can take advantage of the EXSLT str:tokenize() extension function.

Consider the following simplified example:

XML

<root>
  <ProductRevision id="id45">
    <ApplicationRef/>
    <UserData>
      <UserValue title="title1" value="11111 000 000"/>
      <UserValue title="object_name" value="test name1"/>
    </UserData>
  </ProductRevision>
  <ProductRevision id="id50">
    <ApplicationRef/>
    <UserData id="id46">
      <UserValue title="title1" value="22222 000 000"/>
      <UserValue title="object_name" value="test name2"/>
    </UserData>
  </ProductRevision>
  <GeneralRelation id="id49" subType="TestType" relatedRefs="#id2 #id45">
    <ApplicationRef/>
    <UserData>
      <UserValue title="ds5_amont" type="int" value="453"/>
      <UserValue title="ds5_cavities" type="int" value="452"/>
    </UserData>
  </GeneralRelation>
  <GeneralRelation id="id49" subType="TestType" relatedRefs="#id2 #id50">
    <ApplicationRef/>
    <UserData>
      <UserValue title="ds5_amont" type="int" value="502"/>
      <UserValue title="ds5_cavities" type="int" value="503"/>
    </UserData>
  </GeneralRelation>
</root>

XSLT 1.0 + EXSLT

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

<xsl:key name="relation" match="GeneralRelation" use="str:tokenize(@relatedRefs, ' ')" />

<xsl:template match="/root">
    <table>
        <xsl:for-each select="ProductRevision">
            <Row>
                <!-- ProductRevision -->
                <Cell>
                    <xsl:value-of select="UserData/UserValue[@title='object_name']/@value"/>
                </Cell>
                <Cell>
                    <xsl:value-of select="UserData/UserValue[@title='title1']/@value"/>
                </Cell>
                <!-- GeneralRelation -->
                <xsl:variable name="relation" select="key('relation', concat('#', @id))" />
                <Cell>
                    <xsl:value-of select="$relation/UserData/UserValue[@title='ds5_amont']/@value"/>
                </Cell>
                <Cell>
                    <xsl:value-of select="$relation/UserData/UserValue[@title='ds5_cavities']/@value"/>
                </Cell>
            </Row> 
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<table>
  <Row>
    <Cell>test name1</Cell>
    <Cell>11111 000 000</Cell>
    <Cell>453</Cell>
    <Cell>452</Cell>
  </Row>
  <Row>
    <Cell>test name2</Cell>
    <Cell>22222 000 000</Cell>
    <Cell>502</Cell>
    <Cell>503</Cell>
  </Row>
</table>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thanks Michael: I'll test your code right now, then I'll let you know how it works ;-)! – V4N3X Feb 21 '19 at 16:27
  • Sorry, I tested your code in my editor, and it says that .XSL is not valid; using an online editor, the result I have is this: Unable to generate the XML document using the provided XML/XSL input. Cannot find a matching 2-argument function named {http://exslt.org/strings}tokenize() – V4N3X Feb 21 '19 at 16:59
  • @V4N3X There is a reason why I asked which processor you will be using. Your editor, an online editor, and any other environment are irrelevant. What matters is which processor will you be using in the actual production. – michael.hor257k Feb 21 '19 at 17:44
  • I'll give you an answer tomorrow Michael: will import a partially working stylesheet into Teamcenter and see which processor it uses. For today I'm really done, it's the whole day that I struggle with this crap and my brain is blowing away! I will inform you tomorrow ;-) – V4N3X Feb 21 '19 at 18:05
  • Hallo again Michael, so, in the production environment the processor result is Apache Software Foundation, version 1. Hope this is useful ;-) – V4N3X Feb 22 '19 at 04:33
  • That is Xalan, and it supports `str:tokenize()` too (you can verify this using ``). – michael.hor257k Feb 22 '19 at 07:27
  • Hi Michael, sadly my last post was deleted, but I wanted also to tell you about the problem with XPATH: since the xml code to translate is PLMXML, I need to add the "plm:" prefix to all the nodes. This means that the path, from the root, would be "/plm:PLMXML/plm:ProductRevision/plm:UserData/plm:UserValue" and so on. This is causing some problem to your solution; still struggling with the code.... – V4N3X Feb 22 '19 at 07:57
  • A match pattern does not need a full path: `` should suffice. – michael.hor257k Feb 22 '19 at 08:04
  • Hallo again Michael, I think I've found the solution: it differs a bit from your approach, but anyway you gave me a big input to make the trick! Therefore I must say a big THANK YOU! Maybe I could post the code, but I guess the admins would delete it. So, if you want to know how I did, ask the admins! ;-) Thank you again, and if I'm allowed to post the code, just inform me... – V4N3X Feb 22 '19 at 14:43
  • @V4N3X You are allowed to post an **answer** to your question. – michael.hor257k Feb 22 '19 at 17:02
1

You want to process those GeneralRelation elements having the @id value of your current ProductRevision element in the list of theirs @relatedRefs attribute. In XSLT 1.0 you use contains and current functions like this:

/root
  /GeneralRelation
    [contains
       (concat(@relatedRefs,' '),
        concat('#',current()/@id,' ')
       )
    ]

This input:

<root>
    <ProductRevision id="id45">
        <ApplicationRef />
        <UserData>
            <UserValue title="title1" value="11111 000 000" />
            <UserValue title="object_name" value="test name1" />
        </UserData>
    </ProductRevision>
    <ProductRevision id="id50">
        <ApplicationRef />
        <UserData id="id46">
            <UserValue title="title1" value="22222 000 000" />
            <UserValue title="object_name" value="test name2" />
        </UserData>
    </ProductRevision>
    <GeneralRelation id="id49" subType="TestType"
        relatedRefs="#id2 #id45">
        <ApplicationRef />
        <UserData>
            <UserValue title="ds5_amont" type="int" value="453" />
            <UserValue title="ds5_cavities" type="int" value="452" />
        </UserData>
    </GeneralRelation>
    <GeneralRelation id="id49" subType="TestType"
        relatedRefs="#id2 #id50">
        <ApplicationRef />
        <UserData>
            <UserValue title="ds5_amont" type="int" value="502" />
            <UserValue title="ds5_cavities" type="int" value="503" />
        </UserData>
    </GeneralRelation>
</root>

And this transformation

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" />

    <xsl:variable name="vGeneralRelation" 
        select="/root/GeneralRelation" />

    <xsl:template match="text()|GeneralRelation" />
    <xsl:template match="root">
        <table>
            <xsl:apply-templates />
        </table>
    </xsl:template>

    <xsl:template match="ProductRevision">
        <Row>
            <xsl:apply-templates
                select="*|$vGeneralRelation[
                    contains(concat(@relatedRefs,' '),
                        concat('#',current()/@id,' '))]/*" />
        </Row>
    </xsl:template>

    <xsl:template
        match="UserValue[@title[.='object_name' or .='title1' or .='ds5_cavities' or .='ds5_amont']]">
        <Cell>
            <xsl:value-of select="@value" />
        </Cell>
    </xsl:template>
</xsl:stylesheet>

Produce this output

<?xml version="1.0" encoding="utf-8"?>
<table>
   <Row>
      <Cell>11111 000 000</Cell>
      <Cell>test name1</Cell>
      <Cell>453</Cell>
      <Cell>452</Cell>
   </Row>
   <Row>
      <Cell>22222 000 000</Cell>
      <Cell>test name2</Cell>
      <Cell>502</Cell>
      <Cell>503</Cell>
   </Row>
</table>

Check working example at http://xsltransform.net/6qaFCEU

Alejandro
  • 1,882
  • 6
  • 13
0

Thank you all for your contributions. Finally, this is the solution I've used. I guess yours were all very sophisticated and professional solutions, but for a beginner like me, it's necessary to simplify the code, in a way that even the errors are easily identifiable. But I can tell you that I will study a lot on those solutions! First of all, identify the correct ProductRevision nodes by the subType attribute; then, display the two variables in context of the ProductRevision nodes, and store the id in the pid variable:

    <xsl:template match="//plm:ProductRevision[@subType='DS5_PartRevision']">

    <xsl:variable name="part" select="./plm:UserData/plm:UserValue[@title='object_name']/@value" />
    <xsl:variable name="identnr" select="./plm:UserData/plm:UserValue[@title='ds5_s_serien_identnr']/@value" />
    <xsl:variable name="pid" select="@id"/>

    <Row>

        <Cell ss:StyleID="s73">
            <Data ss:Type="String">
                <xsl:value-of select="$part"/>
            </Data>
        </Cell>
        <Cell ss:StyleID="s73">
            <Data ss:Type="String">
                <xsl:value-of select="$identnr"/>
            </Data>  
        </Cell> 

Once the first ProductRevision node is processed, check all the GeneralRelation nodes; for each node, create the variable relref and store a portion of the value of the relatedRefs attribute. Only the value after " #" (space-#) will be stored:

<xsl:for-each select="/plm:PLMXML/plm:GeneralRelation">
            <xsl:variable name="relref" select="substring-after(@relatedRefs,' #')"/>

            <xsl:choose>
                <xsl:when test="$relref=$pid">
                    <Cell ss:StyleID="s73">
                        <Data ss:Type="String">
                            <xsl:value-of select="./plm:UserData/plm:UserValue[@title='ds5_amont']/@value"/>
                        </Data>  
                    </Cell>
                    <Cell ss:StyleID="s73">
                        <Data ss:Type="String">
                            <xsl:value-of select="./plm:UserData/plm:UserValue[@title='ds5_cavities']/@value"/>
                        </Data>  
                    </Cell>
                </xsl:when>
            </xsl:choose>
        </xsl:for-each>

    </Row>

For each GeneralRelation node, compare the values of pid and relref : if those are matching, get the values of the two properties in context of the GeneralRelation node (therefore the ./plm/ notation) and display them in the correct row (of the corresponding ProductRevision).

V4N3X
  • 1
  • 3