# -*- mode: Perl -*-
# /=====================================================================\ #
# |  latexml.ltxml                                                      | #
# | Style file for latexml documents                                    | #
# |=====================================================================| #
# | 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::Package::Pool;
use strict;
use warnings;
use LaTeXML::Package;
no warnings 'redefine';    # ????

#======================================================================
# LaTeXML Implementation of latexml customization bindings
#  * controlling various conversion options
#  * presentation customization
#  * semantic enhancement macros
#  * exposing internal functionality
#======================================================================

DefConditional('\iflatexml', sub { 1; });
DefPrimitive('\latexmltrue', sub {
    Warning('unexpected', '\\latexmltrue', $_[0], "Cannot change \\iflatexml!"); });
DefPrimitive('\latexmlfalse', sub {
    Warning('unexpected', '\\latexmlfalse', $_[0], "Cannot change \\iflatexml!"); });

#======================================================================
# Package Options
DeclareOption('ids',   sub { AssignValue('GENERATE_IDS' => 1, 'global'); });
DeclareOption('noids', sub { AssignValue('GENERATE_IDS' => 0, 'global'); });

DeclareOption('comments',   sub { AssignValue('INCLUDE_COMMENTS' => 1, 'global'); });
DeclareOption('nocomments', sub { AssignValue('INCLUDE_COMMENTS' => 0, 'global'); });

DeclareOption('profiling',   sub { AssignValue('PROFILING' => 1, 'global'); });
DeclareOption('noprofiling', sub { AssignValue('PROFILING' => 0, 'global'); });

DeclareOption('mathparserspeculate',   sub { AssignValue('MATHPARSER_SPECULATE' => 1, 'global'); });
DeclareOption('nomathparserspeculate', sub { AssignValue('MATHPARSER_SPECULATE' => 0, 'global'); });

ProcessOptions();
#======================================================================
# From latexml.sty
# [does this really belong here? or should this be disableable?]
DefConstructor('\URL[] Verbatim', "<ltx:ref href='#href'>?#1(#1)(#href)</ltx:ref>",
  properties => sub { (href => CleanURL($_[2])); });
DefMacro('\XML',      '\URL[\texttt{XML}]{http://www.w3.org/XML/}');
DefMacro('\SGML',     '\URL[\texttt{HTML}]{http://www.w3.org/MarkUp/SGML/}');
DefMacro('\HTML',     '\URL[\texttt{HTML}]{http://www.w3.org/html/}');
DefMacro('\XHTML',    '\URL[\texttt{XHTML}]{http://www.w3.org/TR/xhtml11/}');
DefMacro('\XSLT',     '\URL[\texttt{XSLT}]{http://www.w3.org/Style/XSL/}');
DefMacro('\CSS',      '\URL[\texttt{CSS}]{http://www.w3.org/Style/CSS/}');
DefMacro('\MathML',   '\URL[\texttt{MathML}]{http://www.w3.org/Math/}');
DefMacro('\OpenMath', '\URL[\texttt{OpenMath}]{http://www.openmath.org/}');
DefMacro('\LaTeXML',  '\URL[\texttt{LaTeXML}]{http://dlmf.nist.gov/LaTeXML/}');
#DefMacro('\BibTeX','BibTeX');

#======================================================================
# id related features

# Set the id to used for the top-level document
DefMacro('\lxDocumentID{}', '\def\thedocument@ID{#1}');

# \LXMID{id}{math}  Associate the identifier id with the given math expression.
DefMacro('\LXMID{}{}', '\@XMArg{#1}{#2}');

# \LXRef{id} Refer to the math expression associated with id.
# In presentation, this is similar to using a shorthand macro.
# In content situations, an XMRef is generated.
DefMacro('\LXMRef{}', '\@XMRef{#1}');

#======================================================================
# class related features
Let('\lxAddClass', '\@ADDCLASS');

DefConstructor('\lxAddClass Semiverbatim', sub {
    $_[0]->addClass($_[0]->getElement, ToString($_[1])); });

