Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[deprecated] MathML Accessibility Annotations #1305

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
275dc64
first experiment with accessibility annotations
dginev Jul 6, 2020
0191377
also handle a basic dual
dginev Jul 6, 2020
da078da
stacked duals example
dginev Jul 6, 2020
0e530d1
more precise xmdual annotations
dginev Jul 6, 2020
f89c574
for demo, use data-* attributes as per HTML
dginev Jul 7, 2020
02c6c37
also use the most precise current_node info; duals and wraps work wel…
dginev Jul 8, 2020
a56ac44
fragid as primary arg info source; coordinate sourcenode and currentn…
dginev Jul 8, 2020
a7e867c
thanks to Neil Soiffer for spotting the annotations Fatal for failed-…
dginev Jul 9, 2020
d4b4311
fragid-based approach to duals, significant refactor
dginev Jul 9, 2020
dce2cdd
correctly use xml:id instead of fragid, thanks for the correction Bruce!
dginev Jul 9, 2020
0ede129
also handle duals in apply
dginev Jul 9, 2020
765bc3f
switch to using # instead of @ for referring to args
dginev Jul 9, 2020
653d2d4
adapt Bruce's useful memory trick with an attribute marking XMath nod…
dginev Jul 10, 2020
4c950cf
now under 100 lines of code, with straightened out logic routes and m…
dginev Jul 10, 2020
5afea50
add experimental a11ymark binding for web showcase, e.g. \integral
dginev Jul 10, 2020
28d101c
return to never annotating Apply nodes with unknown meaning
dginev Jul 10, 2020
8d84160
use more of Bruce's approach to mark used nodes for easier reasoning …
dginev Jul 10, 2020
8e1ea78
always call into ltx:XMDual node to get arg
dginev Jul 10, 2020
3bfe086
one approach to implementing \power macro
dginev Jul 11, 2020
77cf03e
one way to do embellished atoms and bases
dginev Jul 11, 2020
eeac88b
conquer \derivenum{f}{2}
dginev Jul 11, 2020
b9df85e
undo modding NewScript, leave as-is and instead focus on pruning the …
dginev Jul 13, 2020
4ec4766
stupid mistake with lexical scope of $index
dginev Jul 15, 2020
c1df911
working \fnderive{f}{2} using \DUAL and DefMath
dginev Jul 15, 2020
88a3bc2
subtle a11y - hide the switch in a11ymark.sty, disabled for regular l…
dginev Jul 15, 2020
a8aa3e8
\fndegree handles arbitrary degree expressions
dginev Jul 15, 2020
81df249
refactor until a macro layer feels usable, switch \adjoint \transpose…
dginev Jul 16, 2020
553ef5b
crosswise presentation and semantic tree structures, two examples better
dginev Jul 16, 2020
be48a37
typo in dualize_arglist, we now have 3
dginev Jul 16, 2020
78abadd
fish out idrefs in arbitrary depths within dual, compute lvl
dginev Jul 16, 2020
75412e7
recognize arg annotation case for XMRef-only content branches of duals
dginev Jul 16, 2020
6d3ee96
typo in refactoring, guard the "data-arg" in final pmml, via _a11y at…
dginev Jul 16, 2020
0c10ef1
cleanup naming, another rough edge logic bug while refactoring
dginev Jul 16, 2020
7a5fe4b
adding functional power, Laplacian, inverse
dginev Jul 16, 2020
afcded9
add: norm, determinant, inner-product, legendre symbol
dginev Jul 16, 2020
1e65ac7
introduce \pragma as an ergonomic frontend for lxDeclare
dginev Aug 4, 2020
cd56742
curried BesselJ via a pragma
dginev Aug 4, 2020
485d4f7
experiment with pre/post actions for pragmas/lxDeclare
dginev Aug 5, 2020
cc17c35
comment out pragmas, move them to showcase
dginev Aug 6, 2020
5661de3
better a11y annotation treatment for XMapp with meaning attr
dginev Aug 12, 2020
c2dc9e0
cleaner rewrite from scratch of accessibility traversal
dginev Aug 14, 2020
620ea05
compact simple XMDual of two XMToks
dginev Aug 14, 2020
a7616cd
transition away from the artificial level count, just do child counti…
dginev Aug 14, 2020
2291bbe
also handle single XMTok duals
dginev Aug 14, 2020
5c433ea
avoid marking up special markup pmml with no given meaning
dginev Aug 14, 2020
1b5936a
tighten handler logic. Handles almost all examples in a11y showcase, …
dginev Aug 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ lib/LaTeXML/Package/PoS.cls.ltxml
lib/LaTeXML/Package/TeX.pool.ltxml
lib/LaTeXML/Package/a0poster.cls.ltxml
lib/LaTeXML/Package/a0size.sty.ltxml
lib/LaTeXML/Package/a11ymark.sty.ltxml
lib/LaTeXML/Package/a4.sty.ltxml
lib/LaTeXML/Package/a4wide.sty.ltxml
lib/LaTeXML/Package/aa.cls.ltxml
Expand Down
47 changes: 44 additions & 3 deletions lib/LaTeXML/Core/Rewrite.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use LaTeXML::Global;
use LaTeXML::Common::Object;
use LaTeXML::Common::Error;
use LaTeXML::Common::XML;
use LaTeXML::Core::Token qw(T_CS T_MATH);
use LaTeXML::Core::Tokens qw(Tokens);

