InsertFalsePrecision1.xsl

From TEIWiki
Jump to navigation Jump to search

TEI datatypes permit times on the value= attribute of the various date & time elements to be expressed to any precision (year, month, day, hour, minute, second, or decimal fraction of a second). However, because the W3C XML Schema datatypes for times do not permit precision to the hour or minute, there exists software that does not handle these values. E.g., such software will reject "13:30" but accept "13:30:00". This styleseet (as well as the better version that only works with XSLT 2 processors) converts times that are precise to only the minute or hour to times that are precise to the second.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
  add_false_time_precision_4v1.xslt

  This style-sheet reads in an XML file (presumably a TEI P5 one, but
  any other that has similar value= attributes of <date> and <time>
  elements would work) and writes out the same file with any right-
  truncated times padded with zeroes.

  Known limitations:
  * Only handles times, not dateTimes. The XSLT 2 version
    (add_false_time_precision_4v2.xslt) handles both.
  * The "00" should probably be a parameter, so users could use some
    other value (e.g., "30") if they wanted.
  * Obviously, the file cannot be converted back, as there would be no
    way for a stylesheet to know whether any particular ":00" was
    added by this stylesheet or actually represents a time precise to
    the minute or second.

  I don't claim for a moment that the method I've used here is the
  best in any sense of the word "best", only that it is the one this
  novice XSLTer thought of. In particular, the nesting of setting the
  timezone separator and parsing out the time from the timezone might
  be better if it were in some other order, and likely could benefit
  from abstracting out a separate template.
  
  Written 2005-09-25 by Syd Bauman
  copyleft 2005 by Syd Bauman and the Text Encoding Initiative Consortium
-->
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:tei="http://www.tei-c.org/ns/1.0" >

  <!-- explicitly indicate our output is XML -->
  <xsl:output method="xml"/>
  
  <!-- Generic copy-everything template -->                                                                                 
  <xsl:template match="@*|*|processing-instruction()|comment()">                                                            
    <xsl:copy>                                                                                                              
      <xsl:apply-templates select="*|@*|text()|processing-instruction()|comment()"/>                                        
    </xsl:copy>                                                                                                             
  </xsl:template>                                                                                                           
  
  <xsl:template match="time[@value]|tei:time[@value]|date[@value]|tei:date[@value]">
    <!-- strip any leading or trailing whitespacce -->
    <xsl:variable name="value">
      <xsl:value-of select="normalize-space(@value)"/>
    </xsl:variable>
    <!-- First, ascertain whether or not there is a time zone designation, -->
    <!-- and if so, remember whether we have the UTC indicator ("Z"), or   -->
    <!-- are ahead of or at UTC ("+hh:mm") or behind UTC ("-hh:mm").       -->
    <xsl:variable name="zoneSep">
      <!-- Slightly misnamed variable, as the Z is the entire zone   -->
      <!-- designator, and the + or - is really the first character  -->
      <!-- of the zone designator, not a separator. Nonetheless,     -->
      <!-- set zoneSep to nothing, "Z", "-", or "+", depending on    -->
      <!-- the first character of the zone designator. Here we rely  -->
      <!-- on the fact that the characters +, -, and Z cannot appear -->
      <!-- anywhere else in an ISO date or time. -->
      <xsl:choose>
        <xsl:when test="contains($value,'-')">
          <xsl:text>-</xsl:text>
        </xsl:when>
        <xsl:when test="contains($value,'+')">
          <xsl:text>+</xsl:text>
        </xsl:when>
        <xsl:when test="contains($value,'Z')">
          <!-- It will always be the last character, but AFAIK there -->
          <!-- is no ends-with() function in XSLT 1.0. -->
          <xsl:text>Z</xsl:text>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text/>
          <!-- Is there a better way to set to null? -->
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <!-- Parse out the time indication from the zone designator. -->
    <xsl:variable name="time">
      <xsl:choose>
        <xsl:when test="$zoneSep=''">
          <!-- No zone designator, so time is entire value -->
          <xsl:value-of select="$value"/>
        </xsl:when>
        <xsl:otherwise>
          <!-- There is a zone designator, time is whatever is -->
          <!-- before it. -->
          <xsl:value-of select="substring-before($value,$zoneSep)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:variable name="zone">
      <xsl:choose>
        <xsl:when test="$zoneSep=''">
          <!-- No zone designator -->
          <xsl:text/>
        </xsl:when>
        <xsl:otherwise>
          <!-- There is a zone designator, remember whatever is after its -->
          <!-- first character. -->
          <xsl:value-of select="substring-after($value,$zoneSep)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <!-- Now see if we need to give time some false precision -->
    <xsl:variable name="newValue">
      <!-- Assign new value based on current time format -->
      <xsl:choose>
        <xsl:when test="string-length($time)=2">
          <!-- must be "hh" -->
          <xsl:value-of select="$time"/>
          <xsl:text>:00:00</xsl:text>
          <xsl:value-of select="$zoneSep"/>
          <xsl:value-of select="$zone"/>
        </xsl:when>
        <xsl:when test="string-length($time)=5">
          <!-- must be "hh:mm" -->
          <xsl:value-of select="$time"/>
          <xsl:text>:00</xsl:text>
          <xsl:value-of select="$zoneSep"/>
          <xsl:value-of select="$zone"/>
        </xsl:when>
        <xsl:otherwise>
          <!-- must be "hh:mm:ss[.s*]" -->
          <xsl:value-of select="$value"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <!-- OK, now spit out a copy of this element that's the same, -->
    <!-- but uses the new value -->
    <xsl:element name="{name(.)}">
      <!-- create <time> element, with all the original attributes ... -->
      <xsl:copy-of select="@*"/>
      <!-- ... but then copy over a new value= attribute, using -->
      <xsl:attribute name="value">
        <!-- ... our new value for it ... -->
        <xsl:value-of select="$newValue"/>
        <!-- ... replacing the original value= --> 
      </xsl:attribute>
      <xsl:apply-templates/>
      <!-- ... and all text and element children -->
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>