Tench
Tench

Reputation: 527

Looking up key-value pairs in JSON objects from XSLT

I have an XSLT implementation that hyphenates strings following latex patterns, i.e. inserts soft hyphens in strings. I call this function like this:

<xsl:template match="p[lang('en')]/text()">
  <xsl:analyze-string select="." regex="\w+">
   <xsl:matching-substring>
    <xsl:value-of select="fn:hyphenate(., '­', 'en')"/>
   <xsl:non-matching-substring>
    <xsl:value-of select="."/>
   </xsl:non-matching-substring>
  </xsl:analyze-string>
 </xsl:template>

Now, before I call the hyphenate function, I would like to consult a list of exceptions. I'm fairly flexible about the format in which I get the exceptions, but I would also like to use this opportunity to learn how to take advantage of json objects from within XSLT 3.0.

So let's say i have a JSON file called exceptions.json that looks like this:

{
    "en" :{
       "recognizance": "re-cog-ni-zance",
       "reformation": "ref-or-ma-tion",
       "retribution": "ret-ri-bu-tion",
       "table": "ta-ble"
    },

    "de" : {   
      //etc.   
    }
}

(I am not using soft hyphens in the above example because they wouldn't show on Stackoverflow, but that's irrelevant for the question I am asking).

How could I read (and parse?) the contents of the JSON file into a variable called $exceptions, so that I could most efficiently do something like:

  <xsl:template match="p[lang('en')]/text()">
  <xsl:analyze-string select="." regex="\w+">
   <xsl:matching-substring>
    <xsl:choose>
     <xsl:when test="">
      <!--test if matched string is a key in my json object and return the 
          corresponding value. for instance, if the matched substring here is 
          "table", I'd like to return something like 
          <xsl:value-of select="$exceptions['en']['table']"/> , 
          i.e. "ta-ble", using whatever notation may be correct for this 
          kind of thing. -->
     </xsl:when>
     <xsl:otherwise>
      <xsl:value-of select="fn:hyphenate(., '­', 'en')"/>
     </xsl:otherwise>
    </xsl:choose>
    <xsl:non-matching-substring>
     <xsl:value-of select="."/>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
 </xsl:template>

Many thanks in advance!

Upvotes: 0

Views: 170

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 117140

Another option is to convert the JSON to XML, then process it as such.

In your example, this could probably look something like:

XSLT 3.0

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:your="your-namespace-here"
exclude-result-prefixes="#all">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="exceptions" select="json-to-xml(unparsed-text('path/to/exceptions.json'))" />

<xsl:key name="excpt" match="fn:string" use="@key" />

<xsl:mode on-no-match="shallow-copy"/>

<xsl:template match="p">
    <xsl:analyze-string select="." regex="\w+">
        <xsl:matching-substring>
            <xsl:variable name="lookup" select="key('excpt', ., $exceptions)" />
            <xsl:value-of select="if($lookup) then $lookup else your:hyphenate(.)" /> 
        </xsl:matching-substring>
        <xsl:non-matching-substring>
            <xsl:value-of select="."/>
        </xsl:non-matching-substring>
    </xsl:analyze-string>
</xsl:template>

</xsl:stylesheet>

Untested, because you did not provide a reproducible example. Do note that I am using the fn prefix as intended by the W3C Recommendation; you will need to use another prefix for your own function.

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167716

Well, the XPath 3.1 and the XSLT 3.0 specs are online, so you could have found the necessary functions (https://www.w3.org/TR/xpath-functions-31/#func-json-doc, https://www.w3.org/TR/xpath-functions-31/#func-map-contains) and notations (https://www.w3.org/TR/xpath-31/#id-lookup).

Use e.g. <xsl:variable name="exceptions" select="json-doc('exceptions.json')"/>, the you can access e.g. map:contains($exceptions?en, .) to check whether the current word is in the en map.

Example: (reading JSON inline for self-containedness):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all"
  expand-text="yes">

  <xsl:output method="html" indent="yes" html-version="5"/>
  
  <xsl:template match="p[lang('en')]/text()">
  <xsl:analyze-string select="." regex="\w+">
   <xsl:matching-substring>
    <xsl:choose>
     <xsl:when test="map:contains($exceptions?en, .)">
      <xsl:sequence select="$exceptions?en(.)"/>
     </xsl:when>
     <xsl:otherwise>
      <xsl:value-of select="."/>
     </xsl:otherwise>
    </xsl:choose>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
     <xsl:value-of select="."/>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
 </xsl:template>
 
  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:copy>
      <xsl:apply-templates/>
      <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} at {current-dateTime()}</xsl:comment>
    </xsl:copy>
  </xsl:template>
  
  <xsl:param name="json-exceptions" as="xs:string" expand-text="no">
{
    "en" :{
       "recognizance": "re-cog-ni-zance",
       "reformation": "ref-or-ma-tion",
       "retribution": "ret-ri-bu-tion",
       "table": "ta-ble"
    }
}    
  </xsl:param>
  
  <xsl:param name="exceptions" select="parse-json($json-exceptions)"/>

</xsl:stylesheet>

Online fiddle.

Upvotes: 1

Related Questions