# -*- 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@originallx@longtable@label'), T_CS('\lx@longtable@label'));
DefMacro('\label Semiverbatim', '\lxRDF@original@label{#1}\lxRDFa{property=dcterms:alternative,content=#1}');
DefMacro('\lx@longtable@label Semiverbatim',
'\lxRDF@originallx@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}
|| !IsEmpty(($content)))) {
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;