0

I have a parts manual that is composed of a set of several XML files that are segmented by different part groups. Each part grouping contains several tables of information related to individual parts, including a unique part number. Each individual part is contained as a single row in the table. I've added an empty column to these tables that will point out if sale of a part is restricted by either a "yes" or "no" value once I run it through my transform.

I wrote an XSLT transformation to find a part number in a lookup file, compare it to a part number in the document I'm processing, and when they match, populate the empty column with either "yes" or "no" based on what is listed in the lookup XML document.

The part numbers are unique. However, some parts are used with multiple part groups. While the parts manual is composed of several XML files, the lookup file I'm using is based off of a BOM. So, it's one big XML document that contains all the parts for every group.

The XML document I'm processing looks like this:

<reference>
    <title>Part Group A</title>
    <refbody>
        <section>
            <image href="partGroupA.svg"/>
        </section>
        <simpletable>
            <sthead>
                <stentry>Annotation</stentry>
                <stentry>Part Name</stentry>
                <stentry>Restricted?</stentry>
                <stentry>Part Description</stentry>
                <stentry>Part Number</stentry>
                <stentry>Quantity</stentry>
                <stentry>Comment</stentry>
            </sthead>
            <strow>
                <stentry translate="no" props="annotation">1</stentry>
                <stentry translate="no" props="part-name">SomePart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">SomePart</stentry>
                <stentry translate="no" props="part-number">1234567-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Some comment</stentry>
            </strow>
            <strow>
                <stentry translate="no" props="annotation">2</stentry>
                <stentry translate="no" props="part-name">AnotherPart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">AnotherPart</stentry>
                <stentry translate="no" props="part-number">2345678-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Another comment</stentry>
            </strow>
            ...
        </simpletable>
    </refbody>
</reference>

The look up XML document contains this:

...
<strow>
  <stentry props="part-section">Part Group A</stentry>
  <stentry props="part-name">SomePart</stentry>
  <stentry props="part-number">1234567-00-A</stentry>
  <stentry props="part-restrict">Yes</stentry>
</strow>
<strow>
   <stentry props="part-section">Part Group A</stentry>
   <stentry props="part-name">AnotherPart</stentry>
   <stentry props="part-number">2345678-00-A</stentry>
   <stentry props="part-restrict">No</stentry>
</strow>
...
<strow>
  <stentry props="part-section">Part Group B</stentry>
  <stentry props="part-name">SomePart</stentry>
  <stentry props="part-number">1234567-00-A</stentry>
  <stentry props="part-restrict">No</stentry>
</strow>
...

My XSLT transformation looks like this:

<xsl:output method="xml" encoding="UTF-8" indent="yes" doctype-system="reference.dtd" doctype-public="-//OASIS//DTD DITA Reference//EN"/>
<xsl:strip-space elements="*"/>

<xsl:key name="part-number" match="strow" use="stentry[@props='part-number']" />

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

<xsl:template match="stentry[@props='part-restrict']">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:variable name="matching-part" select="key('part-number', ../stentry[@props='part-number'], document('pm_restrict_redo-2.xml'))" />
        <xsl:choose>
            <xsl:when test="$matching-part">
                <xsl:value-of select="$matching-part/stentry[@props='part-restrict']"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

This works to a point. Since in some cases, the part number is used multiple times in different part groups (for different systems, subsystems, etc.) and is listed multiple times in the BOM lookup, my transform ends up listing "yes" or "no" for each occurrence of the part. The resulting XML looks like this:

<strow>
        <stentry translate="no" props="annotation">1</stentry>
        <stentry translate="no" props="part-name">SomePart</stentry>
        <stentry translate="no" props="part-restrict">yes no</stentry>
        <stentry translate="yes" props="part-desc">SomePart</stentry>
        <stentry translate="no" props="part-number">1234567-00-A</stentry>
        <stentry translate="no" props="quantity">1</stentry>
        <stentry translate="yes" props="comment">Some comment</stentry>
</strow>

What I'm trying to do is transform XML related to a specific part group and limit the listing of a "yes" or "no" value to what is captured for that part group. Instead, what I'm getting is all the "yes" and "no" values for every occurrence of the part in the BOM.

