File: KeyVals.pm

package info (click to toggle)
latexml 0.8.7-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 29,128 kB
  • sloc: xml: 98,982; perl: 29,706; sh: 179; javascript: 28; makefile: 15
file content (712 lines) | stat: -rw-r--r-- 26,536 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# /=====================================================================\ #
# |  LaTeXML::Core::KeyVals                                             | #
# | Support for key-value pairs 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 <bruce.miller@nist.gov>                        #_#     | #
# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Core::KeyVals;
use strict;
use warnings;
use LaTeXML::Global;
use LaTeXML::Common::Object;
use LaTeXML::Common::Error;
use LaTeXML::Core::Token;
use LaTeXML::Core::Tokens;
use LaTeXML::Core::KeyVal;
use base qw(LaTeXML::Common::Object);

our @EXPORT = (
  qw(&GetKeyVal &GetKeyVals)
);

#======================================================================
# Exposed Methods
#======================================================================

sub GetKeyVal {
  my ($keyvals, $key) = @_;
  return (defined $keyvals) && $keyvals->getValue($key); }

sub GetKeyVals {
  my ($keyvals) = @_;
  return (defined $keyvals ? $keyvals->getKeyVals : {}); }

#======================================================================
# The KeyVals constructor
#======================================================================
# This defines the KeyVals data object that can appear in the datastream
# along with tokens, boxes, etc.
# Thus it has to be digestible, however we may not want to digest it more
# than once.
#**********************************************************************

sub new {
  my ($class, $prefix, $keysets, %options) = @_;
  # parse all the arguments
  $prefix = defined($prefix) ? ToString($prefix) : 'KV';
  $keysets = [split(',', ToString(defined($keysets) ? $keysets : '_anonymous_'))] unless (ref($keysets) eq 'ARRAY');
  my $skip = $options{skip};
  $skip = [split(',', ToString(defined($options{skip}) ? $options{skip} : ''))] unless (ref($options{skip}) eq 'ARRAY');
  my $setAll       = $options{setAll}       ? 1 : 0;
  my $setInternals = $options{setInternals} ? 1 : 0;
  my $skipMissing  = $options{skipMissing};
  my $hookMissing  = $options{hookMissing};
  # hook missing, if defined, must be a token
  if (defined($hookMissing) && $hookMissing) {
    $hookMissing = ref($hookMissing) ? $hookMissing : T_CS(ToString($hookMissing)); }
  else { $hookMissing = undef; }
  # skip missing may be a token (=store all the missing macros there)
  unless (ref($skipMissing)) {
    # may be undef or 0 (= throw errors)
    unless (defined($skipMissing)) { $skipMissing = undef; }
    elsif  ($skipMissing eq '0')   { $skipMissing = undef; }
    # may be 1 (= ignore all missing keys)
    elsif ($skipMissing eq '1') { $skipMissing = 1; }
    # may be a string (= store all the missing keys there)
    else { $skipMissing = T_CS($skipMissing); } }
  my $self = bless {
    # which KeyVals are we parsing and how do we behave?
    prefix      => $prefix,      keysets     => $keysets,
    skip        => $skip,        setAll      => $setAll, setInternals => $setInternals,
    skipMissing => $skipMissing, hookMissing => $hookMissing,
    # all the internal representations
    tuples => [], cachedPairs => [()], cachedHash => {} },
    $class;
  if (my $tuples = $options{tuples}) {
    $$self{tuples} = $tuples;
    $self->rebuild; }
  return $self; }

#======================================================================
# Resolution to KeySets
#======================================================================
# Return a list of the keysets in which this key is defined
sub resolveKeyValFor {
  my ($self, $key) = @_;
  my $prefix     = $$self{prefix};
  my @allkeysets = @{ $$self{keysets} };
  my @keysets    = grep { HasKeyVal($prefix, $_, $key); } @{ $$self{keysets} };
  # throw an error (not really), unless we record the missing macros
  # Since we're not as obsessive about declaring ALL keys, we'll soften the blow
  if (scalar @keysets == 0) {
    Info('undefined', 'Encountered unknown KeyVals key',
      "'$key' with prefix '$prefix' not defined in '" . join(",", @allkeysets) . "', " .
        'were you perhaps using \setkeys instead of \setkeys*?') unless defined($$self{skipMissing});
    return; }
  # return either the first or all of the KeyVal objects
  return ($$self{setAll} ? @keysets : ($keysets[0])); }

