Skip to content

Commit

Permalink
To attribute (#2465)
Browse files Browse the repository at this point in the history
* New toAttribute option to Constructors, Environment which allows customizing conversion to attribute value, with syntax analogous to revert

* List, which is a list of digested things, needs it's own toAttribute method

* Document->setAttribute should recognize _box,_font specially; otherwise use toAttribute

* \hskip needs the flexibility of a Constructor, but still should yield pure text spacing in attribute values

* Attributes holding text for eventual output should (fall-back to) toAttribute, not toString

* attributeForm is a better name for the customization option for toAttribute (dginev)

* Issue warning for inappropriate object set for attribute value
  • Loading branch information
brucemiller authored Jan 8, 2025
1 parent 7857e6b commit fa8191e
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 32 deletions.
19 changes: 14 additions & 5 deletions lib/LaTeXML/Core/Document.pm
Original file line number Diff line number Diff line change
Expand Up @@ -498,10 +498,10 @@ sub serialize_aux {
(map { $_ . '="' . $atnodes{$_} . '"' } sort keys %atnodes)
);
my $noindent_children = ($heuristic
# This emulates libxml2's heuristic
# ? $noindent || grep { $_->nodeType != XML_ELEMENT_NODE } @children
# This emulates libxml2's heuristic
# ? $noindent || grep { $_->nodeType != XML_ELEMENT_NODE } @children
? $noindent || grep { $_->nodeType == XML_TEXT_NODE } @children
# This is the "Correct" way to determine whether to add indentation
# This is the "Correct" way to determine whether to add indentation
: $model->canContain(getNodeQName($self, $node), '#PCDATA'));
return join('',
($noindent ? '' : $indent), $start,
Expand Down Expand Up @@ -1376,8 +1376,17 @@ sub makeError {
# [xml:id and namespaced attributes are always allowed]
sub setAttribute {
my ($self, $node, $key, $value) = @_;
return if (ref $value) && ((!blessed($value)) || !$value->can('toAttribute'));
$value = $value->toAttribute if ref $value;
if (ref $value) {
if ($key eq '_box') {
return $self->setNodeBox($node, $value); }
elsif ($key eq '_font') {
return $self->setNodeFont($node, $value); }
elsif ((!blessed($value)) || !$value->can('toAttribute')) {
Warn('unexpected', (ref $value), $self,
"Don't know how to encode $value as an attribute value");
return; }
else {
$value = $value->toAttribute; } }
if ((defined $value) && ($value ne '')) { # Skip if `empty'; but 0 is OK!
if ($key eq 'xml:id') { # If it's an ID attribute
$value = recordID($self, $value, $node); # Do id book keeping
Expand Down
6 changes: 5 additions & 1 deletion lib/LaTeXML/Core/List.pm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use LaTeXML::Common::Object;
use LaTeXML::Common::Error;
use LaTeXML::Common::Dimension;
use List::Util qw(min max);
use base qw(Exporter LaTeXML::Core::Box);
use base qw(Exporter LaTeXML::Core::Box);
our @EXPORT = (qw(&List));

# Tricky; don't really want a separate constructor for a Math List,
Expand Down Expand Up @@ -71,6 +71,10 @@ sub toString {
my ($self) = @_;
return join('', grep { defined $_ } map { $_->toString } $self->unlist); }

sub toAttribute {
my ($self) = @_;
return join('', grep { defined $_ } map { $_->toAttribute } $self->unlist); }

# Methods for overloaded operators
sub stringify {
no warnings 'recursion';
Expand Down
25 changes: 25 additions & 0 deletions lib/LaTeXML/Core/Whatsit.pm
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,31 @@ sub beAbsorbed {
LaTeXML::Core::Definition::stopProfiling($profiled, 'absorb') if $profiled;
return @result; }

# Similar to ->revert, but converts to pure string for use in an attribute value
sub toAttribute {
my ($self) = @_;
my $props = $$self{properties};
my $defn = $self->getDefinition;
my $spec = $$props{attributeForm} || $$defn{attributeForm};
if (!defined $spec) {
return $self->toString; } # Default
elsif (ref $spec eq 'CODE') { # If handled by CODE, call it
$spec = &$spec($self, $self->getArgs); }
# Now, similar to substituteParameters, but creating a string.
$spec =~ s/#(#|[1-9]|\w+)/ toAttribute_aux($self,$1)/eg;
return $spec; }

sub toAttribute_aux {
my ($self, $code) = @_;
my $value;
if ($code eq '#') { return $code; }
elsif ((ord($code) > ord('0')) && (ord($code) <= ord('9'))) {
$value = $self->getArg(ord($code) - ord('0')); }
else {
$value = $self->getProperty($code); }
$value = $value->toAttribute if ref $value;
return $value; }

# See discussion in Box.pm
sub computeSize {
my ($self, %options) = @_;
Expand Down
8 changes: 4 additions & 4 deletions lib/LaTeXML/Engine/LaTeX.pool.ltxml
Original file line number Diff line number Diff line change
Expand Up @@ -1211,9 +1211,9 @@ DefConstructor('\lx@author@prefix', sub {
my $i = scalar(@{ $document->findnodes('//ltx:creator[@role="author"]') });
if ($i <= 1) { }
elsif ($i == $nauthors) {
$document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@conj')))); }
$document->setAttribute($node, before => Digest(T_CS('\lx@author@conj'))); }
else {
$document->setAttribute($node, before => ToString(Digest(T_CS('\lx@author@sep')))); }
$document->setAttribute($node, before => Digest(T_CS('\lx@author@sep'))); }
});

DefMacro('\@author', '\@empty');
Expand Down Expand Up @@ -4376,8 +4376,8 @@ DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
"<ltx:bibref show='#1' bibrefs='#bibrefs' inlist='#bibunit'"
. " separator='#separator' yyseparator='#yyseparator'>#3#4</ltx:bibref>",
properties => sub { (bibrefs => CleanBibKey($_[2]),
separator => ToString(Digest(LookupValue('CITE_SEPARATOR'))),
yyseparator => ToString(Digest(LookupValue('CITE_YY_SEPARATOR'))),
separator => Digest(LookupValue('CITE_SEPARATOR')),
yyseparator => Digest(LookupValue('CITE_YY_SEPARATOR')),
bibunit => LookupValue('CITE_UNIT')); });

# Simple container for any phrases used in the bibref
Expand Down
7 changes: 4 additions & 3 deletions lib/LaTeXML/Engine/TeX_Glue.pool.ltxml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ DefConstructor('\hskip Glue', sub {
else {
# $document->openText(DimensionToSpaces($length), $props{font}); } },
$document->absorb(DimensionToSpaces($length)); } },
reversion => sub { revertSkip(T_CS('\hskip'), $_[1]); },
properties => sub {
reversion => sub { revertSkip(T_CS('\hskip'), $_[1]); },
attributeForm => sub { DimensionToSpaces($_[1]); },
properties => sub {
my ($stomach, $length) = @_;
(width => $length, isSpace => 1, isSkip => 1); });

Expand All @@ -95,7 +96,7 @@ DefConstructor('\vskip Glue', sub {
elsif ($document->isOpenable('ltx:break')) {
$document->insertElement('ltx:break'); } }
return; },
properties => sub { (height => $_[1], isSpace => 1,, isSkip => 1, isVerticalSpace => 1, isBreak => 1); });
properties => sub { (height => $_[1], isSpace => 1, isSkip => 1, isVerticalSpace => 1, isBreak => 1); });

# Remove skip, if last on LIST
DefPrimitiveI('\unskip', undef, sub {
Expand Down
53 changes: 34 additions & 19 deletions lib/LaTeXML/Package.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1381,12 +1381,12 @@ sub flatten {
# properties : a hashref listing default values of properties to assign to the Whatsit.
# These properties can be used in the constructor.
my $constructor_options = { # [CONSTANT]
mode => 1, requireMath => 1, forbidMath => 1, font => 1,
alias => 1, reversion => 1, sizer => 1, properties => 1,
nargs => 1,
beforeDigest => 1, afterDigest => 1, beforeConstruct => 1, afterConstruct => 1,
captureBody => 1, scope => 1, bounded => 1, locked => 1,
outer => 1, long => 1, robust => 1 };
mode => 1, requireMath => 1, forbidMath => 1, font => 1,
alias => 1, reversion => 1, sizer => 1, properties => 1,
nargs => 1, attributeForm => 1,
beforeDigest => 1, afterDigest => 1, beforeConstruct => 1, afterConstruct => 1,
captureBody => 1, scope => 1, bounded => 1, locked => 1,
outer => 1, long => 1, robust => 1 };

sub inferSizer {
my ($sizer, $reversion) = @_;
Expand Down Expand Up @@ -1424,12 +1424,13 @@ sub DefConstructorI {
nargs => $options{nargs},
alias => (defined $options{alias} ? $options{alias}
: ($options{robust} ? $cs : undef)),
reversion => $options{reversion},
sizer => inferSizer($options{sizer}, $options{reversion}),
captureBody => $options{captureBody},
properties => $options{properties} || {},
outer => $options{outer},
long => $options{long}),
reversion => $options{reversion},
attributeForm => $options{attributeForm},
sizer => inferSizer($options{sizer}, $options{reversion}),
captureBody => $options{captureBody},
properties => $options{properties} || {},
outer => $options{outer},
long => $options{long}),
$options{scope});
AssignValue(ToString($cs) . ":locked" => 1) if $options{locked};
return; }
Expand Down Expand Up @@ -1798,7 +1799,7 @@ sub defmath_cons {
? $cs : $presentation->unlist); }; }
$STATE->installDefinition(LaTeXML::Core::Definition::Constructor->new($defcs, $paramlist,
($nargs == 0
# If trivial presentation, allow it in Text
# If trivial presentation, allow it in Text
? ($presentation !~ /(?:\(|\)|\\)/
? "?#isMath(<ltx:XMTok role='#role' scriptpos='#scriptpos' stretchy='#stretchy'"
. " font='#font' $cons_attr$end_tok)"
Expand Down Expand Up @@ -1827,7 +1828,8 @@ my $environment_options = { # [CONSTANT]
beforeDigest => 1, afterDigest => 1,
afterDigestBegin => 1, beforeDigestEnd => 1, afterDigestBody => 1,
beforeConstruct => 1, afterConstruct => 1,
reversion => 1, sizer => 1, scope => 1, locked => 1 };
reversion => 1, sizer => 1, scope => 1, locked => 1,
attributeForm => 1 };

sub DefEnvironment {
my ($proto, $replacement, %options) = @_;
Expand Down Expand Up @@ -1873,8 +1875,9 @@ sub DefEnvironmentI {
nargs => $options{nargs},
captureBody => 1,
properties => $options{properties} || {},
(defined $options{reversion} ? (reversion => $options{reversion}) : ()),
(defined $sizer ? (sizer => $sizer) : ()),
(defined $options{reversion} ? (reversion => $options{reversion}) : ()),
(defined $options{attributeForm} ? (attributeForm => $options{attributeForm}) : ()),
(defined $sizer ? (sizer => $sizer) : ()),
), $options{scope});
$STATE->installDefinition(LaTeXML::Core::Definition::Constructor
->new(T_CS("\\end{$name}"), "", "",
Expand Down Expand Up @@ -1914,8 +1917,9 @@ sub DefEnvironmentI {
nargs => $options{nargs},
captureBody => T_CS("\\end$name"), # Required to capture!!
properties => $options{properties} || {},
(defined $options{reversion} ? (reversion => $options{reversion}) : ()),
(defined $sizer ? (sizer => $sizer) : ()),
(defined $options{reversion} ? (reversion => $options{reversion}) : ()),
(defined $options{attributeForm} ? (attributeForm => $options{attributeForm}) : ()),
(defined $sizer ? (sizer => $sizer) : ()),
), $options{scope});
$STATE->installDefinition(LaTeXML::Core::Definition::Constructor
->new(T_CS("\\end$name"), "", "",
Expand Down Expand Up @@ -2475,7 +2479,7 @@ sub AddToMacro {
else {
local $LaTeXML::Core::State::UNLOCKED = 1; # ALLOW redefinitions that only adding to the macro
DefMacroI($cs, undef, Tokens(map { $_->unlist }
map { (blessed $_ ? $_ : TokenizeInternal($_)) } ($defn->getExpansion, @tokens)),
map { (blessed $_ ? $_ : TokenizeInternal($_)) } ($defn->getExpansion, @tokens)),
nopackParameters => 1, scope => 'global', locked => $$defn{locked}); }
return; }

Expand Down Expand Up @@ -3682,6 +3686,15 @@ the one defined in the C<prototype>. This is a convenient alternative for
reversion when a 'public' command conditionally expands into
an internal one, but the reversion should be for the public command.
=item C<attributeForm=E<gt>I<texstring> | I<code>($whatsit,#1,#2,...)>
specifies the conversion of the invocation back into plain text
for an attribute value, used by the C<toAttribute> method
(the default is C<toString>).
The I<textstring> string can include C<#1>, C<#2>...
The I<code> is called with the C<$whatsit> and digested arguments
and must return a string.
=item C<sizer=E<gt>I<string> | I<code>($whatsit)>
specifies how to compute (approximate) the displayed size of the object,
Expand Down Expand Up @@ -3884,6 +3897,8 @@ These options are the same as for L</Primitives>
=item C<alias=E<gt>I<cs>>,
=item C<attributeForm=E<gt>I<texstring> | I<code>($whatsit,#1,#2,...)>,
=item C<sizer=E<gt>I<sizer>>,
=item C<properties=E<gt>I<properties>>,
Expand Down

0 comments on commit fa8191e

Please sign in to comment.