# -*- mode: Perl -*-
# /=====================================================================\ #
# |  lxRDFa                                                             | #
# | LaTeXML support for RDFa                                            | #
# |=====================================================================| #
# | 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;

#======================================================================
# Package Options
DeclareOption('labels', sub {
    Let(T_CS('\lxRDF@original@label'),           T_CS('\label'));
    Let(T_CS('\lxRDF@original@longtable@label'), T_CS('\@longtable@label'));
    DefMacro('\label Semiverbatim', '\lxRDF@original@label{#1}\lxRDFa{property=dcterms:alternative,content=#1}');
    DefMacro('\@longtable@label Semiverbatim',
      '\lxRDF@original@longtable@label{#1}\lxRDFa{property=dcterms:alternative,content=#1}');
});

ProcessOptions();
#======================================================================
# Context

# \lxRDFaPrefix{prefix}{initialurl}
# Associates a prefix with the given (partial) URI
# for use in RDFa Compact URI's.
# These associations are global, not attached to the current node.
# If prefix is empty, this defines the vocabulary(?)
DefPrimitive('\lxRDFaPrefix{}{}', sub {
    my ($stomach, $prefix, $url) = @_;
    $prefix = ToString(Expand($prefix));
    $url    = CleanURL(ToString(Expand($url)));
    if ($prefix) {
      AssignMapping('RDFa_prefixes', $prefix => $url); }
    else {
      AssignValue('RDFa_vocabulary' => $url, 'global'); }
    return; });

#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# Keywords for RDF Attributes

#======================================================================
# about: Establishes the subject for predicates appearing on the current element or descendants.
#   about can be SafeCURIE, CURIE or IRI;
#   Or, if the keyword is of the form \ref{label} or #id,
#   it actually sets the aboutlabelref or aboutidref attribute (resp).
#   These will be resolved back to a IRI for about during postprocessing.
#   Note, however, that otherwise, the about need not be an explicit reference
#   to a part of the document; it can be 'vitual'.
DefKeyVal('RDFa', about => 'Semiverbatim');
#======================================================================
# resource: Specifies the object of the a @property on the same element,
#   AND specifies the subject for any predicates on descendant elements (chaining)
#   resource can be SafeCURIE, CURIE or IRI;
#   Or, if \ref{label} or #id, specifies resourcelabelref or resourceidref attribute.
DefKeyVal('RDFa', resource => 'Semiverbatim');
#======================================================================
# typeof : if @resource is on the same element, it forms a new triple indicating the type
#   Otherwise, it creates an anonymous resource (blank node or bnode) with that type
#   typeof can be a space separated list of: Term, CURIE or Abs. IRI
DefKeyVal('RDFa', typeof => 'Semiverbatim');    # space sep list of: Term, CURIE or Abs. IRI
#======================================================================
# property: specifies predicate(s) and asserts that the current subject is related to object
#   * subject is @about on same element, or @resource/@typeof on ancestor, or document root;
#   * object is @resource, @href, @content, @typeof on same element, or the text content
#   resource can be a space separated list of: Term, CURIE or Abs. IRI.
DefKeyVal('RDFa', property => 'Semiverbatim');
#======================================================================
# rel : Exactly the same as @property, except that
#   * can form multiple triples,
#   * the objects being nearest @resource,@href on same or descendent
#   rel can be a space separated list of: Term, CURIE or Abs. IRI.
DefKeyVal('RDFa', rel => 'Semiverbatim');
#======================================================================
# rev : Exactly the same as @rel, except that subject & object are reversed.
#   rev can be a space separated list of: Term, CURIE or Abs. IRI
DefKeyVal('RDFa', rev => 'Semiverbatim');
#======================================================================
# content: specifies the object as a plain string in place of the element's content().
DefKeyVal('RDFa', content => 'Semiverbatim');    # CDATA
#======================================================================
# datatype: specifies the datatype of the content/content()
DefKeyVal('RDFa', datatype => 'Semiverbatim');    # Term, CURIE or Abs. IRI
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# href : specifies the object of a predicate.
#   It is similar to @resource but does not chain to indicate subject
#   @labelref, @idref and @href, and thus <ltx:ref>, can thus participate
#   in RDFa by indicating the object of a predicate.
#======================================================================
# src : also indicates the object of a predicate
#    @src doesn't appear directly in LaTeXML's schema,
#    but <ltx:graphics @source> does, and is mapped to <img src..> in html.
#    Should @source then act as an object?
#======================================================================

