# -*- mode: Perl -*-
# /=====================================================================\ #
# | latexml.ltxml | #
# | Style file for latexml documents | #
# |=====================================================================| #
# | 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;
no warnings 'redefine'; # ????
#======================================================================
# LaTeXML Implementation of latexml customization bindings
# * controlling various conversion options
# * presentation customization
# * semantic enhancement macros
# * exposing internal functionality
#======================================================================
DefConditional('\iflatexml', sub { 1; });
#======================================================================
# Package Options
DeclareOption('ids', sub { AssignValue('GENERATE_IDS' => 1, 'global'); });
DeclareOption('noids', sub { AssignValue('GENERATE_IDS' => 0, 'global'); });
DeclareOption('comments', sub { AssignValue('INCLUDE_COMMENTS' => 1, 'global'); });
DeclareOption('nocomments', sub { AssignValue('INCLUDE_COMMENTS' => 0, 'global'); });
DeclareOption('tracing', sub {
AssignValue(TRACING => (LookupValue('TRACING') || 0) | TRACE_ALL); });
DeclareOption('notracing', sub {
AssignValue(TRACING => (LookupValue('TRACING') || 0) & ~TRACE_ALL); });
DeclareOption('profiling', sub {
AssignValue(TRACING => (LookupValue('TRACING') || 0) | TRACE_PROFILE); });
DeclareOption('noprofiling', sub {
AssignValue(TRACING => (LookupValue('TRACING') || 0) & ~TRACE_PROFILE); });
DeclareOption('mathparserspeculate', sub { AssignValue('MATHPARSER_SPECULATE' => 1, 'global'); });
DeclareOption('nomathparserspeculate', sub { AssignValue('MATHPARSER_SPECULATE' => 0, 'global'); });
DeclareOption('guesstabularheaders', sub { AssignValue(GUESS_TABULAR_HEADERS => 1, 'global'); });
DeclareOption('noguesstabularheaders', sub { AssignValue(GUESS_TABULAR_HEADERS => 0, 'global'); });
# 'nobibtex' intended to be used for arXiv-like build harnesses, where there
# is explicit instruction to only use ".bbl" and that bibtex will not be ran.
DeclareOption('bibtex', sub { AssignValue('NO_BIBTEX' => 0, 'global'); });
DeclareOption('nobibtex', sub { AssignValue('NO_BIBTEX' => 1, 'global'); });
# Lexeme serialization for math formulas
DeclareOption('mathlexemes', sub { AssignValue('LEXEMATIZE_MATH' => 1, 'global'); });
# Finer control over which (if any) raw .sty/.cls files to include
DeclareOption('rawstyles', sub { AssignValue('INCLUDE_STYLES' => 1, 'global'); });
DeclareOption('localrawstyles', sub { AssignValue('INCLUDE_STYLES' => 'searchpaths', 'global'); });
DeclareOption('norawstyles', sub { AssignValue('INCLUDE_STYLES' => 0, 'global'); });
DeclareOption('rawclasses', sub { AssignValue('INCLUDE_CLASSES' => 1, 'global'); });
DeclareOption('localrawclasses', sub { AssignValue('INCLUDE_CLASSES' => 'searchpaths', 'global'); });
DeclareOption('norawclasses', sub { AssignValue('INCLUDE_CLASSES' => 0, 'global'); });
# Avoid extra line-breaks in UnTeX
DeclareOption('breakuntex', sub { AssignValue('SUPPRESS_UNTEX_LINEBREAKS' => 0, 'global'); });
DeclareOption('nobreakuntex', sub { AssignValue('SUPPRESS_UNTEX_LINEBREAKS' => 1, 'global'); });
DefConstructor('\lx@save@parameter{}{}', sub {
$_[0]->insertPI('latexml', ToString($_[1]) => $_[2]); });
DefKeyVal('LTXML', 'dpi', 'Number', '', code => sub {
$STATE->assignValue(DPI => ToString($_[1]));
AtBeginDocument(Tokens(T_CS('\lx@save@parameter'), T_OTHER('DPI'), T_BEGIN, $_[1], T_END)); });
DefKeyVal('LTXML', 'magnify', 'Number', '', code => sub {
AtBeginDocument(Tokens(T_CS('\lx@save@parameter'), T_OTHER('magnifiy'), T_BEGIN, $_[1], T_END)); });
DefKeyVal('LTXML', 'upsample', 'Number', '', code => sub {
AtBeginDocument(Tokens(T_CS('\lx@save@parameter'), T_OTHER('upsample'), T_BEGIN, $_[1], T_END)); });
DefKeyVal('LTXML', 'zoomout', 'Number', '', code => sub {
AtBeginDocument(Tokens(T_CS('\lx@save@parameter'), T_OTHER('zoomout'), T_BEGIN, $_[1], T_END)); });
DefKeyVal('LTXML', 'tokenlimit', 'Number', '', code => sub {
$LaTeXML::TOKEN_LIMIT = int(ToString($_[1]));
return; });
DefKeyVal('LTXML', 'iflimit', 'Number', '', code => sub {
$LaTeXML::IF_LIMIT = int(ToString($_[1]));
return; });
DefKeyVal('LTXML', 'absorblimit', 'Number', '', code => sub {
$LaTeXML::ABSORB_LIMIT = int(ToString($_[1]));
return; });
DefKeyVal('LTXML', 'pushbacklimit', 'Number', '', code => sub {
$LaTeXML::PUSHBACK_LIMIT = int(ToString($_[1]));
return; });
ProcessOptions(inorder => 1, keysets => ['LTXML']);
#======================================================================
# From latexml.sty
# Making these all be links, every time, seems in hindsight rather obnoxious.
# OTOH, would be nice to have an idiom to make (some of) them be links; or only the first one?
# [does this really belong here? or should this be disableable?]
DefConstructor('\URL[] Verbatim', "<ltx:ref href='#href'>?#1(#1)(#href)</ltx:ref>",
properties => sub { (href => CleanURL($_[2])); });
DefMacro('\XML', '\textsc{xml}'); # '\URL[\texttt{XML}]{http://www.w3.org/XML/}');
DefMacro('\SGML', '\textsc{sgml}'); # '\URL[\texttt{HTML}]{http://www.w3.org/MarkUp/SGML/}');
DefMacro('\HTML', '\textsc{html}'); #'\URL[\texttt{HTML}]{http://www.w3.org/html/}');
DefMacro('\XHTML', '\textsc{xhtml}'); #'\URL[\texttt{XHTML}]{http://www.w3.org/TR/xhtml11/}');
DefMacro('\XSLT', '\textsc{xslt}'); # '\URL[\texttt{XSLT}]{http://www.w3.org/Style/XSL/}');
DefMacro('\CSS', '\textsc{css}'); #'\URL[\texttt{CSS}]{http://www.w3.org/Style/CSS/}');
DefMacro('\MathML', '\texttt{MathML}'); # '\URL[\texttt{MathML}]{http://www.w3.org/Math/}');
DefMacro('\OpenMath', '\texttt{OpenMath}'); # '\URL[\texttt{OpenMath}]{http://www.openmath.org/}');
##DefMacro('\LaTeXML', '\URL[\texttt{LaTeXML}]{http://dlmf.nist.gov/LaTeXML/}');
#DefMacro('\BibTeX','BibTeX');
# Link is maybe a bit pushy? (by default)
#DefMacro('\LaTeXML', '\URL[\LaTeXML@logo]{http://dlmf.nist.gov/LaTeXML/}');
DefMacro('\LaTeXML', '\LaTeXML@logo');
DefConstructor('\LaTeXML@logo',
"<ltx:text class='ltx_LaTeXML_logo'>"
. "<ltx:text cssstyle='letter-spacing:-0.2em; margin-right:0.1em'>"
. "L"
. "<ltx:text cssstyle='font-variant:small-caps;' yoffset='0.4ex'>a</ltx:text>"
. "T"
. "<ltx:text cssstyle='font-variant:small-caps;font-size:120%' yoffset='-0.2ex'>e</ltx:text>"
. "</ltx:text>"
. "<ltx:text cssstyle='font-size:90%' yoffset='-0.2ex'>XML</ltx:text>"
. "</ltx:text>",
sizer => sub { (Dimension('3.8em'), Dimension('1.6ex'), Dimension('0.4ex')); });
DefMacro('\LaTeXMLversion', sub { ExplodeText($LaTeXML::VERSION); });
DefMacro('\LaTeXMLrevision', sub { ExplodeText($LaTeXML::Version::REVISION); });
DefMacro('\LaTeXMLfullversion',
'\LaTeXML (\LaTeXMLversion\expandafter\ifx\expandafter.\LaTeXMLrevision.\else; rev.~\LaTeXMLrevision\fi)');
#======================================================================
# id related features
# Set the id to used for the top-level document
DefMacro('\lxDocumentID{}', '\def\thedocument@ID{#1}');
# \LXMID{id}{math} Associate the identifier id with the given math expression.
DefMacro('\LXMID{}{}', '\lx@xmarg{#1}{#2}');
# \LXRef{id} Refer to the math expression associated with id.
# In presentation, this is similar to using a shorthand macro.
# In content situations, an XMRef is generated.
DefMacro('\LXMRef{}', '\lx@xmref{#1}');
#======================================================================
# Augmenting & Annotating features
# \lxRegisterNamespace{prefix}{namespace-uri}
# Registers an XML namespace that can be used for foreign attributes in the document.
DefPrimitive('\lxRegisterNamespace {} Semiverbatim', sub {
my ($stomach, $prefix, $namespaceuri) = @_;
RegisterNamespace(ToString($prefix) => ToString($namespaceuri));
return; });
# class related features
DefConstructor('\lxAddClass Semiverbatim', sub {
$_[0]->addClass($_[0]->getElement, ToString($_[1])); });
# Add $box to the document, returning a node that can be annotated (attributes)
# This may wrap the $box in an ltx:text if necessary.
sub getAnnotatableNode {
my ($document, $box) = @_;
my $context = $document->getElement; # Where we originally start inserting.
my @nodes = ();
if (isTextNode($document->getNode)) {
push(@nodes, $document->openElement('ltx:text')); }
push(@nodes, $document->absorb($box));
@nodes = $document->filterChildren($document->filterDeletions(@nodes));
$document->closeToNode($context);
return $nodes[0]; }
DefConstructor('\lxWithClass Semiverbatim {}', sub {
my ($document, $class, $box) = @_;
if (my $node = getAnnotatableNode($document, $box)) {
$document->addClass($node, ToString($class)); } });
# Data extensions
# Add attributes (from keyvals) to a $node, or float to appropriate node if undef
# If a key does not have a namespace prefix, assume data: ???????
# If it starts with ":" it has no namespace; be careful of conflicst with LaTeXML's attributes!
sub addAnnotations {
my ($document, $node, %keyvals) = @_;
my $floating = !defined $node;
foreach my $key (keys %keyvals) {
my $value = $keyvals{$key};
$key = ToString($key);
if ($key =~ s/^://) { } # leading : means NO namespace
elsif ($key =~ /:/) { } # embedded : means user namespace
else { $key = 'data:' . $key; } # otherwise, assume data:
$node = $document->floatToAttribute($key) if $floating;
$document->setAttribute($node, $key => $value); }
return; }
# The following add annotation data attributes to a node.
# The annnotations are given by the key=value pairs in kv,
# with the key being in the "data" namespace, unless it has a namespace prefix
# which was presumably registered using \lxRegisterNamespace
# \lxAddAnnotation{kv} Add annotation to the current containing node that can accept them
DefConstructor('\lxAddAnnotation RequiredKeyVals', sub {
my ($document, $kv, $thing) = @_;
my $savenode = $document->getNode;
addAnnotations($document, undef, $kv->getHash);
$document->setNode($savenode);
return; });
# \lxWithAnnotation{kv}{box} Add box, with annottions.
DefConstructor('\lxWithAnnotation RequiredKeyVals {}', sub {
my ($document, $kv, $box) = @_;
if (my $node = getAnnotatableNode($document, $box)) {
addAnnotations($document, $node, $kv->getHash); }
return; });
#======================================================================
# links
# Similar to stuff from hyperref, but more straightforward
DefConstructor('\lxRef Semiverbatim {}',
"<ltx:ref labelref='#label'>#2</ltx:ref>",
properties => sub { (label => CleanLabel($_[1])); });
#======================================================================
# Resources
# \lxRequireResource[options]{name}
# options: type (mime-type), media (?), ...
DefPrimitive('\lxRequireResource OptionalKeyVals {}', sub {
my ($stomach, $kv, $path) = @_;
RequireResource(ToString($path), ($kv ? $kv->getHash : ())); });
#======================================================================
# Page customization
# options to create or customize
# navbar: full content, context TOC, ...
# headers, footers
DefMacro('\lxKeywords{}',
'\@add@frontmatter{ltx:keywords}[name={keywords}]{#1}');
DefConstructor('\lxContextTOC',
"<ltx:TOC format='context'/>");
AssignValue('navigation' => [], 'global');
sub insertNavigation {
my ($document) = @_;
if (my @items = @{ LookupValue('navigation') }) {
$document->appendTree($document->getDocument->documentElement,
['ltx:navigation', {}, @items]); }
return; }
Tag('ltx:document', 'afterClose' => \&insertNavigation);
DefEnvironment('{lxNavbar}', sub { },
beforeDigest => sub { AssignValue(inPreamble => 0); },
beforeConstruct => sub {
my ($document, $whatsit) = @_;
PushValue('navigation',
['ltx:inline-logical-block', { class => 'ltx_page_navbar' }, $whatsit->getBody]);
return; });
# Of course, it would be more interesting to supply a "template"
# for header & footer that would show where the next link goes,
# rather than predict what the next link will be! (after splitting!)
# Repeated header/footers should give multiple header/footer lines ?
# or do they just arrange the lines within it?
DefEnvironment('{lxHeader}', sub { },
beforeDigest => sub { AssignValue(inPreamble => 0); },
beforeConstruct => sub {
my ($document, $whatsit) = @_;
PushValue('navigation',
['ltx:inline-logical-block', { class => 'ltx_page_header' }, $whatsit->getBody]);
return; });
DefEnvironment('{lxFooter}', sub { },
beforeDigest => sub { AssignValue(inPreamble => 0); },
beforeConstruct => sub {
my ($document, $whatsit) = @_;
PushValue('navigation',
['ltx:inline-logical-block', { class => 'ltx_page_footer' }, $whatsit->getBody]);
return; });
#======================================================================
# Table beautification.
# Low-level support to mark column and row headers.
# This really calls for styling, but why should we get into that game?
# There are many other packages for that.
# To mark the table head/foot (table column headers)
# Put this before first table heading row
DefMacroI('\lxBeginTableHead', undef, '\@tabular@begin@heading');
# put this after the \\ ending the table heading.
DefMacroI('\lxEndTableHead', undef, '\@tabular@end@heading');
# Ditto for table foot (last rows in table)
DefMacroI('\lxBeginTableFoot', undef, '\@tabular@begin@heading');
# put this after the \\ ending the table heading.
DefMacroI('\lxEndTableFoot', undef, '\@tabular@end@heading');
# To mark an individual cell as a column header
DefMacroI('\lxTableColumnHead', undef, sub {
if (my $alignment = LookupValue('Alignment')) {
$alignment->currentColumn->{thead}{column} = 1; }
return; });
# To mark an individual cell as a row header
DefMacroI('\lxTableRowHead', undef, sub {
if (my $alignment = LookupValue('Alignment')) {
$alignment->currentColumn->{thead}{row} = 1; }
return; });
# Easy way to mark a whole column as row headers:
# \usepackage{array}
# then put this in the column spec
# >{\lxTableRowHead}
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Declarative information for Mathematics
# particularly those that assist parsing.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#======================================================================
# Marking the type of particular instances of a symbol.
# \LxFcn{f} treat f as a function here (only).
DefConstructor('\lxFcn{}', "<ltx:XMWrap role='FUNCTION'>#1</ltx:XMWrap>",
requireMath => 1, reversion => '#1', alias => '');
DefConstructor('\lxID{}', "<ltx:XMWrap role='ID'>#1</ltx:XMWrap>",
requireMath => 1, reversion => '#1', alias => '');
DefConstructor('\lxPunct{}', "<ltx:XMWrap role='PUNCT'>#1</ltx:XMWrap>",
requireMath => 1, reversion => '#1', alias => '');
# More general form: eg. \lxTweakMath{role=POSTFIX}{@}
# [same as \lx@math@tweak]
DefConstructor('\lxMathTweak RequiredKeyVals {}',
"<ltx:XMWrap %&GetKeyVals(#1)>#2</ltx:XMWrap>",
afterDigest => sub {
my ($stomach, $whatsit) = @_;
my ($kv, $body) = $whatsit->getArgs;
$whatsit->setProperties($kv->getPairs);
$whatsit->setFont($body->getFont);
return; },
reversion => '#2');
#======================================================================
# Define a mathematical object with both presentation & content information
# \lxDefMath{\name}[nargs][optional]{presentation body}[content keywords]
# The first part is essentially equivalent to \newcommand, it defines
# an expansion for \name used for the presentation.
# The content keywords are used to define the semantics of the object.
# See DefMath in LaTeXML::Package for more information.
DefPrimitive('\lxDefMath{}[Number][]{} OptionalKeyVals:XMath', sub {
my ($stomach, $cs, $nargs, $opt, $presentation, $params) = @_;
my ($name, $meaning, $cd, $role, $alias, $scope) =
$params && map { $_ && ToString($_) } map { $params->getValue($_) }
qw(name meaning cd role alias scope);
my $needsid = $params && ($params->getValue('tag') || $params->getValue('description'));
my $id = ($needsid ? next_declaration_id() : undef);
DefMathI($cs, convertLaTeXArgs($nargs, $opt), $presentation,
name => $name, meaning => $meaning, omcd => $cd, role => $role, alias => $alias,
scope => $scope, decl_id => $id, revert_as => 'context');
if ($needsid) { # Also provide for decl_id hook for definition links.
return Digest(Invocation('\@lxDefMathDeclare', $id, $params)); }
else {
return; }
});
DefConstructor('\@lxDefMathDeclare{} RequiredKeyVals:XMath', sub {
my ($document, $id, $kv, %props) = @_;
my $save = $document->floatToElement('ltx:declare');
$document->openElement('ltx:declare', 'xml:id' => $id);
if ($props{term} || $props{short}) {
$document->openElement('ltx:tags');
$document->insertElement('ltx:tag', $props{term}, role => 'term') if $props{term};
$document->insertElement('ltx:tag', $props{short}, role => 'short') if $props{short};
$document->closeElement('ltx:tags'); }
if (my $description = $props{description}) {
$document->insertElement('ltx:text', $description); }
$document->closeElement('ltx:declare');
$document->setNode($save); },
mode => 'text',
afterDigest => sub { my ($stomach, $whatsit) = @_;
my ($id, $kv) = $whatsit->getArgs;
normalizeDeclareKeys($kv, $whatsit);
return; },
properties => { alignmentSkippable => 1 },
reversion => '');
# We're interested in getting some useful phrases for several contexts:
# Notations lists: $term$ : description
# [where layout may require substituting some markup for the :, so we'll want to split them]
# tooltips: short (name), probably text preferred.
# And we want to synthesize these out of two keyvals: tag,description.
# If both are given tag is shortname, description is long form, possibly separating out the term
# If only 1 given, and matches the math term: record it, and use remainder for name & description
# Else use the given for name, description, with no term
sub normalizeDeclareKeys {
my ($kv, $whatsit) = @_;
my $tag = $kv->getValue('tag');
my $description = $kv->getValue('description');
my ($term, $short, $desc);
if (my $stuff = $description || $tag) {
($term, $desc) = splitDeclareTag($stuff); }
$short = ($description ? $tag || $desc : undef);
$desc = $desc || $description || $tag;
$whatsit->setProperties(term => $term, short => $short, description => $desc);
return; }
# Temporary(?) Hack for DLMF: Split an \lxDeclare tag of the form tag={math: description}
sub splitDeclareTag {
my ($tag) = @_;
my @boxes = $tag->unlist;
my @tag = ();
# Or should we collect initial math box?
while (@boxes && ($boxes[0]->getString ne ':')) {
push(@tag, shift(@boxes)); }
if (@boxes) {
shift(@boxes);
return (List(@tag), List(@boxes)); }
else {
return; } }
#======================================================================
# NOTE: I'm concerned about the order of applying these filters.
# even though it seems right so far.
# Keyword options:
# scope=<scope> : Specifies the scope of the declaration, ie. to what portion of
# the document the declarations apply
# You can specify one of the counters associated with sections,
# equations, etc.
# If unspecified, the declaration is scoped to the current unit.
# Note that this applies to equations, as well.
# label=<label> : assigns a label to the declaration so that it can be reused
# at another point in the document (with \lxRefDeclaration), particularly when
# that point is not otherwise within the scope of the original declaration.
# To effect the declaration:
# role=<role> : Assigns a grammatical role to the matched item for parsing.
# name=<name> : Assigns a name to the matched item.
# meaning=<meaning> : Assigns a semantic name to the matched item.
# Alternatively, use
# replace : provides a replacement for the matched expression, rather than adding attributes.
# Potential keywords/operations needed(?)
# nodef : inhibits the marking of the current point as the `definition' of the expression.
# (a ref declaration would normally not be a def anyway)
DefKeyVal('Declare', 'nowrap', '{}', 1);
DefKeyVal('Declare', 'trace', '{}', 1);
DefKeyVal('Declare', 'replace', 'UndigestedKey');
our $declare_keys = { scope => 1, role => 1, tag => 1, description => 1, name => 1, meaning => 1,
trace => 1, nowrap => 1, replace => 1, label => 1 };
# Most is same as above; merge into one!!!!!
DefConstructor('\lxDeclare OptionalMatch:* OptionalKeyVals:Declare {}', sub {
my ($document, $flag, $kv, $pattern, %props) = @_;
if (my $id = $props{id}) {
my $save = $document->floatToElement('ltx:declare');
$document->openElement('ltx:declare', 'xml:id' => $id);
if ($props{term} || $props{short}) {
$document->openElement('ltx:tags');
$document->insertElement('ltx:tag', $props{term}, role => 'term') if $props{term};
$document->insertElement('ltx:tag', $props{short}, role => 'short') if $props{short};
$document->closeElement('ltx:tags'); }
if (my $description = $props{description}) {
$document->insertElement('ltx:text', $description); }
$document->closeElement('ltx:declare');
$document->setNode($save); } },
mode => 'text',
beforeDigest => sub { reenterTextMode(); neutralizeFont(); },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my ($star, $kv, $pattern) = $whatsit->getArgs;
return unless $kv;
CheckOptions("\\lxDeclare keys", $declare_keys, %{ $kv->getKeyVals });
foreach my $key (qw(role tag name meaning replace)) {
if (my $value = $kv->getValue($key)) {
Warn('unexpected', $key, $stomach,
"Repeated $key: " . join('; ', map { Stringify($_) } @$value))
if ref $value eq 'ARRAY'; } }
my $id = ($kv->getValue('tag') || $kv->getValue('description') ? next_declaration_id() : undef);
if ($id && LookupValue('InPreamble')) {
Warn('unexpected', 'tag', $stomach,
"Declaration with tag cannot appear in preamble"
. Stringify($whatsit)); }
# Temporary(?) Hack: If no description, bui tag is of form <math>: text
# make description = tag, and tag be only the shorter text part
$whatsit->setProperties(scope => getDeclarationScope($kv),
role => ToString($kv->getValue('role')),
name => ToString($kv->getValue('name')),
meaning => ToString($kv->getValue('meaning')),
trace => defined $kv->getValue('trace'),
nowrap => defined $kv->getValue('nowrap'),
id => $id,
match => $pattern,
replace => $kv->getValue('replace'));
normalizeDeclareKeys($kv, $whatsit);
if (my $label = ToString($kv->getValue('label'))) {
PushValue("Declaration_$label", $whatsit); }
return; },
afterConstruct => sub { my ($document, $whatsit) = @_;
my $scope = $whatsit->getProperty('scope');
createDeclarationRewrite($document, $scope, $whatsit); },
properties => { alignmentSkippable => 1 },
reversion => '');
DefConstructor('\lxRefDeclaration OptionalKeyVals:Declare {}', '',
afterDigest => sub { my ($stomach, $whatsit) = @_;
my ($keys, $labels) = $whatsit->getArgs;
$whatsit->setProperties(scope => getDeclarationScope($keys),
labels => [split(',', ToString($labels))]); },
afterConstruct => sub { my ($document, $whatsit) = @_;
my $scope = $whatsit->getProperty('scope');
foreach my $label (@{ $whatsit->getProperty('labels') }) {
if (my $declaration = LookupValue("Declaration_$label")) {
map { createDeclarationRewrite($document, $scope, $_) } @$declaration; }
else {
Warn('unexpected', $label, $document,
"No Declaration with label=$label was found"); } } },
properties => { alignmentSkippable => 1 },
reversion => '');
NewCounter('@XMDECL', 'section', idprefix => 'XMD');
sub next_declaration_id {
StepCounter('@XMDECL');
DefMacroI(T_CS('\@@XMDECL@ID'), undef,
Tokens(Explode(LookupRegister('\c@@XMDECL')->valueOf)),
scope => 'global');
return ToString(Expand(T_CS('\the@XMDECL@ID'))); }
sub getDeclarationScope {
my ($keys) = @_;
# Sort out the scope.
my $scope = $keys && $keys->getValue('scope');
$scope = ($scope ? ToString($scope) : LookupValue('current_counter'));
if ($scope && LookupRegister("\\c\@$scope")) { # Scope is some counter.
$scope = "id:" . ToString(Digest(Expand(T_CS("\\the$scope\@ID")))); }
return $scope; }
sub createDeclarationRewrite {
my ($document, $scope, $whatsit) = @_;
my %props = $whatsit->getProperties;
my ($id, $match, $nowrap, $role, $name, $meaning, $ref, $trace, $replace)
= map { $props{$_} } qw(id match nowrap role name meaning ref trace replace);
# Put this rule IN FRONT of other rules!
UnshiftValue('DOCUMENT_REWRITE_RULES',
LaTeXML::Core::Rewrite->new('math',
($trace ? (trace => $trace) : ()),
($scope ? (scope => $scope) : ()),
($match ? (match => $match) : ()),
($replace
? (replace => $replace)
: attributes => { ($role ? (role => $role) : ()),
($name ? (name => $name) : ()),
($meaning ? (meaning => $meaning) : ()),
($id ? (decl_id => $id) : ()),
($nowrap ? (_nowrap => $nowrap) : ()),
}),
));
return; }
#======================================================================
1;