# -*- 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> #_# | #
# \=========================================================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; });
DefPrimitive('\latexmltrue', sub {
Warning('unexpected', '\\latexmltrue', $_[0], "Cannot change \\iflatexml!"); });
DefPrimitive('\latexmlfalse', sub {
Warning('unexpected', '\\latexmlfalse', $_[0], "Cannot change \\iflatexml!"); });
#======================================================================
# 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('profiling', sub { AssignValue('PROFILING' => 1, 'global'); });
DeclareOption('noprofiling', sub { AssignValue('PROFILING' => 0, 'global'); });
DeclareOption('mathparserspeculate', sub { AssignValue('MATHPARSER_SPECULATE' => 1, 'global'); });
DeclareOption('nomathparserspeculate', sub { AssignValue('MATHPARSER_SPECULATE' => 0, 'global'); });
ProcessOptions();
#======================================================================
# From latexml.sty
# [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('\BibTeX','BibTeX');
#======================================================================
# 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{}{}', '\@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{}', '\@XMRef{#1}');
#======================================================================
# class related features
Let('\lxAddClass', '\@ADDCLASS');
DefConstructor('\lxAddClass Semiverbatim', sub {
$_[0]->addClass($_[0]->getElement, ToString($_[1])); });
DefConstructor('\lxWithClass Semiverbatim {}', sub {
my ($document, $class, $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);
$document->addClass($nodes[0], ToString($class)) if @nodes; });
#======================================================================
# 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
# \lxResource[options]{name}
# RequireResource($name,%options);
#======================================================================
# 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-para', { 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-para', { class => 'ltx_page_header' }, $whatsit->getBody]);
return; });
DefEnvironment('{lxFooter}', sub { },
beforeDigest => sub { AssignValue(inPreamble => 0); },
beforeConstruct => sub {
my ($document, $whatsit) = @_;
PushValue('navigation',
['ltx:inline-para', { 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 => '');
#======================================================================
# 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) =
$params && map { $_ && ToString($_) } map { $params->getValue($_) } qw(name meaning cd role);
DefMathI($cs, convertLaTeXArgs($nargs, $opt), $presentation,
name => $name, meaning => $meaning, omcd => $cd, role => $role);
});
#DefKeyVal('XMath',name,string);
#======================================================================
# 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', 'wrap', '{}', 1);
DefKeyVal('Declare', 'trace', '{}', 1);
DefKeyVal('Declare', 'replace', 'UndigestedKey');
our $declare_keys = { scope => 1, role => 1, tag => 1, name => 1, meaning => 1,
trace => 1, wrap => 1, replace => 1, label => 1 };
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 (my $tag = $kv->getValue('tag')) {
$document->insertElement('ltx:tag', $tag); }
$document->closeElement('ltx:declare');
$document->setNode($save); } },
mode => 'text',
# Screwy bit to `neutralize' the font in the matches.
beforeDigest => sub { AssignValue(font => LaTeXML::Common::Font->textDefault(), 'local');
AssignValue(mathfont => LaTeXML::Common::Font->mathDefault(), 'local');
return; },
afterDigest => sub { my ($stomach, $whatsit) = @_;
my ($star, $keys, $pattern) = $whatsit->getArgs;
return unless $keys;
CheckOptions("\\lxDeclare keys", $declare_keys, %{ $keys->getKeyVals });
foreach my $key (qw(role tag name meaning replace)) {
if (my $value = $keys->getValue($key)) {
Warn('unexpected', $key, $stomach,
"Repeated $key: " . join('; ', map { Stringify($_) } @$value))
if ref $value eq 'ARRAY'; } }
my $id = ($keys->getValue('tag') ? next_declaration_id() : undef);
if ($id && LookupValue('InPreamble')) {
Warn('unexpected', 'tag', $stomach,
"Declaration with tag cannot appear in preamble"
. Stringify($whatsit)); }
$whatsit->setProperties(scope => getDeclarationScope($keys),
role => ToString($keys->getValue('role')),
name => ToString($keys->getValue('name')),
meaning => ToString($keys->getValue('meaning')),
trace => defined $keys->getValue('trace'),
wrap => defined $keys->getValue('wrap'),
id => $id,
match => $pattern,
replace => $keys->getValue('replace'));
if (my $label = ToString($keys->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 => '');
sub next_declaration_id {
StepCounter('@XMDECL');
DefMacroI(T_CS('\@@XMDECL@ID'), undef,
Tokens(Explode(LookupValue('\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 && LookupValue("\\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, $wrap, $role, $name, $meaning, $ref, $trace, $replace)
= map { $props{$_} } qw(id match wrap role name meaning ref trace replace);
### print STDERR "REWRITER: scope=>$scope, match=>$match, role=>$role, dec_id=>$id\n";
# 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) : ()),
($wrap ? (wrap => $wrap) : ()),
($replace
? (replace => $replace)
: attributes => { ($role ? (role => $role) : ()),
($name ? (name => $name) : ()),
($meaning ? (meaning => $meaning) : ()),
($id ? (dec_id => $id) : ()),
}),
));
return; }
#======================================================================
1;