Any help appreciated.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jason Davis
  • 65
  • 1
  • 8
  • "*The way to arrive at the value that relates to only the context node is eluding me.*" Well, what *is* the correct context? – michael.hor257k Apr 11 '15 at 00:28
  • @michael.hor257k Yeah, that is part of the problem right? How is the script supposed to know what the correct context is? For that, I've been trying to narrow down the correct context by use of the part-section node in the lookup file. This, at least, narrows the scope down to part numbers that are only members of a specific System => Subsystem => Part grouping. However, I have not yet been able to figure how to limit the template outputting that specific node set. – Jason Davis Apr 11 '15 at 02:21
  • Please, *edit* the question and provide the wanted output. Also, please, explain the rules that the transformation should implement. Without this important information, the question is ambiguous and you could receive many different answers none of which provides the solution to the actual problem. – Dimitre Novatchev Apr 11 '15 at 03:07
  • "*I have not yet been able to figure how to limit the template outputting that specific node set.*" You don't want to limit the template; you want to limit the scope of the **key**. Instead of letting it match any `strow` element anywhere in the lookup document, you need to narrow down the match pattern to include only `strow` nodes in a particular branch of the document. I cannot advise you how to do that, because I don't see the document and don't know how it's structured. – michael.hor257k Apr 11 '15 at 03:29
  • @DimitreNovatchev I have edited the post and tried to encapsulate what I'm trying to do more accurately. Please let me know if I failed to address your request. – Jason Davis Apr 12 '15 at 18:38
  • @JasonDavis, I cannot see any "Part Group" in your source XML document, and thus no solution can be produced if this data isn't shown. Please, provide a data-complete (but short) source XML document. – Dimitre Novatchev Apr 12 '15 at 18:42
  • @michael.hor257k That makes sense and I tried to do that by doing some matching against the part group (part-section in the lookup). However, I couldn't arrive at a construction that would allow me to do that. I've edited my post to add clarity. So, hopefully that will provide more insight. – Jason Davis Apr 12 '15 at 18:49
  • @DimitreNovatchev I have edited the post to provide a more complete example. Each part group in the parts manual is represented by a single XML. The part group for which a part is associated in the BOM is represented by props="part-section" nodes. – Jason Davis Apr 12 '15 at 19:13
  • You need to tell us how would you do this manually. You have an entry with part number "1234567-00-A". You look for this part number in the lookup document, and you find two entries matching the given part number - one with part-section "Part Group A", and the other with "Part Group B". How do *you* know which one of these two to pick? – michael.hor257k Apr 12 '15 at 19:21
  • To do this manually, I would need to look in the XML for a specific part group (e.g. Part Group A) and find all the part numbers. I would then need to look in the loopup file and find the same part numbers there. I would then take only the `props="part-restrict"` values for part numbers with a sibling `props="part-section"` node that matches up with Part Group A. – Jason Davis Apr 12 '15 at 19:40

2 Answers2

1

Here is an immediate fix for your transformation. For convenience I have inlined the lookup document and this makes the transformation seem long, but it is actually short and straightforward:

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

 <xsl:key name="part-number" match="strow" 
 use="concat(stentry[@props='part-section'], '|', stentry[@props='part-number'])" />

 <xsl:variable name="vLookup">
  <lookup>
        <strow>
            <stentry props="part-section">Part Group A</stentry>
            <stentry props="part-name">SomePart</stentry>
            <stentry props="part-number">1234567-00-A</stentry>
            <stentry props="part-restrict">Yes</stentry>
        </strow>
        <strow>
            <stentry props="part-section">Part Group A</stentry>
            <stentry props="part-name">AnotherPart</stentry>
            <stentry props="part-number">2345678-00-A</stentry>
            <stentry props="part-restrict">No</stentry>
        </strow>
        <strow>
            <stentry props="part-section">Part Group B</stentry>
            <stentry props="part-name">SomePart</stentry>
            <stentry props="part-number">1234567-00-A</stentry>
            <stentry props="part-restrict">No</stentry>
        </strow>
    </lookup>
 </xsl:variable>  

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

 <xsl:template match="stentry[@props='part-restrict']">
    <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:variable name="matching-part" 
             select="key('part-number', 
                         concat(ancestor::refbody[1]/preceding-sibling::title[1], 
                         '|',
                         ../stentry[@props='part-number']), 
                     $vLookup)" />
         <xsl:sequence select=
           "($matching-part/stentry[@props='part-restrict'], 
             .[empty($matching-part)])/text()"/>
    </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided source XML document:

