Newer
Older
XML / modules / number-formatting.xml
<?xml version="1.0" encoding="utf-8"?>

<!--
	Format numbers according to ISO 80000-1 specifications. In particular, sequences of digits longer than four are separated into groups of up to three digits, separated by a thin space.
-->

<stylesheet	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
			xmlns:xs="http://www.w3.org/2001/XMLSchema">

	<!--
		General template for formatting numbers.
	-->
	<template name="number" match="number">
		<!-- LaTeX just uses the \num macro from the siunitx package. -->
		<common formats="/latex/xelatex/">
			<xsl:text>\num{</xsl:text>
			<xsl:apply-templates />
			<xsl:text>}</xsl:text>
		</common>
		<common formats="/html/xhtml/">
			<xsl:value-of select="infosci:format-number( node() )" />
		</common>
	</template>
	
	<!--
		Format a number in according to ISO 80000-1, as follows:
		
			* Sign is preserved.
			* A decimal number with no digits before the decimal point has a zero (0) inserted before the decimal point.
			* Sequences of digits longer than four are grouped into thousands separated by a thin space.
		
		Note: HTML only, as LaTeX can simply use the \num macro in the siunitx package.
		
		$unformatted-value: The value to be formatted.
		
		Returns: The formatted number as a string.
	-->
	<function name="infosci:format-number" as="xs:string">
		<common formats="/html/xhtml/">
			<xsl:param name="unformatted-value" />
			<xsl:variable name="sign" select="
				if ( matches( $unformatted-value, '^[-+]' ) )
				then replace( $unformatted-value, '^([-+]).*', '$1' )
				else ''" />
			<xsl:variable name="left" select="
				if ( matches( $unformatted-value, '^[-+]?\d+' ) )
				then replace( $unformatted-value, '^[-+]?(\d+).*', '$1' )
				else '0'" />
			<xsl:variable name="right" select="
				if ( matches ( $unformatted-value, '^[-+]?\d*\.\d+' ) )
				then replace ( $unformatted-value, '^[-+]?\d*\.(\d+)', '$1' )
				else ''" />
			<xsl:sequence select="
				concat(	$sign,
						if ( string-length ( $left ) > 4 ) then infosci:separate-thousands( $left, 'l' ) else $left,
						if ( $right != '' ) then '.' else '',
						if ( string-length ( $right ) > 4 ) then infosci:separate-thousands( $right, 'r' ) else $right )" />
		</common>
	</function>
		
	<!--
		Splits a sequence of digits into thousand groups, separated by thin spaces (U+2009 THIN SPACE; this appears to work in both UTF-8 and ISO-8859-1).
		
		$unseparated-value: The value to be formatted.
		$mode: The "direction" in which to separate the digit sequence.
			'l' or 'L': the input sequence is left of the decimal point. [default]
			'r' or 'R': if the input sequence is right of the decimal point.
		
		Returns: The formatted number as a string.
	-->
	<function name="infosci:separate-thousands" as="xs:string">
		<common formats="/html/xhtml/">
			<xsl:param name="unseparated-value" />
			<xsl:param name="mode" />
			<!-- Sanity check. -->
			<xsl:choose>
				<xsl:when test="lower-case( $mode ) = 'l'" />
				<xsl:when test="lower-case( $mode ) = 'r'" />
				<xsl:otherwise>
					<xsl:message terminate="yes">ERROR: The mode parameter of function infosci:separate-thousands must be one of the values 'L', 'l', 'R' or 'r'.</xsl:message>
				</xsl:otherwise>
			</xsl:choose>
			<!--
				Recursively subdivide the sequence. Terminate when sequence length <= 3. Note that we have to use the numeric entity for thin space rather than the character entity &thinsp; because of the weird way XSLT handles character entities. Using the character entity, the HTML visibly displays as "&thinsp;".
			-->
			<xsl:sequence select="
				if ( string-length( $unseparated-value ) > 3 )
				then concat(
						if ( lower-case( $mode ) = 'r' )
						then substring( $unseparated-value, 1, 3 )
						else infosci:separate-thousands( substring( $unseparated-value, 1, string-length( $unseparated-value ) - 3 ), $mode ),
						'&#x2009;',
						if ( lower-case( $mode ) = 'r' )
						then infosci:separate-thousands( substring( $unseparated-value, 4 ), $mode )
						else substring( $unseparated-value, string-length( $unseparated-value ) - 2 )
					)
				else $unseparated-value" />
		</common>
	</function>

</stylesheet>