sub canResolveKeyValFor {
  my ($self, $key) = @_;
  my $prefix = $$self{prefix};
  # iterate over the keysets
  foreach my $keyset (@{ $$self{keysets} }) {
    return 1 if HasKeyVal($prefix, $keyset, $key); }
  return 0; }

# Return the 1st of the keysets, or the 1st one of the KeyVals itself
sub getPrimaryKeyValOf {
  my ($self, $key, @keysets) = @_;
  return (@keysets ? $keysets[0] : $$self{keysets}[0]); }

#======================================================================
# Changing contained values
#======================================================================

sub addValue {
  my ($self, $key, $value, $useDefault, $noRebuild) = @_;

  # figure out the keyset(s) for the key to be added
  my @keysets = $self->resolveKeyValFor($key);
  my $pkeyset = $self->getPrimaryKeyValOf($key, @keysets);

  # and add the new tuple to the set of tuples
  push(@{ $$self{tuples} },
    [$key, ($useDefault ? keyval_get(keyval_qname($$self{prefix}, $pkeyset, $key), 'default') : $value),
      $useDefault, $pkeyset, [@keysets]]);

  # we now need to rebuild, unless we were asked not to
  # TODO: Maybe only update the last element?
  $self->rebuild unless $noRebuild;
  return; }

sub setValue {
  my ($self, $key, $value, $useDefault) = @_;
  # delete the existing values by skipping key;
  $self->rebuild($key);

  # if we have an array, we need to push all of them
  if (ref $value eq 'ARRAY') {
    foreach my $val (@{$value}) {
      $self->addValue($key, $val, $useDefault, 1); }
    $self->rebuild(); }
  # if we have a single value, set it normally
  elsif (defined($value)) {
    $self->addValue($key, $value, $useDefault); }
  return; }

sub rebuild {
  my ($self, $skip) = @_;
  # the new data structures to create
  my @newtuples = ();
  my @pairs     = ();
  my %hash      = ();
  foreach my $tuple (@{ $$self{tuples} }) {
    # take all the elements we need from the stack
    my ($key, $value, $useDefault, $keyset, $keysets) = @$tuple;
    # if we want to skip some values, we need to store new tuples
    if (defined($skip)) {
      next if $key eq $skip;
      push(@newtuples, [$key, $value, $useDefault, $keyset, $keysets]) if defined($skip); }
    # push key / value into the pair
    push(@pairs, $key, $value);
    # if we do not have a value yet, set it
    if (!defined $hash{$key}) {
      $hash{$key} = $value; }
    # If we get a third value, push into an array
    # This is unlikely to be what the caller expects!! But what else?
    elsif (ref $hash{$key} eq 'ARRAY') {
      push(@{ $hash{$key} }, $value); }
    # If we get a second value, make an array
    else {
      $hash{$key} = [$hash{$key}, $value]; } }
  # store all of the values
  $$self{cachedPairs} = [@pairs];
  $$self{cachedHash}  = \%hash;
  $$self{tuples}      = [@newtuples] if defined($skip);
  return; }

#======================================================================
# parsing values from a gullet
#======================================================================

# A KeyVal argument MUST be delimited by either braces or brackets (if optional)
# This method reads the keyval pairs INCLUDING the delimiters, (rather than
# parsing after the fact), since some values may have special catcode needs.
##my $T_EQ    = T_OTHER('=');    # [CONSTANT]
##my $T_COMMA = T_OTHER(',');    # [CONSTANT]