<reference>
    <title>Part Group A</title>
    <refbody>
        <section>
            <image href="partGroupA.svg"/>
        </section>
        <simpletable>
            <sthead>
                <stentry>Annotation</stentry>
                <stentry>Part Name</stentry>
                <stentry>Restricted?</stentry>
                <stentry>Part Description</stentry>
                <stentry>Part Number</stentry>
                <stentry>Quantity</stentry>
                <stentry>Comment</stentry>
            </sthead>
            <strow>
                <stentry translate="no" props="annotation">1</stentry>
                <stentry translate="no" props="part-name">SomePart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">SomePart</stentry>
                <stentry translate="no" props="part-number">1234567-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Some comment</stentry>
            </strow>
            <strow>
                <stentry translate="no" props="annotation">2</stentry>
                <stentry translate="no" props="part-name">AnotherPart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">AnotherPart</stentry>
                <stentry translate="no" props="part-number">2345678-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Another comment</stentry>
            </strow>
        </simpletable>
    </refbody>
    <title>Part Group B</title>
    <refbody>
        <section>
            <image href="partGroupB.svg"/>
        </section>
        <simpletable>
            <sthead>
                <stentry>Annotation</stentry>
                <stentry>Part Name</stentry>
                <stentry>Restricted?</stentry>
                <stentry>Part Description</stentry>
                <stentry>Part Number</stentry>
                <stentry>Quantity</stentry>
                <stentry>Comment</stentry>
            </sthead>
            <strow>
                <stentry translate="no" props="annotation">1</stentry>
                <stentry translate="no" props="part-name">SomePart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">SomePart</stentry>
                <stentry translate="no" props="part-number">1234567-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Some comment</stentry>
            </strow>
            <strow>
                <stentry translate="no" props="annotation">2</stentry>
                <stentry translate="no" props="part-name">AnotherPart</stentry>
                <stentry translate="no" props="part-restrict"></stentry>
                <stentry translate="yes" props="part-desc">AnotherPart</stentry>
                <stentry translate="no" props="part-number">2345678-00-A</stentry>
                <stentry translate="no" props="quantity">1</stentry>
                <stentry translate="yes" props="comment">Another comment</stentry>
            </strow>
        </simpletable>
    </refbody>
</reference>

the wanted, correct result is produced:

<reference>
   <title>Part Group A</title>
   <refbody>
      <section>
         <image href="partGroupA.svg"/>
      </section>
      <simpletable>
         <sthead>
            <stentry>Annotation</stentry>
            <stentry>Part Name</stentry>
            <stentry>Restricted?</stentry>
            <stentry>Part Description</stentry>
            <stentry>Part Number</stentry>
            <stentry>Quantity</stentry>
            <stentry>Comment</stentry>
         </sthead>
         <strow>
            <stentry translate="no" props="annotation">1</stentry>
            <stentry translate="no" props="part-name">SomePart</stentry>
            <stentry translate="no" props="part-restrict">Yes</stentry>
            <stentry translate="yes" props="part-desc">SomePart</stentry>
            <stentry translate="no" props="part-number">1234567-00-A</stentry>
            <stentry translate="no" props="quantity">1</stentry>
            <stentry translate="yes" props="comment">Some comment</stentry>
         </strow>
         <strow>
            <stentry translate="no" props="annotation">2</stentry>
            <stentry translate="no" props="part-name">AnotherPart</stentry>
            <stentry translate="no" props="part-restrict">No</stentry>
            <stentry translate="yes" props="part-desc">AnotherPart</stentry>
            <stentry translate="no" props="part-number">2345678-00-A</stentry>
            <stentry translate="no" props="quantity">1</stentry>
            <stentry translate="yes" props="comment">Another comment</stentry>
         </strow>
      </simpletable>
   </refbody>
   <title>Part Group B</title>
   <refbody>
      <section>
         <image href="partGroupB.svg"/>
      </section>
      <simpletable>
         <sthead>
            <stentry>Annotation</stentry>
            <stentry>Part Name</stentry>
            <stentry>Restricted?</stentry>
            <stentry>Part Description</stentry>
            <stentry>Part Number</stentry>
            <stentry>Quantity</stentry>
            <stentry>Comment</stentry>
         </sthead>
         <strow>
            <stentry translate="no" props="annotation">1</stentry>
            <stentry translate="no" props="part-name">SomePart</stentry>
            <stentry translate="no" props="part-restrict">No</stentry>
            <stentry translate="yes" props="part-desc">SomePart</stentry>
            <stentry translate="no" props="part-number">1234567-00-A</stentry>
            <stentry translate="no" props="quantity">1</stentry>
            <stentry translate="yes" props="comment">Some comment</stentry>
         </strow>
         <strow>
            <stentry translate="no" props="annotation">2</stentry>
            <stentry translate="no" props="part-name">AnotherPart</stentry>
            <stentry translate="no" props="part-restrict"/>
            <stentry translate="yes" props="part-desc">AnotherPart</stentry>
            <stentry translate="no" props="part-number">2345678-00-A</stentry>
            <stentry translate="no" props="quantity">1</stentry>
            <stentry translate="yes" props="comment">Another comment</stentry>
         </strow>
      </simpletable>
   </refbody>