# Decipher the RDF Keywords into a hash of RDF attributes,
# accounting for \ref{label} or #id in the about and resource keywords
sub RDFAttributes {
  my ($keyvals) = @_;
  $keyvals = $keyvals->beDigested($STATE->getStomach);
  my $hash = GetKeyVals($keyvals);
  my $x;
  foreach my $key (qw(about resource)) {
    if (my $value = $$hash{$key}) {
      if ((ref $value eq 'LaTeXML::Core::Whatsit')
        && ($value->getDefinition->getCSName eq '\ref')) {
        $$hash{ $key . 'labelref' } = CleanLabel($value->getArg(2));
        delete $$hash{$key}; }
      elsif (($x = ToString($value)) && ($x =~ /^#(.+)$/)) {
        $$hash{ $key . 'idref' } = CleanID($1);
        delete $$hash{$key}; }
      else {
        $$hash{$key} = CleanURL($value); } } }    # at least clean it.
  return $hash; }

# It ought to be wrong to put resource (or resourceXXref) attributes on an ltx:ref...

# Given the RDF Keywords, and content (if any) check/complete the RDF triple
# Presumably an anonymous subject or object isn't sufficient?
sub RDFTriple {
  my ($keyvals, $content) = @_;
  my $attr = RDFAttributes($keyvals);
  if (!($$attr{about} || $$attr{aboutlabelref} || $$attr{aboutidref})) {
    $$attr{about} = ''; }    # Silently make the triple about the entire document
  if (!($$attr{property} || $$attr{rel} || $$attr{rev})) {
    Warn('expected', 'property', $keyvals,
      "Expected 'property' or 'rel' or 'rev' in RDF attributes"); }
  if (!($$attr{content} || $$attr{resource} || $$attr{resourcelabelref} || $$attr{resourceidref}
      || ($content && scalar($content->unlist)))) {
    Warn('expected', 'resource', $keyvals,
      "Expected 'resource' or 'content' in RDF attributes"); }
  return $attr; }

#======================================================================
# Adding RDF Attributes to arbitrary LaTeXML Markup.

# \lxRDFa[xpath]{keywordpairs} "RDF attributes"
#    Add any RDF attributes to a node.
# If xpath is given, the RDF attributes are added to the node
# indicated by the xpath expression, otherwise to the current node.
# You are responsible for how the attributes relate to those
# in both parent and children nodes, such as establishing
# of default subject or objects, chaining and so forth.
DefConstructor('\lxRDFa OptionalSemiverbatim RequiredKeyVals:RDFa', sub {
    my ($document, $xpath, $kv) = @_;
    my ($save, @nodes);
    $xpath = ToString($xpath);
    if ($xpath) {
      $save  = $document->getNode;
      @nodes = $document->findnodes(ToString($xpath), $save);
      Warn('expected', 'node', $document,
        "No node matched xpath $xpath for RDFa properties") unless scalar(@nodes); }
    else {
      $save  = $document->floatToAttribute('property');    # pic arbitrary rdf attribute
      @nodes = ($document->getElement);
      Warn('expected', 'node', $document,
        "No node matched accepts RDFa properties") unless scalar(@nodes); }
    my $attr = RDFAttributes($kv);
    foreach my $node (@nodes) {
      foreach my $k (sort keys %$attr) {
        # use direct method ($doc method doesn't skips about="", which we need!)
        $node->setAttribute($k => ToString($$attr{$k})); } }
    $document->setNode($save); });

#======================================================================
# \lxRDF[content]{keyvals}  "Add RDF triple"
#   Add an ltx:rdf element to the document.
# If content is given, it provides the content of the element,
# otherwise an invisible metadata container is created.
# In either case, with the given RDF attributes.
# This can appear anywhere in the document, including in the preamble.

# This version of \lxRDF is for use in the preamble.
# It is complicated because \@add@frontmatter provides us no way to
# do the extra processing of the RDF Keywords.
DefPrimitive('\lxRDF@preamble[]RequiredKeyVals:RDFa', sub {
    my ($stomach, $content, $kv) = @_;
    # Since this gets digested in the preamble...
    my $inpreamble = LookupValue('inPreamble');
    AssignValue(inPreamble => 0);
    my $attr = RDFTriple($kv);    # require complete triple, here.
    push(@{ LookupValue('frontmatter')->{'ltx:rdf'} },
      ['ltx:rdf', { map { ($_ => ToString($$attr{$_})) } keys %$attr },
        ($content ? (Digest(Tokens(T_BEGIN, $content, T_END))) : ())]);
    AssignValue(inPreamble => $inpreamble);
    return; });

# This version of \lxRDF is for use in the document body.
DefConstructor('\lxRDF@body[] RequiredKeyVals:RDFa', sub {
    # "^<ltx:rdf %&RDFTriple(#2,#1)>#1</ltx:rdf>",
    my ($document, $content, $kv) = @_;
    if (my $savenode = $document->floatToElement('ltx:rdf')) {
      my $rdf = $document->openElement('ltx:rdf');
      # a bit of trouble to add empty elements!
      my $attr = RDFTriple($kv, $content);
      foreach my $k (keys %$attr) {
        # use direct method ($doc method doesn't skips about="", which we need!)
        $rdf->setAttribute($k => ToString($$attr{$k})); }
      $document->absorb($content) if $content;
      $document->closeElement('ltx:rdf');
      $document->setNode($savenode); } },
  alias => '\lxRDF');

# Start with \lxRDF bound to the preamble form, which saves the rdf as frontmatter
Let('\lxRDF', '\lxRDF@preamble');
# When the document's begun, switch to the inline version.
PushValue('@at@begin@document', (T_CS('\let'), T_CS('\lxRDF'), T_CS('\lxRDF@body')));

#======================================================================
# \lxRDFAnnotate{keyvals}{text}  "Add RDF Annotated text"
# Create a visible text node, with the given RDF attributes.
# This node inherits whatever RDF context exists at that point,
# such as the current subject, chaining or whatever.
# Thus, you may want to give an explicit about.
DefConstructor('\lxRDFAnnotate RequiredKeyVals:RDFa {}',
  "<ltx:text %&RDFAttributes(#1,#2)>#2</ltx:text>");

#======================================================================
# Other shorthands might be useful, like
#  DefMacro('\lxRDFResource[]{}{}','\@lxRDF{about={#1},predicate={#2},resource={#3}}');
#  DefMacro('\lxRDFProperty[]{}{}','\@lxRDF{about={#1},predicate={#2},content={#3}}');
# or maybe leave to other applications, till I get some feedback ?

# Another useful thing might be the ability to attribute
#    the current node, the previous node, the parent node...?
#======================================================================
1;