sub readFrom {
  my ($self, $gullet, $until, %options) = @_;

  # if we want to force skipMissing keys, we set it up here
  my $silenceMissing = $options{silenceMissing} ? 1 : 0;
  my $skipMissing    = $$self{skipMissing};
  my $hookMissing    = $$self{hookMissing};

  # if we want to silence all missing errors, store them in a hook
  if ($silenceMissing) {
    $$self{skipMissing} = 1;
    $$self{hookMissing} = undef; }

  # read the opening token and figure out where we are
  my $startloc = $gullet->getLocator;
  # set and read tokens
  my $open = $gullet->readToken;
  # create arrays for key-value pairs and explicit values
  my @kv        = ();
  my @explicits = ();
  # iterate over all the key-value pairs to read
  while (1) {
    # gobble leading spaces
    $gullet->skipSpaces;
    if ($gullet->ifNext(T_BEGIN)) {    # Protect against redundant {} wrapping
      $gullet->readToken;
      $gullet->unread($gullet->readBalanced()->stripBraces);
      $gullet->skipSpaces; }
    # Read a single keyword, get a delimiter and a set of keyword tokens
    my ($ktoks, $delim) = $self->readKeyWordFrom($gullet, $until);

    # if there was no delimiter at the end, we throw an error
    Error('expected', $until, $gullet,
      "Fell off end expecting " . Stringify($until) . " while reading KeyVal key",
      "key started at " . ToString($startloc))
      unless $delim;

    # turn the key tokens into a string and trim whitespace
    my $key = ToString($ktoks); $key =~ s/^\s+//; $key =~ s/\s+$//;
    # if we have a non-empty key
    if ($key) {
      my $value;
      my $isDefault;
      # if we have an '=', we explcity assign a value
      if ($delim->equals(T_OTHER('='))) {
        $isDefault = 0;
        # setup the key-codes to properly read
        my $keyset  = $self->getPrimaryKeyValOf($key, $self->resolveKeyValFor($key));
        my $keytype = keyval_get(keyval_qname($$self{prefix}, $keyset, $key), 'type');
        $keytype->setupCatcodes if $keytype;
        # read until comma
        my ($tok, @toks) = ();
        while ((!defined($delim = $gullet->readMatch(T_OTHER(','), $until)))
          && (defined($tok = $gullet->readToken()))) {    # Copy next token to args
          push(@toks, $tok,
            ($tok->getCatcode == CC_BEGIN ? ($gullet->readBalanced, T_END) : ())); }
        # reparse (and expand) the tokens representing the value
        $value = Tokens(@toks)->stripBraces;
        $value = $keytype->reparse($gullet, $value) if $keytype && $value;
        # and cleanup
        $keytype->revertCatcodes if $keytype; }
      # we did not get an '=', and thus need to read the default value
      else { $isDefault = 1; }
      # and store our value please
      $self->addValue($key, $value, $isDefault, 0) if (!$silenceMissing || $self->canResolveKeyValFor($key)); }

    # we finish if we have the last element
    last if $delim->equals($until); }

  # rebuild and return nothing
  $self->rebuild;

  # restore all settings if we silenced the missing keys
  if ($silenceMissing) {
    $$self{skipMissing} = $skipMissing;
    $$self{hookMissing} = $hookMissing; }

  return; }

sub readKeyWordFrom {
  my ($self, $gullet, $close) = @_;
  my @tokens = ();
  my @delim  = ($close, T_OTHER(','), T_OTHER('='));
  $gullet->skipSpaces;    # Skip leasding spaces
  my $token;
  while ($token = $gullet->readXToken) {
    # skip to the next iteration if we have a paragraph
    next if $token->equals(T_CS('\par'));
    # if we have one of out delimiters, we end
    last if grep { $token->equals($_) } @delim;
    push(@tokens, $token); }
  # return the tokens and the last token
  return (Tokens(@tokens), $token); }

#======================================================================
# Public accessors of all the values
#======================================================================
# Note: The API of this need to be stable, as people may be using it

# return the value of a given key. If multiple values are given, return the last one.
sub getValue {
  my ($self, $key) = @_;
  my %hash  = %{ $$self{cachedHash} };
  my $value = $hash{$key};
  # Since we (by default) accumulate lists of values when repeated,
  # we need to provide the "common" thing: return the last value given.
  return (!defined $value ? undef : (ref $value eq 'ARRAY' ? $$value[-1] : $value)); }

# return a list of values for a given key
sub getValues {
  my ($self, $key) = @_;
  my %hash  = %{ $$self{cachedHash} };
  my $value = $hash{$key};
  return (!defined $value ? () : (ref $value eq 'ARRAY' ? @$value : ($value))); }

# return the set of key-value pairs
sub getPairs {
  my ($self) = @_;
  return @{ $$self{cachedPairs} }; }

# returns a key => ToString(value)
sub getHash {
  my ($self) = @_;
  return %{ $$self{cachedHash} }; }

# return a hash of key-value pairs
sub getKeyVals {
  my ($self) = @_;
  my %hash = %{ $$self{cachedHash} };
  return \%hash; }    # A COPY ??? or can it be the real hash?

# checks if the value for a given key exists
sub hasKey {
  my ($self, $key) = @_;
  return exists $$self{cachedHash}{$key}; }

#======================================================================
# Value Related Reversion
#======================================================================