DefConstructor('\lxWithClass Semiverbatim {}', sub {
    my ($document, $class, $box) = @_;
    my $context = $document->getElement;    # Where we originally start inserting.
    my @nodes   = ();
    if (isTextNode($document->getNode)) {
      push(@nodes, $document->openElement('ltx:text')); }
    push(@nodes, $document->absorb($box));
    @nodes = $document->filterChildren($document->filterDeletions(@nodes));
    $document->closeToNode($context);
    $document->addClass($nodes[0], ToString($class)) if @nodes; });

#======================================================================
# links
# Similar to stuff from hyperref, but more straightforward
DefConstructor('\lxRef Semiverbatim {}',
  "<ltx:ref labelref='#label'>#2</ltx:ref>",
  properties => sub { (label => CleanLabel($_[1])); });

#======================================================================
# Resources
# \lxResource[options]{name}
#  RequireResource($name,%options);

#======================================================================
# Page customization
#  options to create or customize
#    navbar: full content, context TOC, ...
#    headers, footers

DefMacro('\lxKeywords{}',
  '\@add@frontmatter{ltx:keywords}[name={keywords}]{#1}');

DefConstructor('\lxContextTOC',
  "<ltx:TOC format='context'/>");

AssignValue('navigation' => [], 'global');

sub insertNavigation {
  my ($document) = @_;
  if (my @items = @{ LookupValue('navigation') }) {
    $document->appendTree($document->getDocument->documentElement,
      ['ltx:navigation', {}, @items]); }
  return; }

Tag('ltx:document', 'afterClose' => \&insertNavigation);

DefEnvironment('{lxNavbar}', sub { },
  beforeDigest => sub { AssignValue(inPreamble => 0); },
  beforeConstruct => sub {
    my ($document, $whatsit) = @_;
    PushValue('navigation',
      ['ltx:inline-para', { class => 'ltx_page_navbar' }, $whatsit->getBody]);
    return; });

# Of course, it would be more interesting to supply a "template"
# for header & footer that would show where the next link goes,
# rather than predict what the next link will be! (after splitting!)
# Repeated header/footers should give multiple header/footer lines ?
# or do they just arrange the lines within it?
DefEnvironment('{lxHeader}', sub { },
  beforeDigest => sub { AssignValue(inPreamble => 0); },
  beforeConstruct => sub {
    my ($document, $whatsit) = @_;
    PushValue('navigation',
      ['ltx:inline-para', { class => 'ltx_page_header' }, $whatsit->getBody]);
    return; });
DefEnvironment('{lxFooter}', sub { },
  beforeDigest => sub { AssignValue(inPreamble => 0); },
  beforeConstruct => sub {
    my ($document, $whatsit) = @_;
    PushValue('navigation',
      ['ltx:inline-para', { class => 'ltx_page_footer' }, $whatsit->getBody]);
    return; });

#======================================================================

# Table beautification.
# Low-level support to mark column and row headers.
# This really calls for styling, but why should we get into that game?
# There are many other packages for that.

# To mark the table head/foot (table column headers)
# Put this before first table heading row
DefMacroI('\lxBeginTableHead', undef, '\@tabular@begin@heading');
# put this after the \\ ending the table heading.
DefMacroI('\lxEndTableHead', undef, '\@tabular@end@heading');

# Ditto for table foot (last rows in table)
DefMacroI('\lxBeginTableFoot', undef, '\@tabular@begin@heading');
# put this after the \\ ending the table heading.
DefMacroI('\lxEndTableFoot', undef, '\@tabular@end@heading');

# To mark an individual cell as a column header
DefMacroI('\lxTableColumnHead', undef, sub {
    if (my $alignment = LookupValue('Alignment')) {
      $alignment->currentColumn->{thead}{column} = 1; }
    return; });
# To mark an individual cell as a row header
DefMacroI('\lxTableRowHead', undef, sub {
    if (my $alignment = LookupValue('Alignment')) {
      $alignment->currentColumn->{thead}{row} = 1; }
    return; });
# Easy way to mark a whole column as row headers:
#  \usepackage{array}
# then put this in the column spec
#  >{\lxTableRowHead}

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Declarative information for Mathematics
# particularly those that assist parsing.
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#======================================================================
# Marking the type of particular instances of a symbol.

