# -*- mode: Perl -*-
# /=====================================================================\ #
# | siunitx.sty | #
# | Implementation for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# TODO:
# * rounding options for number formatting
# * Semantics! should be possible to directly construct XMDual's for these
# without invoking the MathParser at all.
# * table alignments!
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Would be nice if we could load the package (without errors!),
# in order to pick up all the unit definitions!
RequirePackage('xcolor');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Dealing with the Options
# Boolean options
foreach my $key (qw(
version-1-compatibility abbreviations binary-units
free-standing-units overwrite-functions
bracket-numbers detect-family detect-italic detect-mode
detect-shape detect-weight multi-part-units parse-numbers
parse-units product-units
copy-complex-root copy-decimal-marker
bracket-negative-numbers bracket-numbers
separate-uncertainty tight-spacing
retain-explicit-plus add-decimal-zero add-integer-zero
omit-uncertainty
add-arc-degree-zero add-arc-minute-zero add-arc-second-zero
angle-symbol-over-decimal
sticky-per prefixes-as-symbols
)) {
DefKeyVal('SIX', $key, '', 'true'); }
sub six_get {
my ($key) = @_;
return LookupValue('SIX_' . ToString($key)); }
# And should probably trim spaces off the values...
sub six_getBool {
my ($key) = @_;
my $v = LookupValue('SIX_' . ToString($key));
return $v && (ToString($v) eq 'true'); }
# Should really figure out how Choice keyvals are set up.
sub six_getChoice {
my ($key) = @_;
my $v = LookupValue('SIX_' . ToString($key));
$v = $v && ToString($v);
$v =~ s/^\s*//; $v =~ s/\s*$//;
return $v; }
sub trim_spaces { # Haven't I written this code several times?
my ($tokens) = @_;
return unless $tokens;
my @tokens = $tokens->unlist;
while (@tokens && $tokens[0]->equals(T_SPACE)) { shift(@tokens); }
while (@tokens && $tokens[-1]->equals(T_SPACE)) { pop(@tokens); }
return Tokens(@tokens); }
sub six_setup {
my ($kv) = @_;
my $hash = $kv->getKeyVals;
foreach my $key (keys %$hash) {
my $value = $kv->getValue($key);
AssignValue('SIX_' . $key => trim_spaces($value)); }
return; }
DefPrimitive('\sisetup RequiredKeyVals:SIX', sub { six_setup($_[1]); });
DefMacro('\ProvidesExplFile{}{}{}{}', '');
DefPrimitiveI('\lx@six@initialize', undef, sub {
my $pkgoptions = LookupValue('opt@siunitx.sty');
my $setup = $pkgoptions && Tokenize('\sisetup{' . join(',', map { $_ } @$pkgoptions) . '}');
Digest($setup) if $setup;
if (six_getBool('version-1-compatibility') || six_get('alsoload')) {
# At present time (siunitx 2.6q) compatibility file is deeply expl3-ish.
## InputDefinitions('siunitx-version-1', type => 'cfg', noltxml => 1);
# Also, it defines a whole different set of options,
# but we can at least simulate the alternative unit definitions (see bottom)
six_load_compat1(); }
# At present time (siunitx 2.6q) these configuration files are nicely readable.
if (six_getBool('abbreviations')) {
InputDefinitions('siunitx-abbreviations', type => 'cfg', noltxml => 1); }
if (six_getBool('binary-units')) {
InputDefinitions('siunitx-binary', type => 'cfg', noltxml => 1); }
if (six_getBool('free-standing-units')) {
six_enableUnitMacros(six_getBool('overwrite-functions')); }
return; });
AtBeginDocument('\lx@six@initialize');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Some useful macros & symbols
DefMathI('\SIUnitSymbolCelsius', undef, UTF(0xB0) . "C");
DefMathI('\SIUnitSymbolOhm', undef, "\x{2126}");
DefMathI('\SIUnitSymbolDegree', undef, UTF(0xB0));
DefMathI('\SIUnitSymbolArcminute', undef, '{}^{\prime}');
DefMathI('\SIUnitSymbolArcsecond', undef, '{}^{\prime\prime}');
DefMathI('\SIUnitSymbolAngstrom', undef, UTF(0xC5));
DefMathI('\SIUnitSymbolMicro', undef, UTF(0xB5));
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Parsing numbers
# Recognize Number: signs, integer, decimal, fractional, exp-marker sign digits, uncertainty
# Recognize multipart numbers: mult, divide, \frac{}{}
# Recognize plural: separated by ;
# Ideally, we'd use an actual grammar, but since
# (a) we're dealing with tokens, including CS's and
# (b) the various terminals are parameterized in the keyvals options,
# it's kinda awkward. Of course, it's awkward parsing by-hand, too....
# We're going to parse from an ARRAY of tokens, just for simplicity
# The apparent grammar implies the following structure for numbers:
# simple: sign,integer part, decimal, fraction part, uncertainty
# There are 2 kinds of uncertainty (stored as a simple number)
# parenthesized: only digits; they represent the uncertainty in the last digits of the number
# separate: \pm digits.digits; the decimal is aligned with the main number's decimal
# These cases are distinguished by the presence of a sign in the uncertainty!
# complex : simple sign simmple i
# exponent: (simple|complex) expmark simple
# multipart: (simple|complex|exponent) (*|/) ...
# Options used:
# input-close-uncertainty, input-comparators, input-complex-roots,
# input-decimal-markers, input-digits, input-exponent-markers,
# input-open-uncertainty, input-protect-tokens, input-signs, input-uncertainty-signs,
# input-symbols, parse-numbers
# Also options for multi-part numbers:
# input-product, input-quotient
# Not yet handled:
# input-ignore
sub six_match1 {
my ($token, @sixkeys) = @_;
# Skip spaces...
# Remove & return all tokens matching one of the sets @sixkeys
my @tomatch = (T_SPACE, map { @{ six_get($_) || [] } } @sixkeys);
return grep { $token->equals($_) } @tomatch; }
sub six_match {
my ($tokens, @sixkeys) = @_;
# Skip spaces...
# Remove & return all tokens matching one of the sets @sixkeys
my @tomatch = (T_SPACE, map { @{ six_get($_) || [] } } @sixkeys);
my @matched = ();
my $t;
while (($t = $$tokens[0])
&& grep { scalar(@$tokens) && $t->equals($_) } @tomatch) {
shift(@$tokens);
push(@matched, $t) unless $t->equals(T_SPACE); }
return (@matched ? Tokens(@matched) : undef); }
sub six_parse_simplenumber {
my ($tokens) = @_;
my $sign = six_match($tokens, 'input-signs');
my ($integer, $decimal, $fraction, $uncertainty);
$integer = six_match($tokens, 'input-digits', 'input-symbols');
if ($decimal = six_match($tokens, 'input-decimal-markers')) {
$fraction = six_match($tokens, 'input-digits', 'input-symbols'); }
if (my $uncsign = six_match($tokens, 'input-uncertainty-signs')) {
# \pm form ('separate') allows decimal! (ie. has the same point as the main number)
my ($unc, $uncdec, $uncfrac);
$unc = six_match($tokens, 'input-digits', 'input-symbols');
if ($uncdec = six_match($tokens, 'input-decimal-markers')) {
$uncfrac = six_match($tokens, 'input-digits', 'input-symbols'); }
# Ambiguous: # \pm # is uncertainty unless followed by i, in which case complex!
if (my $whoops = six_match($tokens, 'input-decimal-markers', 'input-complex-roots')) {
# Whoops, really should be complex!!!!
unshift(@$tokens, map { $_ ? $_->unlist : () } $uncsign, $unc, $uncdec, $uncfrac, $whoops); }
else {
$uncertainty = { sign => $uncsign, integer => $unc,
decimal => $uncdec, fraction => $uncfrac }; } }
elsif (my $uncopen = six_match($tokens, 'input-open-uncertainty')) {
# Parenthesized ONLY allows digits (ie. in the same positions as the last digits of the number)
$uncertainty = { integer => six_match($tokens, 'input-digits', 'input-symbols') };
six_match($tokens, 'input-close-uncertainty'); }
return
($sign || $integer || $decimal || $fraction
? { sign => $sign, integer => $integer, decimal => $decimal, fraction => $fraction,
uncertainty => $uncertainty }
: undef); }
# Complex and/or exponential number
sub six_parse_cenumber {
my ($tokens) = @_;
my $number = six_parse_simplenumber($tokens);
if (my $i = six_match($tokens, 'input-complex-roots')) { # pure imaginary!
my $sign = $$number{sign}; $$number{sign} = undef; # Make sign "infix"
$number = { operator => 'complex', symbol => $i, sign => $sign, arg2 => $number }; }
# Check if followed by a sign, then expect imaginary part
elsif (my $sign = six_match($tokens, 'input-signs')) { # Imaginary part
my ($i, $imag);
if ((($i = six_match($tokens, 'input-complex-roots'))
&& ($imag = six_parse_simplenumber($tokens)))
|| (($imag = six_parse_simplenumber($tokens))
&& ($i = six_match($tokens, 'input-complex-roots')))) {
$number = { operator => 'complex', arg1 => $number, symbol => $i, sign => $sign, arg2 => $imag }; }
else {
Error('unexpected', 'sign', undef, "expected to find complex number"); } }
# Now check if followed by exponent
my ($expmark, $expsign, $exp);
if ($expmark = six_match($tokens, 'input-exponent-markers')) {
$expsign = six_match($tokens, 'input-signs');
$exp = six_match($tokens, 'input-digits', 'input-symbols');
$number = { operator => 'exponent', arg1 => $number,
arg2 => { sign => $expsign, integer => $exp } }; }
return $number; }
# handle comparator, products & fractions
sub six_parse_number {
my ($tokens) = @_;
if (my $comp = six_match($tokens, 'input-comparators')) {
return { operator => 'comparator', comparator => $comp, arg1 => six_parse_number($tokens) }; }
else {
my $number = six_parse_cenumber($tokens);
while (1) {
my $op;
if ($op = six_match($tokens, 'input-product')) {
$number = { operator => 'product', arg1 => $number, arg2 => six_parse_cenumber($tokens) }; }
elsif ($op = six_match($tokens, 'input-quotient')) {
$number = { operator => 'quotient', arg1 => $number, arg2 => six_parse_cenumber($tokens) }; }
else {
return $number; } }
return $number; } } # never gets here...
#======================================================================
# Post-processing numbers
# Options NOT YET HANDLED:
# *add-decimal-zero, *add-integer-zero, *explicit-sign, fixed-exponent,
# minimum-integer-digits, omit-uncertainty, retain-explicit-plus, retain-unity-mantissa
# retain-zero-exponent, round-half, round-integer-to-decimal, round-minimum
# round-mode, round-precision, scientific-notation, zero-decimal-to-integer
sub six_postprocess {
my ($number) = @_;
return six_postprocess_aux($number); }
sub six_postprocess_aux {
my ($number) = @_;
if (!$number) { }
elsif ($$number{operator}) {
$$number{arg1} = six_postprocess_aux($$number{arg1});
$$number{arg2} = six_postprocess_aux($$number{arg2}); }
else {
if (six_getBool('add-decimal-zero') && $$number{decimal} && !$$number{fraction}) {
$$number{fraction} = T_OTHER('0'); }
if (six_getBool('add-integer-zero') && $$number{decimal} && !$$number{integer}) {
$$number{integer} = T_OTHER('0'); }
my $s;
if (!$$number{sign} && ($s = six_getBool('explicit-sign'))) {
$$number{sign} = $s; }
if ($$number{uncertainty} && six_getBool('omit-uncertainty')) {
$$number{uncertainty} = undef; }
six_adjust_uncertainty($number);
}
return $number; }
# Officially part of formatting, but makes more sense here.
sub six_adjust_uncertainty {
my ($number) = @_;
my $uncertainty = $$number{uncertainty};
my $separate = six_getBool('separate-uncertainty');
if (!$uncertainty
|| ($separate && $$uncertainty{sign}) # already separate
|| (!$separate && !$$uncertainty{sign})) { # already not separate
return; } # No adjustment needed.
my $ndigits = ($$number{fraction} ? scalar($$number{fraction}->unlist) : 0);
if ($separate) { # Need separate, but have bracketted uncertainty
my $num = $$uncertainty{integer};
my @dig = $num->unlist;
my $n = scalar(@dig);
if ($n <= $ndigits) {
for (my $i = $n ; $i < $ndigits ; $i++) {
unshift(@dig, T_OTHER('0')); }
$$number{uncertainty} = { sign => T_CS('\pm'),
integer => T_OTHER('0'), decimal => six_get('output-decimal-marker'),
fraction => Tokens(@dig) }; }
else {
my @man = ();
for (my $i = $n ; $i > $ndigits ; $i--) {
push(@man, shift(@dig)); }
$$number{uncertainty} = { sign => T_CS('\pm'),
integer => Tokens(@man), decimal => six_get('output-decimal-marker'),
fraction => Tokens(@dig) }; } }
else { # Need bracketted, but have separate uncertainty.
my @dig = ();
push(@dig, $$uncertainty{fraction}->unlist) if $$uncertainty{fraction};
my $nuf = scalar(@dig);
for (my $i = $nuf ; $i < $ndigits ; $i++) {
push(@dig, T_OTHER('0')); }
unshift(@dig, $$uncertainty{integer}->unlist) if $$uncertainty{integer};
while (@dig && $dig[0]->equals(T_OTHER('0'))) {
shift(@dig); }
if ($nuf > $ndigits) { # Whoops, fraction part of main number needs padding!
my @frac = ($$number{fraction} ? $$number{fraction}->unlist : ());
for (my $i = $ndigits ; $i < $nuf ; $i++) {
push(@frac, T_OTHER('0')); }
$$number{fraction} = Tokens(@frac);
$$number{decimal} = six_get('output-decimal-marker') unless $$number{decimal}; }
$$number{uncertainty} = { integer => Tokens(@dig) }; }
return; }
#======================================================================
# Formatting numbers
# Options:
# bracket-negative-numbers, bracket-numbers, close-bracket, complex-root-position,
# copy-comnplex-root, copy-decimal-marker, exponent-base, exponent-product, group-digits,
# group-minimum-digits, group-separator, negative-color, open-bracket, output-close-uncertainty,
# output-complex-root, output-decimal-marker, output-exponent-marker, output-open-uncertainty,
# separate-uncertainty, uncertainty-separator
# Also options for multi-part numbers:
# fraction-function, output-product, output-quatient, quotient-mode
# Not handled:
# tight-spacing
sub six_format_simplenumber {
my ($number, $bracket) = @_;
# Not ONLY format the number, but arrange for a fork representing the semantics!
my @tokens = ();
my @trailer = ();
my $sign = $$number{sign};
my $integer = $$number{integer};
my $fraction = $$number{fraction};
my $uncertainty = $$number{uncertainty};
my $grouping = six_getChoice('group-digits');
if ($sign) {
if (ToString($sign) eq '-') {
if (my $c = six_get('negative-color')) {
push(@tokens, T_BEGIN, T_CS('\color'), T_BEGIN, $c->unlist, T_END);
unshift(@trailer, T_END); }
if (six_getBool('bracket-negative-numbers')) {
push(@tokens, six_get('open-bracket'));
unshift(@trailer, six_get('close-bracket')); }
else {
push(@tokens, $sign); } }
elsif ((ToString($sign) eq '+') && !six_getBool('retain-explicit-plus')) { }
else {
push(@tokens, $sign); } }
if ($integer) {
my $i = (($grouping eq 'true') || ($grouping eq 'integer')
? six_groupdigits($integer, +1)
: $integer);
push(@tokens, $i); }
if (my $d = (six_getBool('copy-decimal-marker')
? $$number{decimal}
: ($fraction || $$number{decimal} ? six_get('output-decimal-marker') : undef))) {
push(@tokens, $d); }
if ($fraction) {
my $f = (($grouping eq 'true') || ($grouping eq 'decimal')
? six_groupdigits($fraction, -1)
: $fraction);
push(@tokens, $f); }
if ($uncertainty) {
# NOTE: Need to shift the uncertainty if not same separate-ness!!
if (six_getBool('separate-uncertainty')) {
unshift(@tokens, six_get('open-bracket')) if $bracket > 0;
push(@tokens,
(six_get('uncertainty-separator') || ()),
six_format_simplenumber($uncertainty));
push(@tokens, six_get('close-bracket')) if $bracket > 0;
}
else {
push(@tokens,
(six_get('uncertainty-separator') || ()),
six_get('output-open-uncertainty'),
six_format_simplenumber($uncertainty),
six_get('output-close-uncertainty')); } }
return Tokens(@tokens, @trailer); }
sub six_groupdigits {
my ($digits, $direction) = @_;
my @digs = $digits->unlist;
my $min = ToString(six_get('group-minimum-digits'));
return $digits if $min > scalar(@digs);
my @r = ();
my $g = 3;
my $sep = six_get('group-separator');
if ($direction > 0) {
while (@digs) {
for (my $i = 0 ; @digs && $i < $g ; $i++) { unshift(@r, pop(@digs)); }
unshift(@r, $sep) if @digs; } }
else {
while (@digs) {
for (my $i = 0 ; @digs && $i < $g ; $i++) { push(@r, shift(@digs)); }
push(@r, $sep) if @digs; } }
return Tokens(@r); }
sub six_format_number {
my ($number, $bracket) = @_;
return unless $number;
return $number unless ref $number eq 'HASH';
$bracket = 0 unless $bracket && six_getBool('bracket-numbers');
# Not ONLY format the number, but arrange for a fork representing the semantics!
return unless $number;
my @tokens = ();
if (my $op = $$number{operator}) {
if ($op eq 'comparator') {
push(@tokens,
($$number{comparator} || ()),
six_format_number($$number{arg1})); }
elsif ($op eq 'product') {
push(@tokens,
six_format_number($$number{arg1}, 1),
six_get('output-product'),
six_format_number($$number{arg2}, 1)); }
elsif ($op eq 'quotient') {
if (six_getChoice('quotient-mode') eq 'fraction') {
push(@tokens,
six_get('fraction-function'),
T_BEGIN, six_format_number($$number{arg1}), T_END,
T_BEGIN, six_format_number($$number{arg2}), T_END); }
else {
push(@tokens,
six_format_number($$number{arg1}, 1),
six_get('output-quotient'),
six_format_number($$number{arg2}, 2)); } }
elsif ($op eq 'complex') {
my $i = (six_getBool('copy-complex-root')
? $$number{symbol} : six_get('output-complex-root'));
$i = Tokens(T_CS('\text'), T_BEGIN, $i, T_END);
push(@tokens,
($bracket > 0 && $$number{arg1} && $$number{arg2} ? six_get('open-bracket') : ()),
($$number{arg1} ? six_format_number($$number{arg1}) : ()),
(!$$number{sign} ||
(!$$number{arg1} && (ToString($$number{sign}) eq '+')
&& !six_getBool('retain-explicit-plus'))
? ()
: $$number{sign}),
# ($$number{sign} || ()),
(six_getChoice('complex-root-position') eq 'before-number'
? ($i, six_format_number($$number{arg2}))
: (six_format_number($$number{arg2}), $i)),
($bracket > 0 && $$number{arg1} && $$number{arg2} ? six_get('close-bracket') : ())); }
elsif ($op eq 'exponent') {
if (my $m = six_get('output-exponent-marker')) {
push(@tokens,
($bracket > 1 ? six_get('open-bracket') : ()),
six_format_number($$number{arg1}, 1),
$m,
six_format_number($$number{arg2}),
($bracket > 1 ? six_get('close-bracket') : ())); }
else {
push(@tokens,
($bracket > 1 ? six_get('open-bracket') : ()),
six_format_number($$number{arg1}, 1),
# NOTE: Only if number has digits!!!!
($$number{arg1}{integer} || $$number{arg1}{fraction} || $$number{arg1}{operator}
? six_get('exponent-product') : ()),
six_get('exponent-base'),
T_SUPER, T_BEGIN, six_format_number($$number{arg2}), T_END,
($bracket > 1 ? six_get('close-bracket') : ())); } }
else {
Error('unexpected', $op, undef, "Unrecognized operator $op in siunitx number"); } }
else {
push(@tokens, six_format_simplenumber($number, $bracket)); }
return Tokens(@tokens); }
#our @six_combos=('+-'=>'\pm','>='=>'\ge','<='=>'\le','>>'=>'\gg','<<'=>'\ll');
my %six_combinations = (
'+' => { '-' => T_CS('\pm') },
'>' => { '=' => T_CS('\ge'), '>' => T_CS('\gg') },
'<' => { '=' => T_CS('\le'), '<' => T_CS('\ll') },
);
sub six_combine {
my (@tokens) = @_;
my @r = ();
while (my $t = shift(@tokens)) {
my $repl;
if (@tokens && ($repl = $six_combinations{ $t->getCSName }{ $tokens[0]->getCSName })) {
shift(@tokens); push(@r, $repl); }
else {
push(@r, $t); } }
return @r; }
sub six_parse_begin {
my ($gullet, $kv) = @_;
# Need to redefine input-protect-tokens, then expand
my $stomach = $STATE->getStomach;
$stomach->bgroup;
six_setup($kv) if $kv;
map { Let($_, T_CS('\nothing')); } six_get('input-protect-tokens')->unlist;
return; }
sub six_parse_end {
my $stomach = $STATE->getStomach;
$stomach->egroup;
return; }
# Note that these 2 will return Tokens if parse-numbers is false!!!!
# TODO: Don't signal error if we're doing table columns!?
sub six_parse_1number {
my ($gullet, $expr) = @_;
my $result = $expr;
if (six_getBool('parse-numbers')) {
my $expanded = Expand($expr);
my $tokens = [six_combine($expanded->unlist)];
$result = six_postprocess(six_parse_number($tokens));
if (scalar(@$tokens)) {
Error('unexpected', $$tokens[0], $gullet,
"Not matched in \\num: " . ToString(Tokens(@$tokens)) . "\n");
return $expr; } }
return $result; }
sub six_parse_numbers {
my ($gullet, $expr) = @_;
my $result = $expr;
my @results = ();
if (six_getBool('parse-numbers')) {
my $expanded = Expand($expr);
my $tokens = [six_combine($expanded->unlist)];
while (1) {
$result = six_postprocess(six_parse_number($tokens));
push(@results, $result);
if (@$tokens && $$tokens[0]->equals(T_OTHER(';'))) {
shift(@$tokens); }
else {
last; } }
if (scalar(@$tokens)) {
Error('unexpected', $$tokens[0], $gullet,
"Not matched in \\num: " . ToString(Tokens(@$tokens)) . "\n");
@results = ($expr); } }
else {
# Well, what should we return if we're not parsing???
my @tokens = $expr->unlist;
while (@tokens) {
my @r = ();
while (@tokens && !$tokens[0]->equals(T_OTHER(';'))) {
push(@r, shift(@tokens)); }
push(@results, Tokens(@r)); }
}
return @results; }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Number processing macros
sub six_wrap {
my (@stuff) = @_;
my $color = six_get('color');
return Tokens(
($color ? (T_BEGIN, T_CS('\color'), T_BEGIN, $color, T_END) : ()),
T_CS('\@@BEGININLINEMATH'),
(grep { $_; } @stuff),
T_CS('\@@ENDINLINEMATH'),
($color ? (T_END) : ())); }
# \num[options]{number}
DefMacro('\num OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $number) = @_;
six_parse_begin($gullet, $kv);
my $result = six_wrap(six_format_number(six_parse_1number($gullet, $number)));
six_parse_end();
return $result; });
# \numlist[options]{number;number;...}
DefMacro('\numlist OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $numbers) = @_;
six_parse_begin($gullet, $kv);
my @results = six_parse_numbers($gullet, $numbers);
@results = map { six_wrap(six_format_number($_)); } @results;
my @punctuated = ();
if (@results) {
push(@punctuated, shift(@results));
if (!@results) { }
elsif (scalar(@results) == 1) {
push(@punctuated, six_get('list-pair-separator'), shift(@results)); }
else {
while (scalar(@results) > 1) {
push(@punctuated, six_get('list-separator'), shift(@results)); }
push(@punctuated, six_get('list-final-separator'), shift(@results)); } }
six_parse_end();
return Tokens(@punctuated); });
# \numrange[options]{first}{last}
DefMacro('\numrange OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $first, $last) = @_;
six_parse_begin($gullet, $kv);
my $result = Tokens(
six_wrap(six_format_number(six_parse_1number($gullet, $first))),
six_get('range-phrase'),
six_wrap(six_format_number(six_parse_1number($gullet, $last))));
six_parse_end();
return $result; });
DefMathI('\lx@arcdegree', undef, UTF(0xB0), meaning => 'arcdegree');
DefMathI('\lx@arcminute', undef, '{}^{\prime}', meaning => 'arcminute');
DefMathI('\lx@arcsecond', undef, '{}^{\prime\prime}', meaning => 'arcsecond');
# These are in serious need of tweaking!
DefMacro('\lx@arcdegreeoverdot', '\lx@stackrel{\lx@arcdegree}{.}');
DefMacro('\lx@arcminuteoverdot', '\lx@stackrel{{\scriptstyle\prime}}{.}');
DefMacro('\lx@arcsecondoverdot', '\lx@stackrel{{\scriptstyle\prime\prime}}{.}');
DefConstructor('\lx@astimes{}',
"<ltx:XMDual role='MULOP'><ltx:XMTok meaning='times'/><ltx:XMWrap>#1</ltx:XMWrap></ltx:XMDual>",
reversion => '#1');
DefConstructor('\lx@asplus{}',
"<ltx:XMDual role='ADDOP'><ltx:XMTok meaning='plus'/><ltx:XMWrap>#1</ltx:XMWrap></ltx:XMDual>",
reversion => '#1');
# \ang[options]{number;number;number}
DefMacro('\ang OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $expr) = @_;
six_parse_begin($gullet, $kv);
# Hopefully these are undef or simplenumbers!
my ($degrees, $minutes, $seconds) = six_parse_numbers($gullet, $expr);
my $addd0 = !$degrees && six_getBool('add-arc-degree-zero');
my $addm0 = !$minutes && six_getBool('add-arc-minute-zero')
&& (!$degrees || !$$degrees{fraction});
my $adds0 = !$seconds && six_getBool('add-arc-second-zero')
&& (!$degrees || !$$degrees{fraction})
&& (!$minutes || !$$degrees{minutes});
$degrees = { integer => T_OTHER('0') } if $addd0;
$minutes = { integer => T_OTHER('0') } if $addm0;
$seconds = { integer => T_OTHER('0') } if $adds0;
if ($seconds && $$seconds{sign}) {
if ($minutes) { $$minutes{sign} = $$seconds{sign}; $$seconds{sign} = undef; }
elsif ($degrees) { $$degrees{sign} = $$seconds{sign}; $$seconds{sign} = undef; } }
if ($minutes && $$minutes{sign}) {
if ($degrees) { $$degrees{sign} = $$minutes{sign}; $$minutes{sign} = undef; } }
my @punctuated = ();
# Note: $sep1 should appear as invisible times; $sep2 as invisible plus!
my $sep1 = Invocation(T_CS('\lx@astimes'), six_get('number-angle-product'));
my $sep2 = Invocation(T_CS('\lx@asplus'), six_get('arc-separator'));
my $above = six_get('angle-symbol-over-decimal');
my $save = six_get('copy-decimal-marker');
AssignValue('SIX_copy-decimal-marker' => 'true');
if ($degrees) {
if ($above && $$degrees{decimal}) {
$$degrees{decimal} = T_CS('\lx@arcdegreeoverdot');
push(@punctuated, six_format_number($degrees)); }
else {
push(@punctuated, six_format_number($degrees), $sep1, T_CS('\lx@arcdegree')); } }
if ($minutes) {
if (@punctuated) { push(@punctuated, $sep2); }
if ($above && $$minutes{decimal}) {
$$minutes{decimal} = T_CS('\lx@marcinuteoverdot');
push(@punctuated, six_format_number($minutes)); }
else {
push(@punctuated, six_format_number($minutes), $sep1, T_CS('\lx@arcminute')); } }
# Need special formatting of decimal for angle-symbol-over-decimal!!!
if ($seconds) {
if (@punctuated) { push(@punctuated, $sep2); }
if ($above && $$seconds{decimal}) {
$$seconds{decimal} = T_CS('\lx@arcsecondoverdot');
push(@punctuated, six_format_number($seconds)); }
else {
push(@punctuated, six_format_number($seconds), $sep1, T_CS('\lx@arcsecond')); } }
AssignValue('SIX_copy-decimal-marker' => $save);
six_parse_end();
return six_wrap(@punctuated); });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Processing Units
# Unit processing macros
sub six_peel_group {
my (@tokens) = @_;
if ($tokens[0]->getCatcode == CC_BEGIN) {
shift(@tokens);
my @result = ();
my $level = 1;
while (@tokens) {
my $t = shift(@tokens);
my $cc = $t->getCatcode;
if ($cc == CC_END) {
$level--;
last unless $level; }
elsif ($cc == CC_BEGIN) {
$level++; }
push(@result, $t); }
return (Tokens(@result), @tokens); }
else {
return (undef, @tokens); } }
# Turn all the internal definitions into real macros
AssignValue(siunitx_macros => {});
sub six_enableUnitMacros {
my ($overwrite) = @_;
my %hash = %{ LookupValue('siunitx_macros') };
foreach my $name (keys %hash) {
my $cs = $hash{$name}{cs};
if ($overwrite || !LookupDefinition($cs)) {
Let($cs, $hash{$name}{implementation}); } }
return; }
sub six_convertUnits {
my ($tokens) = @_;
my @tokens = $tokens->unlist;
my @defns = ();
while (@tokens) {
my ($name, $arg);
if ($tokens[0]->equals(T_CS('\lx@six@unitobject'))) {
shift(@tokens);
($name, @tokens) = six_peel_group(@tokens);
push(@defns, LookupMapping('siunitx_macros', ToString($name))); }
elsif ($tokens[0]->equals(T_CS('\lx@six@unitobject@arg'))) {
shift(@tokens);
($name, @tokens) = six_peel_group(@tokens);
($arg, @tokens) = six_peel_group(@tokens);
my $newdefn = { %{ LookupMapping('siunitx_macros', ToString($name)) } };
if (my $argkey = $$newdefn{arg}) {
$$newdefn{$argkey} = $arg; }
push(@defns, $newdefn); }
elsif ($tokens[0]->equals(T_SPACE)) {
shift(@tokens); }
elsif ($tokens[0]->equals(T_OTHER('.'))) {
shift(@tokens); }
else {
return; } }
return [@defns]; }
sub six_parse_units {
my ($defns) = @_;
my @defns = @$defns;
my @units = ();
my @descr = ();
my $stickyper = six_getBool('sticky-per');
my $savedper;
while (@defns) {
# Syntax order: \per \prepower \prefix \unit \qualifier \postpower
# BUT: \cancel, \highlight can appear anywhere, but only apply if before \prefix, else NEXT
my $unit = {};
my @save;
foreach my $role (qw(per prepower prefix unit qualifier postpower)) {
my $r;
while (@defns && ($r = $defns[0]{type}) && (($r eq $role) || ($r eq 'style'))) {
if ($r eq $role) {
$$unit{$role} = shift(@defns);
last; }
elsif (!$$unit{prefix} && !$$unit{unit}) {
my $style = $defns[0]{name};
$$unit{$style} = shift(@defns); }
else {
push(@save, shift(@defns)); } } # Else save for next unit!
}
if ((!keys %$unit) && @defns) {
Error('unexpected', $defns[0]{name}, undef, "Don't know what to do with si unit.");
return (); }
# Error if no unit, unless pure prefix(?)
elsif (!$$unit{unit} && !($$unit{prefix} && !$$unit{qualifier} && !$$unit{power})) {
Warn('expected', 'unit', undef, "Unit doesn't have a base unit",
(@defns ? "Next is $defns[0]{name}" : ()));
# return ();
}
if ($savedper) { $$unit{per} = $savedper; }
elsif ($stickyper) { $savedper = $$unit{per}; }
push(@units, $unit);
push(@descr, '[' . join(',', map { $_ . '=' . ToString($$unit{$_}) } keys %$unit) . ']');
unshift(@defns, @save) if @defns; }
return @units; }
sub six_format_1unit {
my ($unit) = @_;
my $per = $$unit{per};
my $pre = $$unit{prefix}{presentation};
my $u = $$unit{unit}{presentation};
my $p = $$unit{prepower}{power} || $$unit{postpower}{power};
my $q = $$unit{qualifier}{presentation};
my @tokens = ();
if ($per) {
$p = ($p ? Tokens(T_OTHER('-'), $p) : Tokens(T_OTHER('-'), T_OTHER('1'))); }
if ($q) {
my $qmode = six_getChoice('qualifier-mode');
if ($qmode eq 'subscript') {
$q = Tokens(T_SUB(), T_BEGIN, T_CS('\mathrm'), T_BEGIN, $q, T_END, T_END); }
elsif ($qmode eq 'brackets') {
$q = Tokens(six_get('open-bracket'), T_CS('\mathrm'), T_BEGIN, $q, T_END, six_get('close-bracket')); }
elsif (($qmode eq 'phrase') || ($qmode eq 'space')) {
my $sep = ($qmode eq 'phrase' ? six_get('qualifier-phrase') : T_CS('\;'));
$u = Tokens(($p ? six_get('open-bracket') : ()),
($pre ? $pre : ()),
$u, $sep, $q,
($p ? six_get('close-bracket') : ()));
$q = $pre = undef; }
elsif ($qmode eq 'text') {
$q = Tokens(T_CS('\mathrm'), T_BEGIN, $q, T_END) } }
push(@tokens,
T_CS('\mathrm'),
T_BEGIN,
($pre ? $pre : ()),
($u ? $u : ()),
T_END,
($q ? $q : ()),
($p ? (T_SUPER(), T_BEGIN, $p, T_END) : ()),
);
if ($$unit{cancel}) {
@tokens = (T_CS('\cancel'), T_BEGIN, @tokens, T_END); }
if (my $color = $$unit{highlight}{color}) {
@tokens = (T_BEGIN, T_CS('\color'), T_BEGIN, $color, T_END, @tokens, T_END); }
return @tokens; }
sub six_format_unitseq {
my (@units) = @_;
my @tokens = ();
my $times = six_get('inter-unit-product');
foreach my $unit (@units) {
push(@tokens, $times) if @tokens;
push(@tokens, six_format_1unit($unit)); }
return @tokens; }
sub six_format_units {
my (@units) = @_;
my @tokens = ();
my $p2 = 0;
my $p10 = 0;
if (!six_getBool('prefixes-as-symbols')) {
foreach my $unit (@units) {
if (my $pre = $$unit{prefix}) {
my $p = ToString($$unit{prepower}{power} || $$unit{postpower}{power} || 1)
* ($$unit{per} ? -1 : +1);
if ($$pre{base} == 2) { $p2 += $p * ToString($$pre{power}); }
else { $p10 += $p * ToString($$pre{power}); }
$$unit{prefix} = undef;
if (($$unit{unit}{name} || '') eq 'gram') { # Special case: keep kilograms!
$$unit{prefix} = LookupMapping('siunitx_macros', 'kilo');
$p10 -= 3 * $p; } } }
if ($p2) {
push(@tokens, T_OTHER('2'), T_SUPER, T_OTHER($p2), six_get('inter-unit-product')); }
if ($p10) {
push(@tokens, T_OTHER('10'), T_SUPER, T_OTHER($p10), six_get('inter-unit-product')); } }
# per-mode = reciprocal, fraction, reciprocal-positive-first, symbol, symbol-or-fraction
my $permode = six_getChoice('per-mode');
if ($permode eq 'symbol-or-fraction') {
$permode = ((LookupValue('font')->getMathstyle || 'display') eq 'display' ? 'fraction' : 'symbol'); }
if ($permode eq 'reciprocal') {
push(@tokens, six_format_unitseq(@units)); }
else { # Otherwise, we've got to collect num & denom
my @numer = ();
my @denom = ();
foreach my $unit (@units) { # Separate into positive & negative powers.
if ($$unit{per}) { push(@denom, $unit); }
else { push(@numer, $unit); } }
if ($permode eq 'reciprocal-positive-first') {
push(@tokens, six_format_unitseq(@numer, @denom)); }
else { # Otherwise, remove per markers from denom.
map { $$_{per} = undef; } @denom;
if ($permode eq 'fraction') {
push(@tokens, T_CS('\frac'),
T_BEGIN, six_format_unitseq(@numer), T_END,
T_BEGIN, six_format_unitseq(@denom), T_END); }
elsif ($permode eq 'repeated-symbol') {
push(@tokens, six_format_unitseq(@numer));
foreach my $denom (@denom) {
push(@tokens, six_get('per-symbol'), six_format_1unit($denom)); } }
elsif ($permode eq 'symbol') {
my $bracket = (scalar(@denom) > 1) && six_getBool('bracket-unit-denominator');
push(@tokens, six_format_unitseq(@numer),
six_get('per-symbol'),
($bracket ? six_get('open-bracket') : ()),
six_format_unitseq(@denom),
($bracket ? six_get('close-bracket') : ())); }
else {
Error('unexpected', $permode, undef, "Unknown siunitx per-mode $permode"); } } }
return Tokens(@tokens); }
sub six_parse_literalunits {
my ($expr) = @_;
my @tokens = $expr->unlist;
my @result = ();
while (@tokens) {
my $t = shift(@tokens);
my $cc = $t->getCatcode;
if ($t->equals(T_OTHER('.'))) {
push(@result, six_get('inter-unit-product')); }
elsif ($t->equals(T_SUPER)) {
my $g;
($g, @tokens) = six_peel_group(@tokens);
$g = shift(@tokens) unless $g;
push(@result, T_SUPER, T_BEGIN, $g, T_END); }
elsif ($cc == CC_BEGIN) {
my $g;
($g, @tokens) = six_peel_group(T_BEGIN, @tokens);
$g = shift(@tokens) unless $g;
push(@result, T_BEGIN, $g, T_END); }
elsif (($cc == CC_LETTER) || ($cc == CC_OTHER)) {
push(@result, T_CS('\mathrm'), T_BEGIN, $t, T_END); }
else {
push(@result, $t); } } # whatever it is....
return Tokens(@result); }
sub six_process_units {
my ($expr) = @_;
$expr = Expand($expr);
if (my $defns = six_convertUnits($expr)) {
return six_format_units(six_parse_units($defns)); }
else {
return six_parse_literalunits($expr); } }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Unit Macros
# \si[options]{units}
DefMacro('\si OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $units) = @_;
six_parse_begin($gullet, $kv);
six_enableUnitMacros(1);
my $funits = six_wrap(six_process_units($units));
six_parse_end();
return $funits; });
# \SI [options]{number}{units}
DefMacro('\SI OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $number, $units) = @_;
six_parse_begin($gullet, $kv);
# multi-part-units, product-units !!!! BLECH!!!
my $fnumber = six_format_number(six_parse_1number($gullet, $number));
six_enableUnitMacros(1);
my $funits = six_process_units($units);
my $result = six_wrap($fnumber, six_get('number-unit-product'), $funits);
six_parse_end();
return $result; });
# \SIlist[options]{number;number;...}{units}
DefMacro('\SIlist OptionalKeyVals:SIX {}{}', sub {
my ($gullet, $kv, $numbers, $units) = @_;
six_parse_begin($gullet, $kv);
my $prod = six_get('number-unit-product');
my $mode = six_getChoice('list-units'); # brackets, repeat, single.
my @results = six_parse_numbers($gullet, $numbers);
six_enableUnitMacros(1);
my $funits = six_process_units($units);
@results = map { six_wrap(six_format_number($_), ($mode eq 'repeat' ? ($prod, $funits) : ())); } @results;
my @punctuated = ();
my $dobrackets = (scalar(@results) > 1) && ($mode eq 'brackets');
if ($dobrackets) {
push(@punctuated, six_get('open-bracket')); }
if (@results) {
push(@punctuated, shift(@results));
if (!@results) { }
elsif (scalar(@results) == 1) {
push(@punctuated, six_get('list-pair-separator'), shift(@results)); }
else {
while (scalar(@results) > 1) {
push(@punctuated, six_get('list-separator'), shift(@results)); }
push(@punctuated, six_get('list-final-separator'), shift(@results)); } }
if ($dobrackets) {
push(@punctuated, six_get('close-bracket')); }
if ($mode ne 'repeat') {
push(@punctuated, six_wrap($prod, $funits)); }
six_parse_end();
return Tokens(@punctuated); });
# \SIrange[options]{number}{first}{last}
DefMacro('\SIrange OptionalKeyVals:SIX {}{}{}', sub {
my ($gullet, $kv, $first, $last, $units) = @_;
six_parse_begin($gullet, $kv);
my $prod = six_get('number-unit-product');
my $mode = six_getChoice('range-units'); # brackets, repeat, single.
my $fnumber = six_format_number(six_parse_1number($gullet, $first));
my $lnumber = six_format_number(six_parse_1number($gullet, $last));
six_enableUnitMacros(1);
my $funits = six_process_units($units);
my $result = Tokens(
($mode eq 'brackets' ? six_get('open-bracket') : ()),
six_wrap($fnumber, ($mode eq 'repeat' ? ($prod, $funits) : ())),
six_get('range-phrase'),
six_wrap($lnumber, ($mode eq 'repeat' ? ($prod, $funits) : ())),
($mode eq 'brackets' ? six_get('close-bracket') : ()),
($mode ne 'repeat' ? six_wrap($prod, $funits) : ()));
six_parse_end();
return $result; });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
DefPrimitive('\lx@six@unitobject{}', "");
DefPrimitive('\lx@six@unitobject@arg{}{}', "");
# These should only get expanded if we've failed to parse the unit structure
DefMacro('\lx@six@unitobject{}', sub {
my ($gullet, $name) = @_;
my $defn = LookupMapping('siunitx_macros', ToString($name));
my $pres = $$defn{presentation};
return ($pres ? Tokens(T_CS('\mathrm'), T_BEGIN, $pres, T_END) : T_OTHER('??')); },
protected => 1);
DefMacro('\lx@six@unitobject@arg{}{}', sub {
my ($gullet, $name, $data) = @_;
my $defn = LookupMapping('siunitx_macros', ToString($name));
# Incorporate the arg by assuming that the presentation TAKES an arg!
my $pres = $$defn{presentation};
return ($pres ? Tokens($pres, T_BEGIN, $data, T_END) : T_OTHER('??')); },
protected => 1);
# Collapsing nested definitions: If the data of this unit are just more unit objects, return them,
DefMacro('\lx@six@unitobject@collapsible{}{}', sub {
my ($gullet, $name, $data) = @_;
$data = Expand($data);
my @tokens = $data->unlist;
my @result = ();
while (@tokens) {
if ($tokens[0]->equals(T_CS('\lx@six@unitobject'))) {
my ($aname);
shift(@tokens);
($aname, @tokens) = six_peel_group(@tokens);
push(@result, Invocation(T_CS('\lx@six@unitobject'), $aname)); }
elsif ($tokens[0]->equals(T_CS('\lx@six@unitobject@arg'))) {
my ($aname, $arg);
shift(@tokens);
($aname, @tokens) = six_peel_group(@tokens);
($arg, @tokens) = six_peel_group(@tokens);
push(@result, Invocation(T_CS('\lx@six@unitobject@arg'), $aname, $arg)); }
elsif ($tokens[0]->getCatcode == CC_SPACE) {
shift(@tokens); }
else {
return Invocation(T_CS('\lx@six@unitobject'), $name); } }
return Tokens(@result); });
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Unit Declarations
# \NewDocumentCommand \DeclareBinaryPrefix { m m m }{
# \__siunitx_declare_prefix : Nnnn #1 {#2} { 2 } {#3} }
DefPrimitive('\DeclareBinaryPrefix OptionalKeyVals:SIX SkipSpaces DefToken {}{}', sub {
my ($stomach, $kv, $cs, $presentation, $power) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs, keyvals => $kv, type => 'prefix',
base => 2, power => $power, presentation => $presentation });
DefMacroI($newcs, undef,
'\lx@six@unitobject@collapsible{' . $name . '}{' . ToString($presentation) . '}');
return; });
# \NewDocumentCommand \DeclareSIPrefix { m m m }{
# \__siunitx_declare_prefix : Nnnn #1 {#2} { 10 } {#3} }
# Prefix operator, applies a power
DefPrimitive('\DeclareSIPrefix OptionalKeyVals:SIX SkipSpaces DefToken {}{}', sub {
my ($stomach, $kv, $cs, $presentation, $power) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs, keyvals => $kv, type => 'prefix',
base => 10, power => $power, presentation => $presentation });
DefMacroI($newcs, undef,
'\lx@six@unitobject@collapsible{' . $name . '}{' . ToString($presentation) . '}');
return; });
# \NewDocumentCommand \DeclareSIPrePower { m m } {
# \__siunitx_declare_power_before:Nn #1 {#2} }
# Prefix operator, applies a power
DefPrimitive('\DeclareSIPrePower OptionalKeyVals:SIX SkipSpaces DefToken {}', sub {
my ($stomach, $kv, $cs, $power) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs,
keyvals => $kv, type => 'prepower', power => $power,
presentation => Tokens(T_SUPER, T_BEGIN, $power, T_END) });
DefMacroI($newcs, undef, '\lx@six@unitobject{' . $name . '}');
return; });
# \NewDocumentCommand \DeclareSIPostPower { m m } {
# \__siunitx_declare_power_after:Nn #1 {#2} }
# Postfix operator, applies a power
DefPrimitive('\DeclareSIPostPower OptionalKeyVals:SIX SkipSpaces DefToken {}', sub {
my ($stomach, $kv, $cs, $power) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs,
keyvals => $kv, type => 'postpower', power => $power,
presentation => Tokens(T_SUPER, T_BEGIN, $power, T_END) });
DefMacroI($newcs, undef, '\lx@six@unitobject{' . $name . '}');
return; });
# Special builtins: \tothe{}, \raiseto{}
AssignMapping('siunitx_macros',
tothe => { name => 'tothe', cs => T_CS('\tothe'), implementation => T_CS('\lx@six@tothe'),
keyvals => undef, type => 'postpower', arg => 'power', presentation => T_SUPER });
DefMacro('\lx@six@tothe{}', '\lx@six@unitobject@arg{tothe}{#1}');
AssignMapping('siunitx_macros',
raiseto => { name => 'raiseto', cs => T_CS('\raiseto'), implementation => T_CS('\lx@six@raiseto'),
keyvals => undef, type => 'prepower', arg => 'power', presentation => T_SUPER });
DefMacro('\lx@six@raiseto{}', '\lx@six@unitobject@arg{raiseto}{#1}');
# \NewDocumentCommand \DeclareSIQualifier { m m } {
# \__siunitx_declare_qualifier:Nn #1 {#2} }
# Postfix operator, qualifies the meaning, applies a subscript
DefPrimitive('\DeclareSIQualifier OptionalKeyVals:SIX SkipSpaces DefToken {}', sub {
my ($stomach, $kv, $cs, $qualifier) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs,
keyvals => $kv, type => 'qualifier', presentation => $qualifier });
DefMacroI($newcs, undef, '\lx@six@unitobject{' . $name . '}');
return; });
# Special builtin: \of{}
AssignMapping('siunitx_macros',
of => { name => 'of', cs => T_CS('\of'), implementation => T_CS('\lx@six@of'),
keyvals => undef, type => 'qualifier', arg => 'qualifier', presentation => T_CS('\mathrm') });
DefMacro('\lx@six@of{}', '\lx@six@unitobject@arg{of}{#1}');
# \NewDocumentCommand \DeclareSIUnit { O {} m m } {
# \__siunitx_declare_unit:Nnn #2 {#3} {#1} }
# Core unit.
# BUT may act more like simple macro: an "abbreviation" when presentation is unit keywords!
# Or a macro (or Unit) that expands into... (and it may not even be defined yet)
DefPrimitive('\DeclareSIUnit OptionalKeyVals:SIX SkipSpaces DefToken {}', sub {
my ($stomach, $kv, $cs, $presentation) = @_;
my $name = $cs->getCSName; $name =~ s/^\\//;
my $newcs = T_CS('\lx@six@' . $name);
AssignMapping('siunitx_macros',
$name => { name => $name, cs => $cs, implementation => $newcs,
keyvals => $kv, type => 'unit', presentation => $presentation });
DefMacroI($newcs, undef,
'\lx@six@unitobject@collapsible{' . $name . '}{' . ToString($presentation) . '}');
return; });
# \NewDocumentCommand \DeclareSIUnitWithOptions { m m m }{
# \__siunitx_declare_unit : Nnn #1 {#2} {#3} }
# Special builtins at highest level(?)
# \per Note: with sticky-per, it applies to ALL following units!
# \cancel
# \highlight{color}
AssignMapping('siunitx_macros',
per => { name => 'per', cs => T_CS('\per'), implementation => T_CS('\lx@six@per'),
keyvals => undef, type => 'per', power => -1, presentation => T_OTHER('/') });
DefMacro('\lx@six@per', '\lx@six@unitobject{per}');
AssignMapping('siunitx_macros',
cancel => { name => 'cancel', cs => T_CS('\cancel'), implementation => T_CS('\lx@six@cancel'),
keyvals => undef, type => 'style', presentation => Tokens() });
DefMacro('\lx@six@cancel', '\lx@six@unitobject{cancel}');
AssignMapping('siunitx_macros',
highlight => { name => 'highlight', cs => T_CS('\highlight'), implementation => T_CS('\lx@six@highlight'),
keyvals => undef, type => 'style', arg => 'color', presentation => T_CS('\@gobble') });
DefMacro('\lx@six@highlight{}', '\lx@six@unitobject@arg{highlight}{#1}');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Tables
DefColumnType('s Optional', sub {
my ($gullet, $kv) = @_;
$LaTeXML::BUILD_TEMPLATE->addColumn(
before => Tokens(T_CS('\lx@si@column@begin'), ($kv ? (T_OTHER('['), $kv, T_OTHER(']')) : ())),
# after => Tokens(T_CS('\lx@si@column@end')),
# align => 'char:' . ToString(Digest(T_CS('\nprt@decimal')))
);
return; });
DefColumnType('S Optional', sub {
my ($gullet, $kv) = @_;
$LaTeXML::BUILD_TEMPLATE->addColumn(
before => Tokens(T_CS('\lx@SI@column@begin'), ($kv ? (T_OTHER('['), $kv, T_OTHER(']')) : ())),
# after => Tokens(T_CS('\lx@SI@column@end')),
# align => 'char:' . ToString(Digest(T_CS('\nprt@decimal'))));
);
return; });
DefMacro('\lx@si@column@begin Optional XUntil:\@@close@inner@column',
'\def\@@eat@space{}\lx@table@si[#1]{#2}\@@close@inner@column');
DefMacro('\lx@SI@column@begin Optional XUntil:\@@close@inner@column',
'\def\@@eat@space{}\lx@table@num[#1]{#2}\@@close@inner@column');
# TODO: These two need to deal better with unrecognized arguments.
# Generally they should NOT be in math mode... (but sometimes, still?)
# Similar to \num, no error...
# color treated a bit differently?
DefMacro('\lx@table@num OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $number) = @_;
six_parse_begin($gullet, $kv);
my @tokens = Expand($number)->unlist;
my $doparse = six_getBool('parse-numbers');
# Deal with recognizing "surrounding material"
my @pre = ();
my @post = ();
my $color = six_get('color');
my $result;
while (@tokens) {
my $cc = $tokens[0]->getCatcode;
if (($cc == CC_SPACE)
|| ($doparse && ($cc == CC_CS) # leave cs if not parsing????
# Undoubtedly the wrong approach, but... ?
&& !six_match1($tokens[0], 'input-comparators', 'input-protect-tokens', 'input-symbols'))) {
$color = undef if $tokens[0]->equals(T_CS('\color')); # !?!?!?!
push(@pre, shift(@tokens)); }
elsif ($cc == CC_BEGIN) {
my $p;
($p, @tokens) = six_peel_group(@tokens);
push(@pre, T_BEGIN, $p, T_END); }
else {
last; } }
if ($doparse) {
my $tokens = [six_combine(@tokens)];
my $parsed = six_parse_number($tokens);
@post = @$tokens; # Save what's left
$result = six_format_number(six_postprocess($parsed)); }
else {
$result = Tokens(@tokens); }
if ($color) {
push(@pre, T_BEGIN, T_CS('\color'), T_BEGIN, $color, T_END);
unshift(@post, T_END); }
six_parse_end();
return Tokens(@pre,
($result ? six_wrap($result) : ()),
@post); });
# similar to \si
DefMacro('\lx@table@si OptionalKeyVals:SIX {}', sub {
my ($gullet, $kv, $units) = @_;
six_parse_begin($gullet, $kv);
six_enableUnitMacros(1);
my @tokens = Expand($units)->unlist;
my @pre = ();
my @post = ();
my $color = six_get('color');
my $result;
while (@tokens) {
my $cc = $tokens[0]->getCatcode;
if ($cc == CC_SPACE) { # leave cs if not parsing????
push(@pre, shift(@tokens)); }
elsif ($cc == CC_BEGIN) {
my $p;
($p, @tokens) = six_peel_group(@tokens);
push(@pre, T_BEGIN, $p, T_END); }
else {
last; } }
if (my $defns = six_convertUnits(Tokens(@tokens))) {
$result = six_format_units(six_parse_units($defns)); }
else {
## Info('unexpected', 'whatever', undef,
## "Don't yet know how to parse non-macro unit expressions");
$result = Tokens(@tokens); }
if ($color) {
unshift(@pre, T_BEGIN, T_CS('\color'), T_BEGIN, $color, T_END); # outside!!!
push(@post, T_END); }
six_parse_end();
return Tokens(@pre,
($result ? six_wrap($result) : ()),
@post); });
# ?
#Let('\tablenum', '\lx@table@num');
Let('\tablenum', '\num');
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
RawTeX(<<'EoTeX');
\sisetup{
abbreviations,
binary-units,
math-rm = \mathrm,
math-sf = \mathsf,
math-tt = \mathtt,
mode = math,
text-rm = \rmfamily,
text-sf = \sffamily,
text-tt = \ttfamily,
input-product = x,
input-quotient = /,
%(
input-close-uncertainty = ),
input-complex-roots = ij,
input-comparators = {<=>\approx\ge\geq\gg\le\leq\ll\sim},
input-decimal-markers = {.,},
input-digits = 0123456789,
input-exponent-markers = dDeE,
input-open-uncertainty = (, % )
input-protect-tokens = \approx\dots\ge\geq\gg\le\leq\ll\mp\pi\pm\sim,
input-signs = +-\mp\pm,
input-symbols = \dots\pi,
input-uncertainty-signs = \pm,
add-decimal-zero = true,
add-integer-zero = true,
retain-unity-mantissa = true,
round-half = up,
round-minimum = 0,
round-precision = 2,
bracket-numbers = true , % (
close-bracket = ) ,
complex-root-position = after-number ,
copy-decimal-marker = false ,
exponent-base = 10 ,
exponent-product = \times ,
group-digits = true ,
group-minimum-digits = 5 ,
group-separator = \, ,
open-bracket = ( , % ) (
output-close-uncertainty = ) ,
output-complex-root = \ensuremath { \mathrm { i } } ,
output-decimal-marker = . ,
output-open-uncertainty = (, % )
fraction-function = \frac ,
output-product = \times ,
output-quotient = / ,
parse-numbers = true ,
quotient-mode = symbol,
forbid-literal-units = false,
parse-units = true,
prefixes-as-symbols = true,
bracket-unit-denominator = true,
inter-unit-product = \,,
literal-superscript-as-power = true,
per-mode = reciprocal,
per-symbol = /,
power-font = number,
qualifier-mode = subscript,
qualifier-phrase = { ~ of ~ },
multi-part-units = brackets,
number-unit-product = \, ,
product-units = repeat,
list-final-separator = { ~ and ~ } ,
list-pair-separator = { ~ and ~ } ,
list-separator = { , ~ } ,
list-units = repeat,
range-phrase = { ~ to ~ },
range-units = repeat,
table-unit-alignment = center,
table-align-comparator = true,
table-align-exponent = true,
table-align-text-pre = true,
table-align-text-post = true,
table-align-uncertainty = true,
table-omit-exponent = false,
table-parse-only = false,
table-number-alignment = center-decimal-marker,
table-text-alignment = center,
table-figures-decimal = 2,
table-figures-integer = 3,
redefine-symbols = true,
math-angstrom = \text { \AA },
math-arcminute = { } ^ { \prime },
math-arcsecond = { } ^ { \prime \prime },
math-celsius = { } ^ { \circ } \kern - \scriptspace \__siunitx_unit_mathrm:n { C } ,
math-degree = { } ^ { \circ },
math-micro = \text { \c__siunitx_mu_tl },
math-ohm = \text { \ensuremath { \c__siunitx_omega_tl } },
text-angstrom = \AA,
text-arcminute = \ensuremath { { } ^ { \prime } },
text-arcsecond = \ensuremath { { } ^ { \prime \prime } },
text-celsius =
\ensuremath { { } ^ { \circ } } \kern -\scriptspace C ,
text-degree = \ensuremath { { } ^ { \circ } },
text-micro = \c__siunitx_mu_tl ,
text-ohm = \ensuremath { \c__siunitx_omega_tl },
% if strict;
% bracket-numbers = true,
% detect-family = false,
% detect-mode = false,
% detect-shape = false,
% detect-weight = false,
% multi-part-units = brackets,
% parse-numbers = true,
% parse-units = true,
% product-units = repeat,
% otherwise
bracket-numbers ,
detect-family ,
detect-italic ,
detect-mode ,
detect-shape ,
detect-weight ,
multi-part-units ,
parse-numbers ,
parse-units ,
product-units,
number-angle-product=,
number-angle-separator=,
arc-separator=,
}
\DeclareSIUnit \kilogram { \kilo \gram }
\DeclareSIUnit \metre { m }
\DeclareSIUnit \meter { \metre }
\DeclareSIUnit \mole { mol }
\DeclareSIUnit \second { s }
\DeclareSIUnit \ampere { A }
\DeclareSIUnit \kelvin { K }
\DeclareSIUnit \candela { cd }
\DeclareSIUnit \gram { g }
\DeclareSIPrefix \yocto { y } { -24 }
\DeclareSIPrefix \zepto { z } { -21 }
\DeclareSIPrefix \atto { a } { -18 }
\DeclareSIPrefix \femto { f } { -15 }
\DeclareSIPrefix \pico { p } { -12 }
\DeclareSIPrefix \nano { n } { -9 }
\DeclareSIPrefix \micro { \SIUnitSymbolMicro } { -6 }
\DeclareSIPrefix \milli { m } { -3 }
\DeclareSIPrefix \centi { c } { -2 }
\DeclareSIPrefix \deci { d } { -1 }
\DeclareSIPrefix \deca { da } { 1 }
\DeclareSIPrefix \deka { da } { 1 }
\DeclareSIPrefix \hecto { h } { 2 }
\DeclareSIPrefix \kilo { k } { 3 }
\DeclareSIPrefix \mega { M } { 6 }
\DeclareSIPrefix \giga { G } { 9 }
\DeclareSIPrefix \tera { T } { 12 }
\DeclareSIPrefix \peta { P } { 15 }
\DeclareSIPrefix \exa { E } { 18 }
\DeclareSIPrefix \zetta { Z } { 21 }
\DeclareSIPrefix \yotta { Y } { 24 }
\DeclareSIUnit \becquerel { Bq }
\DeclareSIUnit \celsius { \SIUnitSymbolCelsius }
\DeclareSIUnit \degreeCelsius { \SIUnitSymbolCelsius }
\DeclareSIUnit \coulomb { C }
\DeclareSIUnit \farad { F }
\DeclareSIUnit \gray { Gy }
\DeclareSIUnit \hertz { Hz }
\DeclareSIUnit \henry { H }
\DeclareSIUnit \joule { J }
\DeclareSIUnit \katal { kat }
\DeclareSIUnit \lumen { lm }
\DeclareSIUnit \lux { lx }
\DeclareSIUnit \newton { N }
\DeclareSIUnit \ohm { \SIUnitSymbolOhm }
\DeclareSIUnit \pascal { Pa }
\DeclareSIUnit \radian { rad }
\DeclareSIUnit \siemens { S }
\DeclareSIUnit \sievert { Sv }
\DeclareSIUnit \steradian { sr }
\DeclareSIUnit \tesla { T }
\DeclareSIUnit \volt { V }
\DeclareSIUnit \watt { W }
\DeclareSIUnit \weber { Wb }
\DeclareSIUnit [ number-unit-product = ] \arcmin { \arcminute }
\DeclareSIUnit [ number-unit-product = ]
\arcminute { \SIUnitSymbolArcminute }
\DeclareSIUnit [ number-unit-product = ]
\arcsecond { \SIUnitSymbolArcsecond }
\DeclareSIUnit \day { d }
\DeclareSIUnit[ number-unit-product = ] \degree { \SIUnitSymbolDegree }
\DeclareSIUnit \hectare { ha }
\DeclareSIUnit \hour { h }
\DeclareSIUnit \litre { l }
\DeclareSIUnit \liter { L }
\DeclareSIUnit \minute { min }
\DeclareSIUnit \percent { \char 37 }
\DeclareSIUnit \tonne { t }
\DeclareSIUnit \astronomicalunit { ua }
\DeclareSIUnit \atomicmassunit { u }
\DeclareSIUnit \electronvolt { eV }
\DeclareSIUnit \dalton { Da }
%\group_begin:
%\cs_set_eq:NN \endgroup \group_end:
%\char_set_catcode_math_subscript:N \_
%\use:n
% {
% \endgroup
\DeclareSIUnit \clight { \text { \ensuremath { c _ { 0 } } } }
\DeclareSIUnit \electronmass
{ \text { \ensuremath { m _ { \textup { e } } } } }
% }
\DeclareSIUnit \planckbar { \text { \ensuremath { \hbar } } }
\DeclareSIUnit \elementarycharge { \text { \ensuremath { e } } }
%\group_begin:
%\cs_set_eq:NN \endgroup \group_end:
%\char_set_catcode_math_subscript:N \_
%\use:n
% {
% \endgroup
\DeclareSIUnit \bohr { \text { \ensuremath { a _ { 0 } } } }
\DeclareSIUnit \hartree
{ \text { \ensuremath { E _ { \textup { h } } } } }
% }
\DeclareSIUnit \angstrom { \SIUnitSymbolAngstrom }
\DeclareSIUnit \bar { bar }
\DeclareSIUnit \barn { b }
\DeclareSIUnit \bel { B }
\DeclareSIUnit \decibel { \deci \bel }
\DeclareSIUnit \knot { kn }
\DeclareSIUnit \mmHg { mmHg }
\DeclareSIUnit \nauticalmile { M }
\DeclareSIUnit \neper { Np }
\DeclareSIPrePower \square { 2 }
\DeclareSIPostPower \squared { 2 }
\DeclareSIPrePower \cubic { 3 }
\DeclareSIPostPower \cubed { 3 }
EoTeX
sub six_load_compat1 {
RawTeX(<<'EoTeX');
\DeclareSIPrePower \Square { 2 }
\DeclareSIPrePower \ssquare { 2 }
\DeclareSIUnit \BAR { \bar }
\DeclareSIUnit \bbar { \bar }
\DeclareSIUnit \Day { \day }
\DeclareSIUnit \dday { \day }
\DeclareSIUnit \Gray { \gray }
\DeclareSIUnit \ggray { \gray }
\DeclareSIUnit \atomicmass { \atomicmassunit }
\DeclareSIUnit \arcmin { \arcminute }
\DeclareSIUnit \arcsec { \arcsecond }
\DeclareSIUnit \are { a }
\DeclareSIUnit \curie { Ci }
\DeclareSIUnit \gal { Gal }
\DeclareSIUnit \millibar { \milli \bar }
\DeclareSIUnit \rad { rad }
\DeclareSIUnit \rem { rem }
\DeclareSIUnit \roentgen { R }
\DeclareSIUnit \micA { \micro \ampere }
\DeclareSIUnit \micmol { \micro \mole }
\DeclareSIUnit \micl { \micro \litre }
\DeclareSIUnit \micL { \micro \liter }
\DeclareSIUnit \nanog { \nano \gram }
\DeclareSIUnit \micg { \micro \gram }
\DeclareSIUnit \picm { \pico \metre }
\DeclareSIUnit \micm { \micro \metre }
\DeclareSIUnit \Sec { \second }
\DeclareSIUnit \mics { \micro \second }
\DeclareSIUnit \cmc { \centi \metre \cubed }
\DeclareSIUnit \dmc { \deci \metre \cubed }
\DeclareSIUnit \cms { \centi \metre \squared }
\DeclareSIUnit \centimetrecubed { \centi \metre \cubed }
\DeclareSIUnit \centimetresquared { \centi \metre \squared }
\DeclareSIUnit \cubiccentimetre { \centi \metre \cubed }
\DeclareSIUnit \cubicdecimetre { \deci \metre \cubed }
\DeclareSIUnit \squarecentimetre { \centi \metre \squared }
\DeclareSIUnit \squaremetre { \metre \squared }
\DeclareSIUnit \squarekilometre { \kilo \metre \squared }
\DeclareSIUnit \parsec { pc }
\DeclareSIUnit \lightyear { ly }
\DeclareSIUnit \gmol { g \text { - } mol }
\DeclareSIUnit \kgmol { kg \text { - } mol }
\DeclareSIUnit \lbmol { lb \text { - } mol }
\DeclareSIUnit \molar { \mole \per \cubic \deci \metre }
\DeclareSIUnit \Molar { \textsc { m } }
\DeclareSIUnit \torr { Torr }
\DeclareSIUnit \gon { gon }
\DeclareSIUnit \clight { \text { \ensuremath { c } } }
\DeclareSIUnit \micron { \micro \metre }
\DeclareSIUnit \mrad { \milli \rad }
\DeclareSIUnit \gauss { G }
\DeclareSIUnit \eVperc { \eV \per \clight }
\DeclareSIUnit \nanobarn { \nano \barn }
\DeclareSIUnit \picobarn { \pico \barn }
\DeclareSIUnit \femtobarn { \femto \barn }
\DeclareSIUnit \attobarn { \atto \barn }
\DeclareSIUnit \zeptobarn { \zepto \barn }
\DeclareSIUnit \yoctobarn { \yocto \barn }
\DeclareSIUnit \nb { \nano \barn }
\DeclareSIUnit \pb { \pico \barn }
\DeclareSIUnit \fb { \femto \barn }
\DeclareSIUnit \ab { \atto \barn }
\DeclareSIUnit \zb { \zepto \barn }
\DeclareSIUnit \yb { \yocto \barn }
EoTeX
return; }
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1;