# This apparently means "the expansion of \setkeys"
sub setKeysExpansion {
  my ($self)       = @_;
  my @skipkeys     = @{ $$self{skip} };
  my $setInternals = $$self{setInternals};
  my $prefix       = $$self{prefix};
  # we might have to store values in a seperate token
  my $rmmacro     = (ref $$self{skipMissing} ? $$self{skipMissing} : undef);
  my $hookMissing = $$self{hookMissing};
  my @rmtokens    = ();
  # read in existing tokens (if they are defined)
  if ($rmmacro && $STATE->lookupMeaning($rmmacro)) {
    @rmtokens = LaTeXML::Package::Expand($rmmacro)->unlist; }
  # define some xkeyval internals
  my @tokens = ();
  push(@tokens,
    T_CS('\def'), T_CS('\XKV@fams'), T_BEGIN, Explode(join(',', @{ $$self{keysets} })), T_END,
    T_CS('\def'), T_CS('\XKV@na'),   T_BEGIN, Explode(join(',', @skipkeys)),            T_END)
    if $setInternals;

  # iterate over the key-value pairs
  foreach my $tuple (@{ $$self{tuples} }) {
    my ($key, $value, $useDefault, $keyset, $keysets) = @$tuple;
    next if (grep { $_ eq $key } @skipkeys);    # Skip these keys
    my @keysets = @{$keysets};
    # we might need to save the macros that weren't saved
    if (scalar @keysets == 0) {
      if ($rmmacro) {
        push(@rmtokens, $self->revertKeyVal($key, $keyset, $value, $useDefault, (@rmtokens ? 0 : 1))); }
      my @reversion = $self->revertKeyVal($key, $keyset, $value, $useDefault);
      push(@tokens, $hookMissing, T_BEGIN, $self->revertKeyVal($key, $keyset, $value, $useDefault, 1), T_END) if $hookMissing;
      next; }

    # and iterate over all valid keysets
    foreach my $keyset (@keysets) {
      my $qname = keyval_qname($prefix, $keyset, $key);
      if (!HasKeyVal($prefix, $keyset, $key)) {
        Info('undefined', 'Encountered unknown KeyVals key',
          "'" . $key . "' with prefix '" . $prefix
            . "' not defined in '" . join(",", $keyset) . "'"); }
      elsif (keyval_get($qname, 'disabled')) {    # if we are disabled, return an empty tokens
        Warn('undefined', "`" . $key . "' has been disabled. "); }
      else {
        push(@tokens,                             # definition of 'xkeyval' internals (if applicable)
          T_CS('\def'), T_CS('\XKV@prefix'), T_BEGIN, Explode($prefix . '@'),                 T_END,
          T_CS('\def'), T_CS('\XKV@tfam'),   T_BEGIN, Explode($keyset),                       T_END,
          T_CS('\def'), T_CS('\XKV@header'), T_BEGIN, Explode($prefix . '@' . $keyset . '@'), T_END,
          T_CS('\def'), T_CS('\XKV@tkey'),   T_BEGIN, Explode($key),                          T_END
        ) if $setInternals;
        if ($useDefault) {                        # if it was not given explicitly, call the default macro
          push(@tokens, T_CS('\\' . $qname . '@default')); }
        else {    # we have a value given, call the appropriate macro with it
          push(@tokens, T_CS('\\' . $qname), T_BEGIN, Revert($value), T_END); }
        # 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;
  } } }

  # and assign the skipmissing macro with the other keys
  push(@tokens, T_CS('\def'), $rmmacro, T_BEGIN, @rmtokens, T_END) if $rmmacro;
  # reset all the internals (if applicable)
  push(@tokens,
    T_CS('\def'), T_CS('\XKV@fams'), T_BEGIN, T_END,
    T_CS('\def'), T_CS('\XKV@na'),   T_BEGIN, T_END) if $setInternals;

  return Tokens(@tokens); }

sub beDigested {
  my ($self, $stomach) = @_;

  if ($$self{was_digested}) {
    Info('ignore', 'keyvals', $self,
      "Skipping digestion of \\setkeys as requested (did you digest a KeyVals twice?) "); }
  else {
    $stomach->digest($self->setKeysExpansion); }

  # iterate over the tuples, digesting the values
  my @newtuples = ();
  foreach my $tuple (@{ $$self{tuples} }) {
    my ($key, $value, $useDefault, $keyset, $keysets) = @$tuple;
    my $keytype = keyval_get(keyval_qname($$self{prefix}, $keyset, $key), 'type');
    my $v       = (defined $value ?
        ($keytype ? $keytype->digest($stomach, $value, undef) : $value->beDigested($stomach))
      : undef);
    push(@newtuples, [$key, $v, $useDefault, $keyset, $keysets]); }

  # then Copy the current object
  my $new = LaTeXML::Core::KeyVals->new(
    $$self{prefix}, $$self{keysets},
    setAll       => $$self{setAll}, setInternals => $$self{setInternals},
    skip         => $$self{skip},   skipMissing  => $$self{skipMissing},
    hookMissing  => $$self{hookMissing},
    was_digested => 1,
    tuples       => [@newtuples]);
  return $new; }

