view refs.xsl @ 29:87ed04a0fde2

recursion in place, need to check for names in ranges
author Henry S. Thompson <ht@markup.co.uk>
date Tue, 11 Apr 2017 14:33:14 +0100
parents c56a2e6990bd
children 16eff0d30d4d
line wrap: on
line source

<?xml version='1.0'?>
<!DOCTYPE doc SYSTEM "../../../lib/xml/xsl.dtd" >
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" xmlns:s="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:e="http://markup.co.uk/excel" exclude-result-prefixes="xs s e xf" xmlns="http://markup.co.uk/excel" xmlns:xf="http://www.w3.org/2005/xpath-functions">
 <xsl:param name="sheet-number"/>
 <xsl:param name="xlDir"/>

  <xsl:variable name="pat1">("[^"]*")|(\{[^}]+})|(,)|([^=\-+*/();:,.$&lt;>^!]+(?:\.[^=\-+*/();:,.$&lt;>^!]+)*\()|([)])|(^=|\()|((?:'[^']+')|(?:\[[0-9]+\][^!]*))|(\$?[A-Z]+\$?[0-9]+)|([a-zA-Z_\\][a-zA-Z0-9._]*)|(.)</xsl:variable>
 <xsl:param name="pat" select="$pat1"/><!-- xsl:param for refinement debugging by passing in the pattern -->
 
 <xsl:variable name="workbook" select="document(concat($xlDir,'/workbook.xml'))/*"/>
 <xsl:variable name="sheet-name" select="$workbook/s:sheets/s:sheet[@sheetId=$sheet-number]/@name"/>
 
 <xsl:function name="e:lookup" as="xs:string*">
  <xsl:param name="name" as="xs:string" required="yes"/>
  <xsl:variable name="defn" select="$workbook/s:definedNames/s:definedName[@name=$name]"/>
  <xsl:sequence select="let $prefix := concat($sheet-name,'!')
                   return if ($defn and
                              starts-with($defn,$prefix))
                           then substring-after($defn,$prefix)
                           else ()"/>
 </xsl:function>

 <xsl:function name="e:tokenise" as="array(xs:string*)*">
  <!-- Tokenise a formula, recursively wrt variables -->
  <xsl:param name="formula" as="xs:string" required="yes"/>
  <xsl:sequence select="
    let $tokens := analyze-string($formula,$pat)/xf:match/xf:group
     return if ($tokens[@nr=(7,8,9)])
             then 
              let $n := count($tokens),
                  $vars := for $i in (1 to $n)
                     return if ($tokens[$i][@nr=9] and
                                not($tokens[$i - 1][@nr=10 and
                                .=(':','!')]) and
                                not($tokens[$i + 1][@nr=10 and .=':']))
                             then string($tokens[$i])
                             else (),
                  $defns := for $var in $vars return e:lookup($var),
                  $recur := for $sub in $defns 
                              return if ($sub) then e:tokenise($defns) else (),
                  $singles := for $i in (1 to $n)
                     return if ($tokens[$i][@nr=8] and
                                not($tokens[$i - 1][@nr=10 and
                                .=(':','!')]) and
                                not($tokens[$i + 1][@nr=10 and .=':']))
                             then translate($tokens[$i],'$','')
                             else (),
                  $ranges := for $i in (1 to count($tokens))
                     return if ($tokens[$i][@nr=10 and .=':' and
                                not($i gt 2 and
                                    $tokens[$i - 2][@nr=10 and .='!'])])
                             then translate(concat($tokens[$i - 1],':',
                                                   $tokens[$i + 1]),'$','')
                             else (),
                  $externals := for $i in (1 to count($tokens))
                     return if ($tokens[$i][@nr=7])
                             then 
                              let $bit := concat($tokens[$i],'!',
                                                 translate($tokens[$i + 2],
                                                           '$',''))
                               return if ((($i+3) le $n) and
                                          $tokens[$i + 3][@nr=10 and .=':'])
                                       then concat($bit,':',
                                                   translate($tokens[$i + 4],
                                                             '$',''))
                                       else $bit
                             else ()
                  return [($singles,for $a in $recur return $a?1),
                          ($ranges,for $a in $recur return $a?2),
                          ($externals,for $a in $recur return $a?3)]
             else ()"/>
 </xsl:function>

 <xsl:template match="/">
  <refs sheetName="{$sheet-name}"><xsl:apply-templates select="//s:c"/></refs>
 </xsl:template>
 
 <xsl:template match="s:c[s:f]">
  <xsl:variable name="tokens" select="e:tokenise(s:f/.)"/>
  <xsl:if test="@r='xxx'"><xsl:message>|</xsl:message>
  </xsl:if>
  <xsl:if test="count($tokens)>0">
   <xsl:variable name="singles" select="$tokens?1"/>
   <!-- Note that we don't bother to treat external ranges as ranges,
          since we're not going to try to detect cross-document refs -->
   <xsl:variable name="ranges" select="$tokens?2"/>
   <xsl:variable name="externals" select="$tokens?3"/>
   <ref c="{@r}">
    <xsl:for-each select="distinct-values($singles)">
     <s><xsl:value-of select="."/></s>
    </xsl:for-each>
    <xsl:for-each select="distinct-values($ranges)">
     <r><xsl:value-of select="."/></r>
    </xsl:for-each>
    <xsl:for-each select="distinct-values($externals)">
     <e><xsl:value-of select="."/></e>
    </xsl:for-each>
   </ref></xsl:if>
 </xsl:template>
 
 <xsl:template match="s:c"/>
</xsl:stylesheet>