0
votes

Here is an XML file, representing a list of items and their priority levels on a scale of 1-3. When the file was created, the priority levels were not set:

<root>
<section number="1">    
<group number="1.1">
  <item date="today" priority="Undefined"><title>Item1</title></item>
  <item date="today" priority="Undefined"><title>Item2</title></item>
  <item date="yesterday" priority="Undefined"><title>Item3</title></item>
</group>
  <group number="1.2">
    <item date="tomorrow" priority="Undefined"><title>Item4</title></item>
    <item date="today" priority="Undefined"><title>Item5</title></item>
    <item date="yesterday" priority="Undefined"><title>Item5</title></item>
  </group>
</section>
</root>

I was given the list of priorities by item later. They came to me just in a regular text file, but I can put them into any format necessary, including XML. Just to show as an example:

<priorities>
<item1 p="1">
<item2 p="3">
<item3 p="1">
<item4 p="2">
<item5 p="3">
<item6 p="3">
</priorities>

I'm trying to write an XSL transform that sets the priority attributes for each item appropriately.

In my mind, it should work like this (I already have step 1):

  1. Use the identity transform
  2. Create some kind of data structure ('Item1':'1', 'Item2':'3', ...)
  3. Create a template that:
    • Matches the ITEM element
    • Checks the TITLE
    • Looks up that title in the data structure
    • Sets the priority level appropriately (I think I know how to do this part)

Alternatively, I thought of another way:

  1. Use the identity transform (already written)
  2. Create 3 data structures:
    • $P1 := ('Item1', 'Item2')
    • $P2 := ('Item4')
    • $P3 := ('Item2', 'Item5', 'Item6')
  3. Create 3 templates, one for each priority level. For example, the P1 template:
    • Matches the ITEM element
    • Checks the TITLE
    • Checks whether the TITLE is a member of $P1:
      • If so, set the priority level appropriately
      • If not, do nothing

NOTE: This is a distilled example; I am trying to create a solution that can handle an XML file with approximately 600 item elements with very long strings as the TITLEs.

I've been Googling and searching SO and have been left wondering if such a thing is even possible.

Using Oxygen 16.

3
It's difficult to understand your description. Where exactly are the priority values coming from? Can you supply them as another XML document? Also, can you use XSLT 2.0?michael.hor257k
I updated the post to clarify that I can get the priority values into any format that I want, including XML. Also, I am using Oxygen 16 so yes I can use XSLT 2.0. Thanks for forcing the clarification.mdslup

3 Answers

1
votes

Your base logic is sound. It all depends on what your second input (title-to-priority mapping) looks like.

Assuming it is XML itself then you could pass it into an <xsl:param> of your stylesheet and use it as a reference. If you build the mapping document like this:

<root>
    <title priority="1">Item1</title>
    <!-- etc -->
</root>

then your XSLT could look like this:

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

    <!-- pass in your mapping nodelist from the host environment -->    
    <xsl:param name="priorityByTitle" />

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

    <xsl:template match="@priority">
        <xsl:copy-of select="$priorityByTitle//title[. = current()/../title]/@priority" />
    </xsl:template>
</xsl:transform>

Many host environments allow passing actual nodelist objects to the XSLT processor's parameters in some way. Alternatively you can of course use a file and document().

<xsl:variable name="priorityByTitle" select="document('mapping.xml')" />
1
votes

If you can supply the priorities as an XML document, then I would suggest using a key to lookup the values from there - especially if you can use XSLT 2.0 where lookup across documents is trivial.

For example, assuming an XML document such as:

priorities.xml

<priorities>
    <priority title="Item1">1</priority>
    <priority title="Item2">3</priority>
    <priority title="Item3">1</priority>
    <priority title="Item4">2</priority>
    <priority title="Item5">3</priority>
    <priority title="Item6">3</priority>
</priorities>

the following stylesheet:

XSLT 2.0

<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:strip-space elements="*"/>

<xsl:key name="priority-by-title" match="priority" use="@title" />
<xsl:param name="path-to-priorities" select="'path/to/priorities.xml'"/>

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

<xsl:template match="itemx/@priority">
    <xsl:attribute name="priority">
        <xsl:value-of select="key('priority-by-title', ../title, document($path-to-priorities))" />
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

would return:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <section number="1">
      <group number="1.1">
         <item date="today" priority="1">
            <title>Item1</title>
         </item>
         <item date="today" priority="3">
            <title>Item2</title>
         </item>
         <item date="yesterday" priority="1">
            <title>Item3</title>
         </item>
      </group>
      <group number="1.2">
         <item date="tomorrow" priority="2">
            <title>Item4</title>
         </item>
         <item date="today" priority="3">
            <title>Item5</title>
         </item>
         <item date="yesterday" priority="3">
            <title>Item5</title>
         </item>
      </group>
   </section>
</root>

Note that XML is case-sensitive: item1 does not match Item1.


To restrict the lookup only to items that have a matching entry in the priorities file, you can use:

<xsl:template match="item[key('priority-by-title', title, document($path-to-priorities))]/@priority">
    <xsl:attribute name="priority">
        <xsl:value-of select="key('priority-by-title', ../title, document($path-to-priorities))" />
    </xsl:attribute>
</xsl:template>
0
votes

To replace the n'th Undefined in your primary input with the priority of the n'th item in your priorities file, use

<xsl:template match="@priority[.='Undefined']">
  <xsl:variable name="pos" select="count(../preceding::*[@priority='Undefined']"/>
  <xsl:attribute name="priority" select="(doc('priorities.xml')//priority)[$pos]"/>
</xsl:template>

in conjunction with a suitable identity template.

That's O(n^2) and I'm sure one could do better if it wasn't midnight on a Saturday evening.