sub revert {
  my ($self) = @_;
  # read values from class
  my @tokens = ();
  # iterate over the key-value pairs
  foreach my $tuple (@{ $$self{tuples} }) {
    my ($key, $value, $useDefault, $keyset, $keysets) = @$tuple;
    # revert a single token
    if ($keyset) {    # when is this undef?
      push(@tokens, $self->revertKeyVal($key, $keyset, $value, $useDefault, (@tokens ? 0 : 1))); } }

  # and return the list of tokens
  return Tokens(@tokens); }

# turns this object into a string
sub toString {
  my ($self) = @_;
  my @kv     = $self->getPairs;
  my $string = '';
  while (@kv) {
    my ($key, $value) = (shift(@kv), shift(@kv));
    $string .= ',' if $string;
    $string .= $key . '=' . ToString($value); }
  return $string; }

sub revertKeyVal {
  my ($self, $key, $keyset, $value, $useDefault, $isFirst) = @_;
  # get the key-value definition
  my $keytype = keyval_get(keyval_qname($$self{prefix}, $keyset, $key), 'type');
  # define the tokens
  my @tokens = ();
  # write comma and key, unless in the first iteration
  push(@tokens, T_OTHER(',')) if !$isFirst;
  push(@tokens, Explode($key));
  # write the default (if applicable)
  if (!$useDefault && $value) {
    push(@tokens, T_OTHER('='), ($keytype ? $keytype->revert($value) : Revert($value))); }
  return @tokens; }

# TODO: ????
sub unlist {
  my ($self) = @_;
  return $self; }

#======================================================================
1;

__END__

=pod

=head1 NAME

C<LaTeXML::Core::KeyVals> - Key-Value Pairs in LaTeXML

=head1 DESCRIPTION

Provides a parser and representation of keyval pairs
C<LaTeXML::Core::KeyVals> represents parameters handled by LaTeX's keyval package.
It extends L<LaTeXML::Common::Object>.

=head2 Accessors

=over 4

=item C<< GetKeyVal($arg,$key) >>

Access the value associated with a given key. 
This is useful within constructors to access the value associated with C<$key> 
in the argument C<$arg>. Example usage in a copnstructor:

<foo attrib='&GetKeyVal(#1,'key')'>

=item C<< GetKeyVals($arg) >>

Access the entire hash. Can be used in a constructor like:
Can use in constructor: <foo %&GetKeyVals(#1)/>

=back

=head2 Constructors

=over 4

=item C<<LaTeXML::Core::KeyVals->new(I<prefix>, I<keysets>, I<options>)); >>

Creates a new KeyVals object with the given parameters. 
All arguments are optional and the simples way of calling this method is 
C<< my $keyvals = LaTeXML::Core::KeyVals->new() >>. 

I<prefix> is the given prefix all key-value pairs operate in and defaults to 
C<'KV'>. If given, prefix should be a string. 

I<keysets> should be a list of keysets to find keys inside of. If given, it
should either be reference to a list of strings or a comma-separated string. 
This argument defaults to C<'_anonymous_'>. 

Furthermore, the KeyVals constructor accepts a variety of options that can
be used to customize its behaviour. These are I<setAll>, I<setInternals>, 
I<skip>, I<skipMissing>, I<hookMissing>, I<open>, I<close>.

I<setAll> is a flag that, if set, ensures that keys will be set in all existing
keysets, instead of only in the first one. 

I<setInternals> is a flag that, if set, ensures that certain 'xkeyval' package
internals are set during key digestion. 

I<skip> should be a list of keys to be skipped when digesting the keys of this
object. 

I<skipMissing> allows one way of handling keys during key digestion
that have not been explictilty declared using C<DefKey> or related
functionality. If set to C<undef> or C<0>, an error is thrown upon trying to set
such a key, if set to C<1> they are ignored. Alternatively, this can be set to a
key macro which is then extended to contain a comman-separated list of the
undefined keys. 

I<hookMissing> allows one to call a specific macro if a single key is unknown during
key digestion. 

=back