# \LxFcn{f} treat f as a function here (only).
DefConstructor('\lxFcn{}', "<ltx:XMWrap role='FUNCTION'>#1</ltx:XMWrap>",
  requireMath => 1, reversion => '#1', alias => '');
DefConstructor('\lxID{}', "<ltx:XMWrap role='ID'>#1</ltx:XMWrap>",
  requireMath => 1, reversion => '#1', alias => '');
DefConstructor('\lxPunct{}', "<ltx:XMWrap role='PUNCT'>#1</ltx:XMWrap>",
  requireMath => 1, reversion => '#1', alias => '');

#======================================================================
# Define a mathematical object with both presentation & content information
#   \lxDefMath{\name}[nargs][optional]{presentation body}[content keywords]
#  The first part is essentially equivalent to \newcommand, it defines
# an expansion for \name used for the presentation.
# The content keywords are used to define the semantics of the object.
# See DefMath in LaTeXML::Package for more information.
DefPrimitive('\lxDefMath{}[Number][]{} OptionalKeyVals:XMath', sub {
    my ($stomach, $cs, $nargs, $opt, $presentation, $params) = @_;
    my ($name, $meaning, $cd, $role) =
      $params && map { $_ && ToString($_) } map { $params->getValue($_) } qw(name meaning cd role);
    DefMathI($cs, convertLaTeXArgs($nargs, $opt), $presentation,
      name => $name, meaning => $meaning, omcd => $cd, role => $role);
});
#DefKeyVal('XMath',name,string);

#======================================================================
# NOTE: I'm concerned about the order of applying these filters.
# even though it seems right so far.

# Keyword options:
#    scope=<scope> : Specifies the scope of the declaration, ie. to what portion of
#            the document the declarations apply
#            You can specify one of the counters associated with sections,
#            equations, etc.
#            If unspecified, the declaration is scoped to the current unit.
#            Note that this applies to equations, as well.
#    label=<label> : assigns a label to the declaration so that it can be reused
#            at another point in the document (with \lxRefDeclaration), particularly when
#            that point is not otherwise within the scope of the original declaration.
# To effect the declaration:
#    role=<role>  : Assigns a grammatical role to the matched item for parsing.
#    name=<name>  : Assigns a name to the matched item.
#    meaning=<meaning>  : Assigns a semantic name to the matched item.
#  Alternatively, use
#     replace : provides a replacement for the matched expression, rather than adding attributes.

# Potential keywords/operations needed(?)
#   nodef : inhibits the marking of the current point as the `definition' of the expression.
#          (a ref declaration would normally not be a def anyway)

DefKeyVal('Declare', 'wrap',  '{}', 1);
DefKeyVal('Declare', 'trace', '{}', 1);
DefKeyVal('Declare', 'replace', 'UndigestedKey');

our $declare_keys = { scope => 1, role => 1, tag => 1, name => 1, meaning => 1,
  trace => 1, wrap => 1, replace => 1, label => 1 };
