—# /=====================================================================\ #
# | LaTeXML::Core::KeyVal | #
# | Key-Value Defintions in LaTeXML | #
# |=====================================================================| #
# | Thanks to Tom Wiesing <tom.wiesing@gmail.com> | #
# | 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::Core::KeyVal;
use
strict;
use
warnings;
use
LaTeXML::Global;
use
LaTeXML::Core::Token;
our
@EXPORT
= (
qw(&DefKeyVal &DisableKeyVal &HasKeyVal)
);
#======================================================================
# Exposed Methods
#======================================================================
# Defines a new key-value pair and returns it's header
sub
DefKeyVal {
my
(
$keyset
,
$key
,
$type
,
$default
,
%options
) =
@_
;
# extract the prefix
my
$prefix
=
$options
{prefix};
delete
$options
{prefix};
# create a new keyval object
my
$keyval
= LaTeXML::Core::KeyVal->new(
$prefix
,
$keyset
,
$key
);
$keyval
->define(
$type
,
$default
,
%options
);
# return the keyval object (should we need it)
return
$keyval
; }
# check if a key-value pair is defined
sub
HasKeyVal {
my
(
$prefix
,
$keyset
,
$key
) =
@_
;
my
$keyval
= LaTeXML::Core::KeyVal->new(
$prefix
,
$keyset
,
$key
);
return
$keyval
->isDefined(1); }
# disable a given key-val
sub
DisableKeyVal {
my
(
$prefix
,
$keyset
,
$key
) =
@_
;
my
$keyval
= LaTeXML::Core::KeyVal->new(
$prefix
,
$keyset
,
$key
);
return
$keyval
->disable; }
#======================================================================
# Creating KeyVal objects
#======================================================================
# create new class instance
sub
new {
my
(
$class
,
$prefix
,
$keyset
,
$key
) =
@_
;
return
bless
{
prefix
=>
defined
(
$prefix
) ? ToString(
$prefix
) :
'KV'
,
keyset
=> ToString(
$keyset
),
key
=> ToString(
$key
)
},
$class
; }
#======================================================================
# Accessors
#======================================================================
sub
getPrefix {
my
(
$self
) =
@_
;
return
$$self
{prefix}; }
sub
getKeySet {
my
(
$self
) =
@_
;
return
$$self
{keyset}; }
sub
getKey {
my
(
$self
) =
@_
;
return
$$self
{key}; }
sub
getFullPrefix {
my
(
$self
) =
@_
;
# if we already have this cached, we can return
return
$$self
{fullprefix}
if
defined
(
$$self
{fullprefix});
# else, define the full prefix
$$self
{fullprefix} =
$self
->getPrefix .
'@'
.
$self
->getKeySet .
'@'
;
# and return it
return
$$self
{fullprefix}; }
sub
getHeader {
my
(
$self
) =
@_
;
# if we already have this cached, we can return
return
$$self
{header}
if
defined
(
$$self
{header});
# else, define the header
$$self
{header} =
$self
->getPrefix .
'@'
.
$self
->getKeySet .
'@'
.
$self
->getKey;
# and return it
return
$$self
{header}; }
#======================================================================
# Property access
#======================================================================
# set a key-value property for this key
sub
setProp {
my
(
$self
,
$prop
,
$value
,
$scope
) =
@_
;
$STATE
->assignValue(
'KEYVAL@'
.
$prop
.
'@'
.
$self
->getHeader,
$value
,
$scope
);
return
; }
# get a key-value property for this key
sub
getProp {
my
(
$self
,
$prop
) =
@_
;
return
$STATE
->lookupValue(
'KEYVAL@'
.
$prop
.
'@'
.
$self
->getHeader); }
sub
getType {
my
(
$self
) =
@_
;
return
$$self
{type} =
$self
->getProp(
'type'
); }
sub
setType {
my
(
$self
,
$type
) =
@_
;
my
$prefix
=
$self
->getPrefix;
my
$keyset
=
$self
->getKeySet;
my
$key
=
$self
->getKey;
# parse the parameters
my
$paramlist
= LaTeXML::Package::parseParameters(
$type
||
"{}"
,
"KeyVal $key in set $keyset with prefix $prefix"
);
if
(
scalar
(
@$paramlist
) != 1) {
Warn(
'unexpected'
,
'keyval'
,
$key
,
"Too many parameters in keyval $key (in set $keyset with prefix $prefix); taking only first"
,
$paramlist
); }
my
$parameter
=
$$paramlist
[0];
# store the property
$self
->setProp(
'type'
,
$parameter
);
return
; }
sub
getDefault {
my
(
$self
) =
@_
;
return
$self
->getProp(
'default'
); }
sub
setDefault {
my
(
$self
,
$default
,
$setMacros
) =
@_
;
my
@tdefault
= LaTeXML::Package::Tokenize(
$default
);
$self
->setProp(
'default'
, Tokens(
@tdefault
));
if
(
$setMacros
) {
my
$header
=
$self
->getHeader;
LaTeXML::Package::DefMacroI(
'\\'
.
$header
.
'@default'
,
undef
,
Tokens(T_CS(
'\\'
.
$header
), T_BEGIN,
@tdefault
, T_END));
return
; } }
#======================================================================
# Key Definition
#======================================================================
sub
isDefined {
my
(
$self
,
$checkMacro
) =
@_
;
# start by checking if the defined key exists
return
1
if
(
defined
$self
->getProp(
'exists'
));
# check if the macro is given, if it is defined
return
(
defined
$STATE
->lookupMeaning(T_CS(
'\\'
.
$self
->getHeader)))
if
$checkMacro
;
return
0; }
# (re-)define this key
sub
define {
my
(
$self
,
$type
,
$default
,
%options
) =
@_
;
# define that the key exists and is not disabled
$self
->setProp(
'exists'
, 1);
$self
->setProp(
'disabled'
, 0);
# set the type
$self
->setType(
$type
);
# set the default
$self
->setDefault(
$default
, 1)
if
defined
(
$default
);
# figure out the kind of key-val parameter we are defining
my
$kind
=
$options
{kind} ||
'ordinary'
;
$self
->setProp(
'kind'
,
$kind
);
if
(
$kind
eq
'ordinary'
) {
$self
->defineOrdinary(
$options
{code}); }
elsif
(
$kind
eq
'command'
) {
$self
->defineCommand(
$options
{code},
$options
{macroprefix}); }
elsif
(
$kind
eq
'choice'
) {
$self
->defineChoice(
$options
{code},
$options
{mismatch},
$options
{choices}, (
$options
{normalize} || 0),
$options
{bin}); }
elsif
(
$kind
eq
'boolean'
) {
$self
->defineBoolean(
$options
{code},
$options
{mismatch},
$options
{macroprefix}); }
else
{ Warn(
'unknown'
,
undef
,
"Unknown KeyVals kind $kind, should be one of 'ordinary', 'command', 'choice', 'boolean'. "
); }
return
; }
sub
defineOrdinary {
my
(
$self
,
$code
) =
@_
;
LaTeXML::Package::DefMacroI(
'\\'
.
$self
->getHeader,
LaTeXML::Core::Parameters->new(LaTeXML::Core::Parameter->new(
'Plain'
,
'{}'
)),
defined
(
$code
) ?
$code
:
''
);
return
; }
sub
defineCommand {
my
(
$self
,
$code
,
$macroprefix
) =
@_
;
# store the header we have to define
my
$macroHeader
=
$macroprefix
|| (
"cmd"
.
$self
->getFullPrefix);
$self
->setProp(
'macroHeader'
,
$macroHeader
);
# prefix the original macro with the code
$self
->defineOrdinary(
sub
{
my
(
$gullet
,
$value
) =
@_
;
my
$orig
=
'\\ltxml@orig@'
.
$self
->getHeader;
LaTeXML::Package::DefMacroI(
$orig
,
LaTeXML::Core::Parameters->new(LaTeXML::Core::Parameter->new(
'Plain'
,
'{}'
)),
$code
);
Tokens(
T_CS(
"\\def"
), T_CS(
'\\'
.
$macroHeader
.
$self
->getKey), T_BEGIN,
$value
, T_END,
T_CS(
$orig
), T_BEGIN, T_PARAM,
$value
, T_END
);
});
return
; }
sub
defineChoice {
my
(
$self
,
$code
,
$mismatch
,
$choices
,
$normalize
,
$bin
) =
@_
;
# store the choices
$self
->setProp(
'choices'
,
join
(
','
,
$choices
));
# we might need to 'normalize', i.e. turn labels into lowercase
my
$norm
;
if
(
$normalize
) {
$norm
=
sub
{
lc
$_
[0]; }; }
else
{
$norm
=
sub
{
$_
[0]; }; }
$self
->setProp(
'normalize'
,
$normalize
? 0 : 1);
# Unpack macros (if available)
my
(
$varmacro
,
$idxmacro
) =
defined
(
$bin
) ?
$bin
->unlist : (
undef
,
undef
);
$self
->setProp(
'varmacro'
,
$varmacro
);
$self
->setProp(
'idxmacro'
,
$idxmacro
);
# define advanced macro code
$self
->defineOrdinary(
sub
{
my
(
$gullet
,
$value
) =
@_
;
# Store the normalized value (if applicable)
my
$nvalue
=
&$norm
(ToString(
$value
));
LaTeXML::Package::DefMacro(
$varmacro
,
sub
{ Explode(
$nvalue
); })
if
defined
(
$varmacro
);
# iterate over the possible choices and store them
my
$ochoice
;
my
$index
= 0;
my
$valid
= 0;
foreach
my
$choice
(@{
$choices
}) {
if
(
&$norm
(ToString(
$choice
)) eq
$nvalue
) {
$ochoice
=
$choice
;
$valid
= 1;
LaTeXML::Package::DefMacro(
$idxmacro
, Explode(ToString(
$index
)))
if
defined
(
$idxmacro
); }
$index
+= 1; }
# find a name for the original macro to store in
my
@tokens
= ();
my
$orig
=
'\\ltxml@orig@'
.
$self
->getHeader;
# if we have chosen a valid index, run $code
if
(
$valid
) {
if
(
defined
(
$code
)) {
LaTeXML::Package::DefMacroI(
$orig
,
LaTeXML::Core::Parameters->new(LaTeXML::Core::Parameter->new(
'Plain'
,
'{}'
)),
$code
);
push
(
@tokens
, T_CS(
$orig
), T_BEGIN,
$value
, T_END); } }
# else run $mismatch
elsif
(
defined
(
$mismatch
)) {
LaTeXML::Package::DefMacroI(
$orig
,
LaTeXML::Core::Parameters->new(LaTeXML::Core::Parameter->new(
'Plain'
,
'{}'
)),
$mismatch
);
push
(
@tokens
, T_CS(
$orig
), T_BEGIN,
$value
, T_END); }
@tokens
; });
return
; }
sub
defineBoolean {
my
(
$self
,
$code
,
$mismatch
,
$macroprefix
) =
@_
;
# find the header and define a new conditional
my
$macroHeader
= (
$macroprefix
||
$self
->getFullPrefix) .
$self
->getKey;
LaTeXML::Package::DefConditional(T_CS(
"\\if$macroHeader"
));
# We might need to $scope here
$self
->setProp(
'macroHeader'
,
$macroHeader
);
# and define a choice key
$self
->defineChoice(
sub
{
my
(
$gullet
,
$value
) =
@_
;
# set the value to true (if needed)
my
@tokens
= ();
push
(
@tokens
, T_CS(
'\\'
.
$macroHeader
. (((
lc
ToString(
$value
)) eq
'true'
) ?
'true'
:
'false'
)));
# Store and invoke the original macro if needed
if
(
$code
) {
my
$orig
=
'\\ltxml@@rig@'
.
$self
->getHeader;
LaTeXML::Package::DefMacroI(
$orig
,
LaTeXML::Core::Parameters->new(LaTeXML::Core::Parameter->new(
'Plain'
,
'{}'
)),
$code
);
push
(
@tokens
, T_CS(
$orig
), T_BEGIN,
$value
, T_END); }
@tokens
; },
$mismatch
, [(
"true"
,
"false"
)], 1);
return
; }
sub
isDisabled {
my
(
$self
) =
@_
;
return
$self
->getProp(
'disabled'
, 1); }
sub
disable {
my
(
$self
) =
@_
;
# disable the key
$self
->defineOrdinary(
sub
{
LaTeXML::Package::Tokenize(
"\\PackageWarning{keyval}{`"
.
$self
->getKey .
"' has been disabled. }"
);
});
$self
->setProp(
'disabled'
, 1);
return
; }
#======================================================================
# Value Related Reversion
#======================================================================
sub
setKeysExpansion {
my
(
$self
,
$value
,
$useDefault
,
$checkExistence
,
$checkDisabled
,
$setInternals
) =
@_
;
# if we do not exist, return undef
if
(
$checkExistence
&& !(
$self
->isDefined(1))) {
Error(
'undefined'
,
'Encountered unknown KeyVals key'
,
"'"
.
$self
->getKey .
"' with prefix '"
.
$self
->getPrefix .
"' not defined in '"
.
join
(
","
,
$self
->getKeySet) .
"'"
);
return
; }
# if we are disabled, return an empty tokens
if
(
$checkDisabled
&&
$self
->isDisabled) {
Warn(
'undefined'
,
"`"
.
$self
->getKey .
"' has been disabled. "
);
return
Tokens(); }
# definition of 'xkeyval' internals (if applicable)
my
@tokens
=
$setInternals
? (
T_CS(
'\def'
), T_CS(
'\XKV@prefix'
), T_BEGIN, Explode(
$self
->getPrefix .
'@'
), T_END,
T_CS(
'\def'
), T_CS(
'\XKV@tfam'
), T_BEGIN, Explode(
$self
->getKeySet), T_END,
T_CS(
'\def'
), T_CS(
'\XKV@header'
), T_BEGIN, Explode(
$self
->getFullPrefix), T_END,
T_CS(
'\def'
), T_CS(
'\XKV@tkey'
), T_BEGIN, Explode(
$self
->getKey), T_END
) : ();
# we have a value given, call the appropriate macro with it
unless
(
$useDefault
) {
push
(
@tokens
, T_CS(
'\\'
.
$self
->getHeader), T_BEGIN, Revert(
$value
), T_END); }
# if it was not given explicitly, call the default macro
else
{
push
(
@tokens
, T_CS(
'\\'
.
$self
->getHeader .
'@default'
)); }
# and reset the internals (if applicable)
push
(
@tokens
,
T_CS(
'\def'
), T_CS(
'\XKV@prefix'
), T_BEGIN, T_END,
T_CS(
'\def'
), T_CS(
'\XKV@tfam'
), T_BEGIN, T_END,
T_CS(
'\def'
), T_CS(
'\XKV@header'
), T_BEGIN, T_END,
T_CS(
'\def'
), T_CS(
'\XKV@tkey'
), T_BEGIN, T_END)
if
$setInternals
;
return
Tokens(
@tokens
); }
sub
toString {
my
(
$self
) =
@_
;
return
$self
->getHeader; }
#======================================================================
1;
__END__
=pod
=head1 NAME
C<LaTeXML::Core::KeyVal> - Key-Value Defintions in LaTeXML
=head1 DESCRIPTION
Provides an interface to define and access KeyVal definition.
Used in conjunction with C<LaTeXML::Core::KeyVals> to fully implement KeyVal
pairs. It extends L<LaTeXML::Common::Object>.
=head2 Exposed Methods
=over 4
=item C<DefKeyVal(I<keyset>, I<key>, I<type>, I<default>, I<%options>); >
Defines a new KeyVal Parameter in the given I<keyset>, I<key> and with optional
prefix I<option{prefix}>. For descriptions of further parameters, see I<LaTeXML::Core::KeyVal::define>.
=item C<HasKeyVal(I<prefix>, I<keyset>, I<key>); >
Checks if the given KeyVal pair exists.
=item C<DisableKeyVal(I<prefix>, I<keyset>, I<key>); >
Disables the given KeyVal so that it can not be used.
=back
=head2 Constructors
=over 4
=item C<<LaTeXML::Core::KeyVal->new(I<preset>, I<keyset>, I<key>); >>
Creates a new I<KeyVal> object. This serves as a simple reference to the given
KeyVal object, regardless of its existence or not.
=back
=head2 KeyVal Accessors
=over 4
=item C<< $prefix = $keyval->getPrefix; >>
Gets the prefix of this KeyVal object.
=item C<< $keyset = $keyval->getKeySet; >>
Gets the keyset of this KeyVal object.
=item C<< $key = $keyval->getKey; >>
Gets the key of this KeyVal object.
=item C<< $prefix = $keyval->getFullPrefix; >>
Gets the full prefix of this keyval object, to be used when composing macros.
=item C<< $prefix = $keyval->getHeader; >>
Gets the header of this keyval object, to be used when composing macros.
=back
=head2 Keyval Property Getters
=over 4
=item C<< $value = $keyval->getProp($prop); >>
Gets the value of a given property of this KeyVal. Intended for internal use
only.
=item C<< $value = $keyval->setProp($prop, $value, $scope); >>
Sets the value of the given property with the given value and scope. Intended
for internal use only.
=item C<< $type = $keyval->getType(); >>
Gets the type of this KeyVal object, as found in $STATE.
=item C<< $keyval->setType($type); >>
Sets the type of this KeyVal object, as found in $STATE.
=item C<< $default = $keyval->getDefault(); >>
Gets the default of this KeyVal object, as found in $STATE.
=item C<< $keyval->setDefault($default, $setMacros); >>
Sets the default of this KeyVal object, and optionally sets the macros as well.
=back
=head2 KeyVal Key Definition
=over 4
=item C<< $defined = $keyval->isDefined($checkMacros); >>
Checks if this KeyVal item is actually defined. If checkMacros is set to true,
also check if macros are defined.
=item C<< $keyval->define($type, $default, %options); >>
(Re-)defines this Key of kind 'kind'.
Defines a keyword I<key> used in keyval arguments for the set I<keyset> and,
and if the option I<code> is given, defines appropriate macros
when used with the I<keyval> package (or extensions thereof).
If I<type> is given, it defines the type of value that must be supplied,
such as C<'Dimension'>. If I<default> is given, that value will be used
when I<key> is used without an equals and explicit value in a keyvals argument.
A I<scope> option can be given, which can be used to defined the key-value pair
globally instead of in the current scope.
Several more I<option>s can be given. These implement the behaviour of the
xkeyval package.
The I<prefix> parameter can be used to configure a custom prefix for
the macros to be defined. The I<kind> parameter can be used to configure special types of xkeyval
pairs.
The 'ordinary' kind behaves like a normal keyval parameter.
The 'command' kind defines a command key, that when run stores the value of the
key in a special macro, which can be further specefied by the I<macroprefix>
option.
The 'choice' kind defines a choice key, which takes additional options
I<choices> (to specify which choices are valid values), I<mismatch> (to be run
if an invalid choice is made) and I<bin> (see xkeyval documentation for
details).
The 'boolean' kind defines a special choice key that takes possible values true and
false, and defines a new Conditional according to the assumed value. The name of
this conditional can be specified with the I<macroprefix> option.
The kind parameter only takes effect when I<code> is given, otherwise only
meta-data is stored.
=item C<< $keyval->defineOrdinary($code); >>
Helper function to define $STATE neccesary for an ordinary key.
=item C<< $keyval->defineCommand($code, $macroprefix); >>
Helper function to define $STATE neccesary for a command key.
=item C<< $keyval->defineChoice($code, $mismatch, $choices, $normalize, $bin); >>
Helper function to define $STATE neccesary for an choice key.
=item C<< $keyval->defineBoolean($code, $mismatch, $macroprefix); >>
Helper function to define $STATE neccesary for a boolean key.
=item C<< disabled = $keyval->isDisabled(); >>
Checks if this keyval property is disabled.
=item C<< $keyval->disable(); >>
Disables this KeyVal object in $STATE.
=back
=head2 Value Related
=over 4
=item C<< $expansion = $keyval->setKeys($value, $useDefault, $checkExistence, $checkDisabled, $setInternals); >>
Expands this KeyVal into a Tokens() to be used with \setkeys.
I<value> contains the value to be used in the expansion, I<useDefault> indicates
if the value argument should be ignored and the default should be used instead.
If I<checkExistence> is set to 1 and the macro does not exist, undef is returned.
If I<checkDisabled> is set to 1 and the macro is disabled, an empty Tokens() is
returns.
If I<$setInternals> is set, sets XKeyVal internal macros.
=item C<< $reversion = $keyval->digest($stomach, $value); >>
Digests this KeyVal with the given stomach and value.
=item C<< $reversion = $keyval->revert($value, $useDefault, $compact, $isFirst, $punct, $assign); >>
Reverts this KeyVal with a given I<value> (or the default if I<IsDefault> is set)
and punctuation and assignment tokens.
If I<compact> is given, spaces will be omitted when possible.
The I<isDefault> parameter indicates if appropriate seperation tokens should be
inserted.
=item C<< $str = $keyval->toString(); >>
Turns this KeyVal object into a string.
=back
=head1 AUTHOR
Tom Wiesing <tom.wiesing@gmail.com>
=head1 COPYRIGHT
Public domain software, produced as part of work done by the
United States Government & not subject to copyright in the US.
=cut