</reference>

Explanation:

The wanted strow elements from the lookup document are identified by a composite-key comprised of two parts: the part-number and the group-name. Thus, both of these values must participate in the use= attribute of <xsl:key> in order to identify the wanted strow.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • +1. This also worked like I would expect and I thought it a very interesting approach. Thank you for the help. – Jason Davis Apr 13 '15 at 16:16
  • Can you kindly explain why it is more advantages to include the lookup document inline? I accepted the other answer because it only slightly built on the existing stylesheet. Having the lookup inline would mean I would need to update the stylesheet if I needed to regenerate the lookup. – Jason Davis Apr 13 '15 at 17:47
  • @JasonDavis, I didn't propose that you should inline the lookup document. I only inlined it for convenience, in order not to have to refer to an additional, *external* document in the solution. The solution is the same, regardless whether you use an inlined or external lookup document. The only change you need to make in the above code is to use: `` – Dimitre Novatchev Apr 13 '15 at 18:48
  • Right, that subtle nuance was lost on me as I am a bit of an amateur but seems fairly obvious now. One more question. I notice the use of xsl:sequence. What role does that play in this transform? – Jason Davis Apr 14 '15 at 15:33
  • @JasonDavis, You are using XSLT 2.0 and it is more "xslt-2.0"-ly to use predominantly `` over ``. In the case when strings are output, the results from using both instructions are the same, however, here I have one-lined what in the original was 8-lines ``. I am outputting a sequence of the `text()` children of two elements, only one of which can exist: `$matching-part/stentry` or the current node `.` when `$matching-part` is the empty sequence. This is a short and better equivalent of the `` and also of the XPath 2.0 if-then-else expression – Dimitre Novatchev Apr 14 '15 at 16:00
  • @JasonDavis See also: http://stackoverflow.com/questions/29524975/xslt-tokenizing-template-to-italicize-and-bold-xml-element-text/29537101#29537101 – michael.hor257k Apr 14 '15 at 18:46
  • JasonDavis, See this: http://deviq.com/don-t-repeat-yourself There is a whole section titled "Suspect Conditionals". And it recommends: "Wherever possible, refactor these conditionals using well-known design patterns". See also this presentation (slide 43 is dedicated to DRY): http://downloads.academy.telerik.com/svn/high-quality-code/2014/Lectures/16.%20SOLID%20Principles%20and%20DRY/SOLID%20and%20Other%20Principles.pdf – Dimitre Novatchev Apr 15 '15 at 02:55
1

To do this manually, I would need to look in the XML for a specific part group (e.g. Part Group A)

If by that you mean the part group that appears in the title element (in the part of the XML document that you have not shown us until now), then you need to make two changes in your XSLT stylesheet:

1.Change the key definition to:

<xsl:key name="part-number" match="strow" use="concat(stentry[@props='part-section'], '|', stentry[@props='part-number'])" />

2.Change the definition of the $matching-part variable to:

<xsl:variable name="matching-part" select="key('part-number', concat(ancestor::reference/title, '|', ../stentry[@props='part-number']), document('pm_restrict_redo-2.xml'))" />
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • You were right to admonish me for not including a complete example initially. This works perfectly. Thanks for the help – Jason Davis Apr 13 '15 at 15:59