sub new {
my ($class, $mode, @specs) = @_;
Expand Down Expand Up @@ -143,8 +145,8 @@ sub applyClause {

# Now make any adjustments to the new nodes
map { $document->recordNodeIDs($_) } @inserted;
my $font = $document->getNodeFont($tree); # the font of the matched node
foreach my $ins (@inserted) { # Copy the non-semantic parts of font to the replacement
my $font = $document->getNodeFont($tree); # the font of the matched node
foreach my $ins (@inserted) { # Copy the non-semantic parts of font to the replacement
$document->mergeNodeFontRec($ins => $font); }
# Now, replace the following nodes.
map { $parent->appendChild($_) } @following; }
Expand Down Expand Up @@ -178,6 +180,31 @@ sub applyClause {
Error('misdefined', '<rewrite>', undef, "Unknown directive '$op' in Compiled Rewrite spec"); }
return; }

## EXPERIMENTAL: This is an early experiment and needs to be refactored before it can be considered for serious use
sub action_insert {
my ($document, $direction, $extra, $tree) = @_;
my $anchor;
if ($direction eq 'pre') {
$anchor = $tree->previousSibling; }
elsif ($direction eq 'post') {
$anchor = $tree->nextSibling; }
if ($anchor) { # What should we do if no anchor? Skip?
# Carry out the operation, inserting whatever nodes.
my $parent = $anchor->parentNode;
my $end_mark = $parent->lastChild;
$document->setNode($parent);
&$extra($document);
my @inserted = ();
my @children = $parent->childNodes;
while (my $child = pop @children) {
last unless ($$child != $$end_mark);
$child->unbindNode;
push @inserted, $child; }
for my $newchild (@inserted) {
$parent->insertAfter($newchild, $anchor);
$document->recordNodeIDs($newchild); } }
return; }

# Set attributes for an encapsulated tree (ie. a decorated symbol as symbol itself)
sub setAttributes_encapsulate {
my ($document, $attributes, @nodes) = @_;
Expand Down Expand Up @@ -321,6 +348,20 @@ sub compileClause {
if (ref $pattern eq 'CODE') { }
else {
$pattern = $self->compile_replacement($document, $pattern); } }
elsif ($op eq 'action') {
if (ref $pattern eq 'CODE') { }
# HACK: it appears this is a stage already **too late** to handle pre/post directive parsing
# maybe what I should consider instead is having a "pre:action" and "post:action" KEY
# which can be parsed via $op, keeping $pattern handled identically to the 'replace' case?
elsif (ToString($pattern) =~ /^(pre|post)[:].(.+)$/) {
my $direction = $1;
my $extra = $self->compile_replacement($document, Tokens(T_MATH, T_CS("\\$2"), T_MATH));
$pattern = sub {
my ($tree) = @_;
action_insert($document, $direction, $extra, $tree); } }
else {
Fatal('misdefined', '<rewrite>', undef,
"Can't generate 'action' for arbitrary tokens.", ToString($pattern)); } }
elsif ($op eq 'regexp') {
$pattern = $self->compile_regexp($pattern); }
print STDERR "Compiled clause $oop=>" . ToString($opattern) . " ==> $op=>" . ToString($pattern) . "\n"
Expand Down Expand Up @@ -532,7 +573,7 @@ sub domToXPath_seq {

__END__

=pod
=pod

=head1 NAME

Expand Down
19 changes: 18 additions & 1 deletion lib/LaTeXML/MathParser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ our @EXPORT_OK = (qw(&Lookup &New &Absent &Apply &ApplyNary &recApply &CatSymbol
&LeftRec
&Arg &MaybeFunction
&SawNotation &IsNotationAllowed
&isMatchingClose &Fence));
&isMatchingClose &Fence
&p_getAttribute &p_setAttribute &p_removeAttribute &p_element_nodes));
our %EXPORT_TAGS = (constructors
=> [qw(&Lookup &New &Absent &Apply &ApplyNary &recApply &CatSymbols
&Annotate &InvisibleTimes &InvisibleComma
Expand Down Expand Up @@ -1054,6 +1055,22 @@ sub p_getAttribute {
elsif (ref $item eq 'XML::LibXML::Element') {
return $item->getAttribute($key); } }

sub p_setAttribute {
my ($node, $key, $value) = @_;
if (ref $node eq 'ARRAY') {
$$node[1]{$key} = $value; }
else {
$node->setAttribute($key => $value); }
return; }

sub p_removeAttribute {
my ($node, $key) = @_;
if (ref $node eq 'ARRAY') {
delete $$node[1]{$key}; }
else {
$node->removeAttribute($key); }
return; }

sub p_element_nodes {
my ($item) = @_;
if (!defined $item) {
Expand Down
255 changes: 255 additions & 0 deletions lib/LaTeXML/Package/a11ymark.sty.ltxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
# -*- mode: Perl -*-
# /=====================================================================\ #
# | a11ymark.sty -- demo semantic bindings for accessibility | #
# | 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 <[email protected]> #_# | #
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;

RequirePackage('latexml');

DefConstructorI(T_CS('\@request@math@a11y'), undef, "<?latexml a11y='enabled'?>");
AtBeginDocument(T_CS('\@request@math@a11y'));

# NOTE: demonstration-oriented binding, all names and definitions subject to change without notice.

# Embellishment is hard to write, hard to speak, but describes exactly several cases
# I will abbreviate it "emb", for now, and use it as a prefix

# \emb@atom{meaning}{presentation}
DefMacro('\emb@atom{}{}', '\DUAL[hide_content_reversion=true]{\@CSYMBOL{#1}}{\@WRAP{#2}}');
DefMacro('\emb@build@apply{}{}', '\DUAL[hide_content_reversion=true]{\@APPLY{#1}}{\@WRAP{#2}}');

sub emb_apply {
my ($gullet, $base, $meaning, $emb, $invert_to_prefix) = @_;
my ($cargs, $pargs) = dualize_arglist('#1', $base);
my $ref_base = $$cargs[0];
my $arg_base = $$pargs[0];
my $presentation = $invert_to_prefix ? Tokens($emb, $arg_base) : Tokens($arg_base, $emb);
return Invocation(T_CS('\emb@build@apply'),
Tokens(Invocation(T_CS('\@CSYMBOL'), $meaning), $ref_base),
$presentation)->unlist; }

sub emb_apply_two { # one-or-two operations, can't fully reuse the simple case...
my ($gullet, $base, $op1_meaning, $op1_pres, $op2_meaning, $op2_pres, $invert_to_prefix) = @_;
if (!$op2_meaning && !$op2_pres) { # one operation, use the simple apply call
return emb_apply($gullet, $base, $op1_meaning, $op1_pres, $invert_to_prefix); }
# Case where we have two consecutive operations
my ($cargs, $pargs) = dualize_arglist('#1', $base);
my $ref_base = $$cargs[0];
my $arg_base = $$pargs[0];

my $pres_tokens = $invert_to_prefix ? Tokens($op2_pres, $op1_pres, $arg_base) : Tokens($arg_base, $op1_pres, $op2_pres);
my $presentation = Invocation(T_CS('\@WRAP'), $pres_tokens);
my $content = Invocation(T_CS('\@APPLY'), Invocation(T_CS('\@CSYMBOL'), $op2_meaning),
Invocation(T_CS('\@APPLY'), Invocation(T_CS('\@CSYMBOL'), $op1_meaning), $ref_base));
return Invocation(T_CS('\DUAL'), undef, $content, $presentation)->unlist; }

# Two operators acting on base in sequence, commonly alternate scripts ^m_n.
# \emb@apply{base}{op1 meaning}{op1 pres}[op2 meaning][op2 pres]
DefMacro('\emb@apply{}{}{}[][]', \&emb_apply_two);

# As with \emb@apply, but the presentation is right-to-left prefix "op2_pres op1_pres base"
# \emb@preapply{base}{op1 meaning}{op1 pres}[op2 meaning][op2 pres]
DefMacro('\emb@preapply{}{}{}[][]', sub { emb_apply_two(@_, 1); });

# ADHOC for the very awkward example we have so far.
# and the order of presentation args is inverted, while the semantic one is kept.
# Example \PrePostArgCrosswise{x}{median}{\overline}{index}{_}{i}
DefMacro('\PrePostArgCrosswise{}{}{}{}{}{}', sub {
my ($gullet, $base, $op1_meaning, $op1_pres, $op2_meaning, $op2_pres, $op2_rhs_var) = @_;
my ($cargs, $pargs) = dualize_arglist('#1#2', $base, $op2_rhs_var);
my ($ref_base, $ref_rhs_var) = @$cargs;
my ($arg_base, $arg_rhs_var) = @$pargs;

my $presentation = Tokens(Tokens($op1_pres, $arg_base), $op2_pres, $arg_rhs_var);
my $content = Tokens(Invocation(T_CS('\@CSYMBOL'), $op1_meaning),
Invocation(T_CS('\@APPLY'), Tokens(
Invocation(T_CS('\@CSYMBOL'), $op2_meaning),
$ref_base, $ref_rhs_var)));
return Invocation(T_CS('\emb@build@apply'), $content, $presentation)->unlist; });

# ADHOC - terrible low-level soup macro with 7 arguments,
# just an example of things being possible...
# \PostArgsCrosswise{x}{derivative-implicit-variable}{^}{\derivemark{1}}{index}{_}{i}
DefMacro('\PostArgsCrosswise{}{}{}{}{}{}{}', sub {
my ($gullet, $base, $op1_meaning, $op1_pres, $op1_rhs_var, $op2_meaning, $op2_pres, $op2_rhs_var) = @_;
my ($cargs, $pargs) = dualize_arglist('#1#2#3', $base, $op1_rhs_var, $op2_rhs_var);
my ($ref_base, $ref_rhs_var1, $ref_rhs_var2) = @$cargs;
my ($arg_base, $arg_rhs_var1, $arg_rhs_var2) = @$pargs;

my $presentation = Tokens(Tokens($arg_base, $op1_pres, $arg_rhs_var1), $op2_pres, $arg_rhs_var2);
my $content = Tokens(Invocation(T_CS('\@CSYMBOL'), $op1_meaning),
Invocation(T_CS('\@APPLY'), Tokens(
Invocation(T_CS('\@CSYMBOL'), $op2_meaning), $ref_base, $ref_rhs_var2)),
$ref_rhs_var1);
return Invocation(T_CS('\emb@build@apply'), $content, $presentation)->unlist; });

## I. Calculus
DefConstructor('\diffd', '<ltx:XMTok meaning="differential" name="diffd" role="DIFFOP">d</ltx:XMTok>');
DefMath('\deriv[]{}{}',
'\frac{\@MAYBEAPPLY{\@SUPERSCRIPT{\diffd}{#1}}{#2}}'
. '{\@SUPERSCRIPT{\@APPLY{\diffd #3}}{#1}}',
meaning => 'derivative', reorder => [2, 3, 1],
# afterDigest => sub {
# # NOTE: arg 2 will be wrapped in XMRef!
# $_[1]->setProperty(role => 'DIFFOP') if checkDiffOp($_[1]);
# return; },
hide_content_reversion => 1);

DefMath('\integral{}{}', '\int #1 \diffd #2', meaning => 'integral');

## II. Scripts
DefMacro('\@sup@apply{}{}', sub {
my ($gullet, $base, $script) = @_;
my ($cargs, $pargs) = dualize_arglist('#1#2', $base, $script);
return Invocation(T_CS('\emb@build@apply'),
Tokens($$cargs[1], $$cargs[0]),
Invocation(T_CS('\@SUPERSCRIPT'), @$pargs))->unlist; });
DefMacro('\supop{}{}{}', '\@sup@apply{#1}{\emb@atom{#2}{#3}}');
DefMacro('\@sub@apply{}{}', sub {
my ($gullet, $base, $script) = @_;
my ($cargs, $pargs) = dualize_arglist('#1#2', $base, $script);
return Invocation(T_CS('\emb@build@apply'),
Tokens($$cargs[1], $$cargs[0]),
Invocation(T_CS('\@SUBCRIPT'), @$pargs))->unlist; });
DefMacro('\subop{}{}{}', '\@sub@apply{#1}{\emb@atom{#2}{#3}}');

DefMath('\power{}{}', "{#1^{#2}}", meaning => 'power',
reversion => '#1^{#2}',
hide_content_reversion => 1);
DefMath('\fnpower{}{}', "{#1^{#2}}", meaning => 'functional-power',
reversion => '#1^{#2}', hide_content_reversion => 1);
DefMath('\fninverse{}', "#1^{-1}", meaning => "inverse", role => 'OPFUNCTION',
reversion => '#1^{-1}', hide_content_reversion => 1);
DefMath('\laplacian', '\nabla^2', meaning => 'Laplacian', role => 'OPERATOR',
hide_content_reversion => 1);
DefMath('\index{}{}', "{#1_{#2}}", meaning => 'index',
reversion => '#1_{#2}', hide_content_reversion => 1);

# only mark the script as a dual, so that we can remix it
DefMacro('\indexArg{}', sub {
my ($gullet, $arg) = @_;
my ($cargs, $pargs) = dualize_arglist('#1', $arg);
return Invocation(T_CS('\emb@build@apply'),
Tokens(Invocation(T_CS('\@CSYMBOL'), 'index'), $$cargs[0]),
Tokens(T_SUB, $$pargs[0]))->unlist; });
DefMacro('\supop{}{}{}', '\@sup@apply{#1}{\emb@atom{#2}{#3}}');

DefMacro('\frobulator', '\emb@atom{frobulator}{x\'}');

DefMacro('\transpose{}', '\supop{#1}{transpose}{T}');
DefMacro('\adjoint{}', '\supop{#1}{adjoint}{\dagger}');
# This works well, but can't be remixed crosswise as \median{x}_i:
DefMacro('\median', '\emb@atom{median}{\overline}');

# What I Really Want to Say here, but can't is likely:
# DefMath('\derivemark{}', '\derivemark@pres{#1}', meaning=>'#1');
DefMacro('\derivemark{}', sub {
my ($gullet, $token) = @_;
# Dualizing the arglist only works if we are going to keep the same token at the end
# in the case of 2 --> '' , this fails. So, obtain the presentation right away to figure this out

# we need to digest due to \@XMArg being a constructor
my $mark = ToString(Digest($token));
my ($content, $presentation);
if ($mark =~ /^\d$/) { # single digit, add primes
$content = $token;
$presentation = Tokens(map { T_CS('\prime') } 1 .. int($mark)) }
else {
# assume an id, wrap in parens
my ($cargs, $pargs) = dualize_arglist('#1', $token);
$content = $$cargs[0];
$presentation = Tokens(T_OTHER('('), $$pargs[0], T_OTHER(')')); }

return Invocation(T_CS('\DUAL'),
undef, # debugging that I missed this 'undef' argument was not fun.
$content,
Invocation(T_CS('\@WRAP'), $presentation))->unlist;
});

# curiously we need an indirection level, so that we point to the dual instead of
# the content node of the dual. The a11y attribute generation does not support the following markup
# at the moment:
#
# <XMDual>
# <XMApp>
# <XMTok meaning="derivative-implicit-variable" name="fnderive" role="UNKNOWN"/>
# <XMRef idref="p1.m1.1"/>
# <XMRef idref="p1.m1.2"/>
# </XMApp>
# <XMApp>
# <XMTok role="SUPERSCRIPTOP" scriptpos="post1"/>
# <XMTok font="italic" role="UNKNOWN" xml:id="p1.m1.1">f</XMTok>
# <XMDual>
# <XMTok font="italic" fontsize="70%" role="UNKNOWN" xml:id="p1.m1.2">n</XMTok>
# <XMWrap> ...
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a nasty limitation in my a11y extraction when an XMRef of a Dual points to a content branch of a sub-XMDual, left a comment here. Unsure if I should tackle this head on, or try to avoid it in the macros... since it is always avoidable, by just adding another layer of indirection in the TeX macros.

It's kind of a nasty precedent to be able to point in the content branch of a child dual, it almost feels like a bug, as it breaks the conceptual encapsulation (in my head) of XMDuals as black boxes, without cross-talk. Need to think about it some more, maybe more examples...

#
# We can only deal with 'p1.m1.2' pointing to the inner XMDual, rather than directly to its content "n"
DefMacro('\fnderive{}{}', '\fnderive@build{#1}{\derivemark{#2}}');
DefMath('\fnderive@build{}{}', '#1^#2',
meaning => 'derivative-implicit-variable',
hide_content_reversion => 1);

## Circumfix, applicative:
DefMath('\norm{}', '|\mathbf{#1}|', meaning => 'norm', role => 'ID',
reversion => '|\mathbf{#1}|', hide_content_reversion => 1);
DefMath('\determinant{}', '|\mathbf{#1}|', meaning => 'determinant', role => 'ID',
reversion => '|\mathbf{#1}|', hide_content_reversion => 1);
################################################################################################################

# Declare some default common in K12 math when using this package:
# Also, improve ergonomics of \lxDecalre to my (Deyan's) liking
# TODO: Can we reuse this keyval from latexml.sty? How?
DefKeyVal('Declare', 'role', '', '');
DefKeyVal('Declare', 'meaning', '', '');
DefKeyVal('Declare', 'action', '', '');
DefKeyVal('Declare', 'replace', '', '');
our %PRAGMA_ROLES = map { $_ => 1 } qw(ID FUNCTION);
DefMacro('\pragma OptionalMatch:* {}{}', sub { # Limitation: never use commas in the symbol/notation contents
my ($gullet, $star, $properties, $notations) = @_;
my @declarations = ();
my $notations_expanded = ToString($notations);
$notations_expanded =~ s/\?/\\WildCard[]/g;
my @notations = $star ? $notations_expanded : split(/\s*,\s*/, $notations_expanded);
my @properties = split(/\s*,\s*/, ToString($properties));
for my $notation (@notations) {
my $kvprops = LaTeXML::Core::KeyVals->new('KV', 'Declare', assign => T_OTHER('='), punct => T_OTHER(','));
for my $p (@properties) { # extend with more of the lxDeclare capabilities? scopes?
if ($PRAGMA_ROLES{$p}) {
$kvprops->setValue('role', $p); }
elsif ($p =~ /^(pre|post)\:/) {
$kvprops->setValue('action', $p); }
else {
$kvprops->setValue('meaning', $p); } }
push @declarations,
Invocation(T_CS('\lxDeclare'), undef, $kvprops,
Tokens(T_MATH, Tokenize($notation), T_MATH)); }
return @declarations; });

# Example pragmas, as incldued with the tiny accessibility showcase:
#
# disabled by default here, since they may assume too much
#
# PushValue('@at@begin@document', Tokenize(<<'EOL'));
# \pragma{FUNCTION}{f,g,h}
# \pragma{ID}{a,b,c,d,n,m,x,y,z}
# \pragma{index}{?_?}
# \pragma{power}{?^?}
# \pragma{Pochhamer-symbol,ID}{\left(?\right)_?}
# \pragma{Legendre-symbol,ID}{\left(?|?\right)}
# \pragma{BesselJ,FUNCTION}{J_?}
# \pragma*{inner-product,ID}{\left<\mathbf{?},\mathbf{?}\right>}
# \pragma*{pre:\@APPLYFUNCTION}{\left(?,?;?|?\right)}
# EOL
#
1;
Loading