diff --git a/lib/LaTeXML/Core/Document.pm b/lib/LaTeXML/Core/Document.pm
index 63b1569be..637945909 100644
--- a/lib/LaTeXML/Core/Document.pm
+++ b/lib/LaTeXML/Core/Document.pm
@@ -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,
@@ -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
diff --git a/lib/LaTeXML/Core/List.pm b/lib/LaTeXML/Core/List.pm
index 34494341f..1c4b206e4 100644
--- a/lib/LaTeXML/Core/List.pm
+++ b/lib/LaTeXML/Core/List.pm
@@ -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,
@@ -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';
diff --git a/lib/LaTeXML/Core/Whatsit.pm b/lib/LaTeXML/Core/Whatsit.pm
index ee21008ed..c6af364c7 100644
--- a/lib/LaTeXML/Core/Whatsit.pm
+++ b/lib/LaTeXML/Core/Whatsit.pm
@@ -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) = @_;
diff --git a/lib/LaTeXML/Engine/LaTeX.pool.ltxml b/lib/LaTeXML/Engine/LaTeX.pool.ltxml
index 7841851bc..a1cb2e6ba 100644
--- a/lib/LaTeXML/Engine/LaTeX.pool.ltxml
+++ b/lib/LaTeXML/Engine/LaTeX.pool.ltxml
@@ -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');
@@ -4376,8 +4376,8 @@ DefConstructor('\@@bibref Semiverbatim Semiverbatim {}{}',
"#3#4",
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
diff --git a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml
index 7aa00e393..b4d0bef5b 100644
--- a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml
+++ b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml
@@ -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); });
@@ -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 {
diff --git a/lib/LaTeXML/Package.pm b/lib/LaTeXML/Package.pm
index 69f8f38e5..60f04b7e3 100644
--- a/lib/LaTeXML/Package.pm
+++ b/lib/LaTeXML/Package.pm
@@ -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) = @_;
@@ -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; }
@@ -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( 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) = @_;
@@ -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}"), "", "",
@@ -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"), "", "",
@@ -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; }
@@ -3682,6 +3686,15 @@ the one defined in the C. 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 CI | I($whatsit,#1,#2,...)>
+
+specifies the conversion of the invocation back into plain text
+for an attribute value, used by the C method
+(the default is C).
+The I string can include C<#1>, C<#2>...
+The I is called with the C<$whatsit> and digested arguments
+and must return a string.
+
=item CI | I($whatsit)>
specifies how to compute (approximate) the displayed size of the object,
@@ -3884,6 +3897,8 @@ These options are the same as for L
=item CI>,
+=item CI | I($whatsit,#1,#2,...)>,
+
=item CI>,
=item CI>,