=head2 KeyVals Accessors (intended for internal usage)

=over 4

=item C<< $keyvals->setTuples(@tuples) >>

Sets the I<tuples> which should be a list of five-tuples (array references) representing
the key-value pairs this KeyVals object is seeded with. See the I<getTuples>
function on details of the structure of this list. 
I<rebuild> is called automatically to populate the other caches. 
Typically, the tuples is set by I<readFrom>.

=back

=head2 Resolution to KeySets

=over 4

=item C<< my @keysets = $keyvals->resolveKeyValFor($key) >>

Finds all keysets that should be used for interacting with the given
I<key>. May return C<undef> if no matching keysets are found. Use the parameters
 I<keysets>, I<setAll> and I<skipMissing> to customize the exact behaviour of
this function.

=item C<< my $canResolveKeyVal = $keyvals->canResolveKeyValFor($key) >>

Checks if this I<KeyVals> object can resolve a KeyVal for I<key>. Ignores
I<setAll> and I<skipMissing> parameters. 

=item C<< my $keyval = $keyvals->getPrimaryKeyValOf($key, @keysets) >>

Gets the primary keyset to be used for interacting a a single I<key>,
given that it resolves to I<keysets>. Defaults to first keyset in KeyVals, if none given.

=back

=head2 Changing contained values

=over 4

=item C<< $keyvals->addValue($key, $value, $useDefault, $noRebuild) >>

Adds the given I<value> for I<key> at the end of the given list of values and
rebuilds all internal caches. If the I<useDefault> flag is set, the specific
value is ignored, and the default is set instead. 

If this function is called multiple times the I<noRebuild> option should be 
given to prevent constant rebuilding and the I<rebuild> function should be 
called manually called. 

=item C<< $keyvals->setValue($key, $value, $useDefault) >>

Sets the value of I<key> to I<value>, optionally using the default if 
I<useDefault> is set. Note that if I<value> is a reference to an array, 
the key is inserted multiple times. If I<value> is C<undef>, the values is
deleted. 

=item C<< $keyvals->rebuild($skip) >>

Rebuilds the internal caches of key-value mapping and list of pairs from from
main list of tuples. If I<skip> is given, all values for the given key are
omitted, and the given key is deleted. 

=back

=head2 Parsing values from a gullet

=over 4

=item C<< $keyvals->readFrom($gullet, $until, %options) >>

Reads a set of KeyVals from I<gullet>, up until the I<until> token, and updates
the state of this I<KeyVals> object accordingly. 

Furthermore, this methods supports several options. 

When the I<silenceMissing> option is set, missing keys will be completely
ignored when reading keys, that is they do not get recorded into the KeyVals
object and no warnings or errors will be thrown. 

=item C<< $keyvals->readKeyWordFrom($gullet, $until) >>

Reads a single keyword from I<gullet>. Intended for internal use only. 

=back

=head2 KeyVals Accessors

=over 4

=item C<< my $value = $keyvals->getValue($key); >>

Return a value associated with C<$key>. 

=item C<< @values = $keyvals->getValues($key); >>

Return the list of all values associated with C<$key>. 

=item C<< %keyvals = $keyvals->getKeyVals; >>

Return the hash reference containing the keys and values bound in the C<$keyval>.
Each value in the hash may be a single value or a list if the key is repeated. 

=item C<< @keyvals = $keyvals->getPairs; >>

Return the alternating keys and values bound in the C<$keyval>.
Note that this may contain multiple entries for a given key, if they
were repeated. 

=item C<< %hash = $keyvals->getHash; >>

Return the hash reference containing the keys and values bound in the C<$keyval>.
Note that will only contain the last value for a given key, if they
were repeated.

=item C<< $haskey = $keyvals->hasKey($key); >>

Checks if the KeyVals object contains a value for $key. 

=back

=head2 Value Related Reversion

=over 4

=item C<< $expansion = $keyvals->setKeysExpansion; >>

Expand this KeyVals into a set of tokens for digesting keys. 


=item C<< $keyvals = $keyvals->beDigested($stomach); >>

Return a new C<LaTeXML::Core::KeyVals> object with both keys and values
digested. 

=item C<< $reversion = $keyvals->revert(); >>

Revert this object into a set of tokens representing the original 
sequence of Tokens that was used to be read it from the gullet. 

=item C<< $str = $keyvals->toString(); >>

Turns this object into a key=value comma separated string. 

=back

=head1 AUTHOR

Bruce Miller <bruce.miller@nist.gov>
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