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) gt 4) then infosci:separate-thousands($left, 'l') else $left,
					   if ($right ne '') then '.' else '',
					   if (string-length($right) gt 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 ) eq 'l'" />
				<xsl:when test="lower-case( $mode ) eq '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) gt 3)
				then concat(
						if (lower-case($mode) eq '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 ) eq '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>