xsl-list
[Top] [All Lists]

Re: [xsl] Better "Report Map" Function?

2018-10-06 09:49:43
I wonder if this is really what you want?

You haven't declared a return type for the function, so there's going to be no 
conversion applied, which means it's going to output a sequence of interspersed 
text and element nodes. That's probably fine if the function call always 
appears within xsl:value-of, but it's not very convenient if, for example, you 
want to call it as an argument of contains() or substring(). I would have 
thought a function that returned a string would be more useful.

And a function that only handles map values that are maps, elements, document 
nodes, single strings, single booleans, or single integers doesn't feel very 
general-purpose.

But you're right that there's probably nothing off-the-shelf that gives you 
exactly what you want. For many purposes (e.g. diagnostic output) the 
serialize() function with method=adaptive will be adequate, but if you want 
something better then you have to do it yourself.

Incidentally, sorting the keys of a map like this will fail if the keys include 
non-ordered values such as QNames, of if they include a mixture of strings and 
numbers. If you want to be sure the sort won't fail, convert keys to strings 
before sorting.

Michael Kay
Saxonica

On 6 Oct 2018, at 14:33, Eliot Kimber ekimber(_at_)contrext(_dot_)com 
<xsl-list-service(_at_)lists(_dot_)mulberrytech(_dot_)com> wrote:

I needed a way to report the contents of maps as nicely-formatted strings 
using XSLT 3 and Saxon (Saxon's built-in serialization of maps did not really 
satisfy me).

Here's my solution:

 <xsl:function name="local:report-map">
   <xsl:param name="map" as="map(*)"/>
   <xsl:param name="level" as="xs:integer"/>

   <xsl:variable name="indent" as="xs:string?" 
     select="(for $i in 1 to $level return '  ') => string-join()"
   />
   <xsl:text>&#x0a;{$indent}{{</xsl:text>
   <xsl:for-each select="map:keys($map) => sort()">
     <xsl:variable name="key" select="."/>
     <xsl:variable name="value" select="map:get($map, $key)"/>
     <xsl:text>&#x0a;{$indent}  "{$key}" : </xsl:text>
     <xsl:choose>
       <xsl:when test="$value instance of map(*)">
         <xsl:text> Map:</xsl:text>
         <xsl:sequence select="local:report-map($value, $level + 3)"/>
       </xsl:when>
       <xsl:when test="$value instance of element()">
         <xsl:sequence select="$value"/>
       </xsl:when>
       <xsl:when test="$value instance of document-node()">
         <xsl:text> document-node(): {name($value/*)} - 
"{document-uri($value)}"</xsl:text>
       </xsl:when>
       <xsl:when test="$value instance of xs:string or $value instance of 
xs:boolean or $value instance of xs:integer">
         <xsl:text>"{$value}"</xsl:text>
       </xsl:when>
       <xsl:otherwise>
         <xsl:text>unhandled value</xsl:text>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:for-each>
   <xsl:text>&#x0a;{$indent}}}</xsl:text>
 </xsl:function>

Which produces output like this:

{
  "baseType" : "map"
  "doc" :  document-node(): map - 
"file:/Users/ekimber/workspace/paccar/repo/paccar/sample_data/test-project/calibration-data/MX11_NA/calibrationSLMP_MX11_NA.ditamap"
  "engineFamilies" : "10.8M01"
  "key" : ""
  "relpath" : "calibration-data/MX11_NA/calibrationSLMP_MX11_NA.ditamap"
  "type" : "map"
}

I'm happy with the result but wondering if there's not a more 
efficient/cleaner/more elegant way to do the same thing?

Cheers,

E.
--
Eliot Kimber
http://contrext.com


--~----------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
EasyUnsubscribe: http://lists.mulberrytech.com/unsub/xsl-list/1167547
or by email: xsl-list-unsub(_at_)lists(_dot_)mulberrytech(_dot_)com
--~--

<Prev in Thread] Current Thread [Next in Thread>