DefConstructor('\lxDeclare OptionalMatch:* OptionalKeyVals:Declare {}',
  sub { my ($document, $flag, $kv, $pattern, %props) = @_;
    if (my $id = $props{id}) {
      my $save = $document->floatToElement('ltx:declare');
      $document->openElement('ltx:declare', 'xml:id' => $id);
      if (my $tag = $kv->getValue('tag')) {
        $document->insertElement('ltx:tag', $tag); }
      $document->closeElement('ltx:declare');
      $document->setNode($save); } },
  mode => 'text',
  # Screwy bit to `neutralize' the font in the matches.
  beforeDigest => sub { AssignValue(font => LaTeXML::Common::Font->textDefault(), 'local');
    AssignValue(mathfont => LaTeXML::Common::Font->mathDefault(), 'local');
    return; },
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my ($star, $keys, $pattern) = $whatsit->getArgs;
    return unless $keys;
    CheckOptions("\\lxDeclare keys", $declare_keys, %{ $keys->getKeyVals });
    foreach my $key (qw(role tag name meaning replace)) {
      if (my $value = $keys->getValue($key)) {
        Warn('unexpected', $key, $stomach,
          "Repeated $key: " . join('; ', map { Stringify($_) } @$value))
          if ref $value eq 'ARRAY'; } }
    my $id = ($keys->getValue('tag') ? next_declaration_id() : undef);
    if ($id && LookupValue('InPreamble')) {
      Warn('unexpected', 'tag', $stomach,
        "Declaration with tag cannot appear in preamble"
          . Stringify($whatsit)); }
    $whatsit->setProperties(scope => getDeclarationScope($keys),
      role    => ToString($keys->getValue('role')),
      name    => ToString($keys->getValue('name')),
      meaning => ToString($keys->getValue('meaning')),
      trace   => defined $keys->getValue('trace'),
      wrap    => defined $keys->getValue('wrap'),
      id      => $id,
      match   => $pattern,
      replace => $keys->getValue('replace'));

    if (my $label = ToString($keys->getValue('label'))) {
      PushValue("Declaration_$label", $whatsit); }
    return; },
  afterConstruct => sub { my ($document, $whatsit) = @_;
    my $scope = $whatsit->getProperty('scope');
    createDeclarationRewrite($document, $scope, $whatsit); },
  properties => { alignmentSkippable => 1 },
  reversion => '');

DefConstructor('\lxRefDeclaration OptionalKeyVals:Declare {}', '',
  afterDigest => sub { my ($stomach, $whatsit) = @_;
    my ($keys, $labels) = $whatsit->getArgs;
    $whatsit->setProperties(scope => getDeclarationScope($keys),
      labels => [split(',', ToString($labels))]); },
  afterConstruct => sub { my ($document, $whatsit) = @_;
    my $scope = $whatsit->getProperty('scope');
    foreach my $label (@{ $whatsit->getProperty('labels') }) {
      if (my $declaration = LookupValue("Declaration_$label")) {
        map { createDeclarationRewrite($document, $scope, $_) } @$declaration; }
      else {
        Warn('unexpected', $label, $document,
          "No Declaration with label=$label was found"); } } },
  properties => { alignmentSkippable => 1 },
  reversion => '');

sub next_declaration_id {
  StepCounter('@XMDECL');
  DefMacroI(T_CS('\@@XMDECL@ID'), undef,
    Tokens(Explode(LookupValue('\c@@XMDECL')->valueOf)),
    scope => 'global');
  return ToString(Expand(T_CS('\the@XMDECL@ID'))); }

sub getDeclarationScope {
  my ($keys) = @_;
  # Sort out the scope.
  my $scope = $keys && $keys->getValue('scope');
  $scope = ($scope ? ToString($scope) : LookupValue('current_counter'));
  if ($scope && LookupValue("\\c\@$scope")) {    # Scope is some counter.
    $scope = "id:" . ToString(Digest(Expand(T_CS("\\the$scope\@ID")))); }
  return $scope; }

sub createDeclarationRewrite {
  my ($document, $scope, $whatsit) = @_;
  my %props = $whatsit->getProperties;
  my ($id, $match, $wrap, $role, $name, $meaning, $ref, $trace, $replace)
    = map { $props{$_} } qw(id match wrap role name meaning ref trace replace);
###  print STDERR "REWRITER: scope=>$scope, match=>$match, role=>$role, dec_id=>$id\n";
  # Put this rule IN FRONT of other rules!
  UnshiftValue('DOCUMENT_REWRITE_RULES',
    LaTeXML::Core::Rewrite->new('math',
      ($trace ? (trace => $trace) : ()),
      ($scope ? (scope => $scope) : ()),
      ($match ? (match => $match) : ()),
      ($wrap  ? (wrap  => $wrap)  : ()),
      ($replace
        ? (replace => $replace)
        : attributes => { ($role ? (role => $role) : ()),
          ($name    ? (name    => $name)    : ()),
          ($meaning ? (meaning => $meaning) : ()),
          ($id      ? (dec_id  => $id)      : ()),
          }),
    ));
  return; }

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