Skip to content

Commit

Permalink
now under 100 lines of code, with straightened out logic routes and m…
Browse files Browse the repository at this point in the history
…ore comments
  • Loading branch information
dginev committed Jul 10, 2020
1 parent 6a0aff1 commit b5a47ff
Showing 1 changed file with 51 additions and 87 deletions.
138 changes: 51 additions & 87 deletions lib/LaTeXML/Post/MathML/Presentation.pm
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use warnings;
use base qw(LaTeXML::Post::MathML);
use LaTeXML::Post::MathML qw(getQName);
use LaTeXML::MathParser qw(p_getAttribute p_setAttribute);
use LaTeXML::Common::XML qw(isElementNode);
use LaTeXML::Common::XML;

sub preprocess {
my ($self, $doc, @maths) = @_;
Expand Down Expand Up @@ -71,8 +71,6 @@ sub convertNode {
sub rawIDSuffix {
return '.pmml'; }

use Data::Dumper;

sub associateNodeHook {
# technical note: $sourcenode is a LibXML element, while $node is that OR the arrayref triple form
my ($self, $node, $sourcenode, $noxref, $currentnode) = @_;
Expand All @@ -88,9 +86,9 @@ sub associateNodeHook {
$self->addAccessibilityAnnotations($node, $currentnode);
return; }

# Experiment: set accessibility attributes on the resulting presentation tree,
# if the XMath source has a claim to the semantics via a "meaning" attribute.
sub addAccessibilityAnnotations {
# Experiment: set accessibility attributes on the resulting presentation tree,
# if the XMath source has a claim to the semantics via a "meaning" attribute.
# Part I: Top-down. Recover the meaning of a subtree as an accessible annotation
my ($self, $node, $currentnode) = @_;
my $current_node_name = getQName($currentnode);
Expand All @@ -101,117 +99,83 @@ sub addAccessibilityAnnotations {
# that second call should just immediately terminate, there is nothing to add in such cases.
return if $currentnode->getAttribute('_a11y_done');
$currentnode->setAttribute('_a11y_done', '1');
my $id = $currentnode->getAttribute('xml:id');
my ($meaning, $arg);
# FIRST AND FOREMOST, run an exclusion check for pieces that are presentation-only fluff for duals
# namely:
# --- TOP PRIORITY: run an exclusion check for pieces that are presentation-only fluff for duals
my @dual_pres_ancestry = $LaTeXML::Post::DOCUMENT->findnodes("ancestor-or-self::*[preceding-sibling::*][parent::ltx:XMDual]", $currentnode);
my $dual_pres_node = $dual_pres_ancestry[-1]; # Weirdly ->findnode() is finding the highest ancestor, rather than the tightest ancestor? This [-1] seems to do it.
if ($dual_pres_node) { # 1) they have a dual ancestor
# 2) no node on the path to that dual has a "id"
if ($dual_pres_node && !$dual_pres_node->isSameNode($currentnode)) { # 1) they have a dual ancestor, but are not the main presentation node
my $check_node = $currentnode;
my $id = $currentnode->getAttribute('xml:id');
while (!$id && !$check_node->isSameNode($dual_pres_node)) {
$id = $check_node->getAttribute('xml:id');
$check_node = $check_node->parentNode; }
if (!$id) {
# 3) they're not "The Main Presentation" node, which is where we want to annotate duals
return unless $currentnode->isSameNode($dual_pres_node); } }
# All other cases, process the node, it has meaningful annotations to add, handle them first
if ($dual_pres_node && $dual_pres_node->isSameNode($currentnode)) { # top-level, annotate with semantic, and potentially arg
my $content_child = $dual_pres_node->previousSibling;
my $op_literal;
if (getQName($content_child) eq 'ltx:XMRef') {
$op_literal = '#op'; # important: we have a clear match in the presentation, so the operator will have an arg
$content_child = $LaTeXML::Post::DOCUMENT->realizeXMNode($content_child); }
if (getQName($content_child) eq 'ltx:XMTok') { # not an else, since this may have just been realized from XMRef
# another exception! (x) will have meaning x, so...
undef $op_literal;
# if no id is found, they are not referenced by the dual
return unless $id; }
# ---
# In the remaining cases, process the node, check if it has meaningful annotations to add
my ($meaning, $arg);
if ($dual_pres_node && $dual_pres_node->isSameNode($currentnode)) { # top-level pres of dual
my $dual_content_node = $dual_pres_node->previousSibling;
my $dual_content_name = getQName($dual_content_node);
if ($dual_content_name eq 'ltx:XMRef') { # single subtree of the presentation, point to it
$meaning = '#1'; }
else {
my $op_node = $content_child->firstChild;
$op_literal = $op_literal || ($op_node && $op_node->getAttribute('meaning')) || '#op';
my @arg_nodes = $content_child->childNodes;
elsif ($dual_content_name eq 'ltx:XMTok') {
$meaning = $dual_content_node->getAttribute('meaning'); }
elsif ($dual_content_name eq 'ltx:XMApp') {
my $op_node = $dual_content_node->firstChild;
my $op = ($op_node && $op_node->getAttribute('meaning')) || '#op';
my @arg_nodes = element_nodes($dual_content_node);
my $arg_count = scalar(@arg_nodes) - 1;
$meaning = $op_literal . '(' . join(",", map { '#' . $_ } (1 .. $arg_count)) . ')'; }
# Note that if the carrier ltx:XMDual had a id, it would get lost as we never visit it through this hook.
$meaning = $op . '(' . join(",", map { '#' . $_ } (1 .. $arg_count)) . ')'; }
# Note that if the carrier ltx:XMDual had a referenced id, it would get lost as we never visit it through this hook.
# to correct that, assign it in the top presentation child
if (!$id) {
my $dual = $dual_pres_node->parentNode;
my $grand_dual = $dual->parentNode;
if (my $id = $dual->getAttribute('xml:id')) {
# But we can't reuse the common logic, since it will comapare the dual with itself rather than its parent, ugh
while (getQName($grand_dual) ne 'ltx:XMDual') { $grand_dual = $grand_dual->parentNode; }
# this HAS to be an apply child right??
my @grand_content_args = $grand_dual->firstChild->childNodes;
my $grand_args_count = scalar(@grand_content_args);
my $index = 0;
while (my $grand_content_arg = shift @grand_content_args) {
if (($grand_content_arg->getAttribute('idref') || '') eq $id) {
$arg = $index ? $index : ($grand_args_count > 1 ? 'op' : '1'); }
else { $index++; } } }
elsif (getQName($grand_dual) eq 'ltx:XMApp') {
# simpler case of the dual being an simple argument, as in x\in(0,1)
my $index = 0;
my $prev = $dual->previousSibling;
while ($prev) {
$index++;
$prev = $prev->previousSibling; }
$arg = $index ? $index : 'op'; } } }
my $dual = $dual_pres_node->parentNode;
if (my $id = $dual->getAttribute('xml:id')) {
$self->addAccessibilityAnnotations($node, $dual); } }
# tokens are simplest - if we know of a meaning, use that for accessibility
elsif ($current_node_name eq 'ltx:XMTok') {
$meaning = $currentnode->getAttribute('meaning'); }
elsif ($current_node_name eq 'ltx:XMApp') {
my @src_children = $currentnode->childNodes;
my $arg_count = scalar(@src_children) - 1;
my @current_children = element_nodes($currentnode);
my $current_op_meaning = $current_children[0]->getAttribute('meaning') || '';
my $arg_count = scalar(@current_children) - 1;
# Ok, so we need to disentangle the case where the operator XMTok is preserved in pmml,
# and the case where it isn't. E.g. in \sqrt{x} we get a msqrt wrapper, but no dedicated token
# so we need to mark the literal "square-root" in msqrt
my $op_literal = $src_children[0]->getAttribute('meaning');
my $name = getQName($node);
if ($op_literal and $name ne 'm:mrow') { # assume we have phased out the operator node. Are there counter-examples?
$meaning = $op_literal . '(' . join(",", map { '#' . $_ } (1 .. $arg_count)) . ')'; }
elsif ($name eq 'm:mrow') {
# usually an mrow keeps the operator token in its children as an <mo> (or such)
# when doesn't it? one example is "multirelation", is there a general pattern?
if ($op_literal and $op_literal eq 'multirelation') {
$meaning = $op_literal . '(' . join(",", map { '#' . $_ } (1 .. $arg_count)) . ')'; }
else { # default case, assume we'll find the @op inside
$meaning = '#op(' . join(",", map { '#' . $_ } (1 .. $arg_count)) . ')'; } } }
my $op;
my $name = getQName($node);
if ($name ne 'm:mrow') { # not an mrow, prefer the literal semantic
$op = $current_op_meaning || $name; }
else { # mrow, prefer #op, except for whitelisted exception cases (which ones??)
$op = ($current_op_meaning eq 'multirelation') ? $current_op_meaning : '#op'; }
if ($op) { # Set the meaning, if we found a satisfying $op:
$meaning = "$op(" . join(",", map { '#' . $_ } (1 .. $arg_count)) . ")"; } }

# if we found some meaning, attach it as an accessible attribute
p_setAttribute($node, 'data-semantic', $meaning) if $meaning;

# Part II: Bottom-up. Also check if argument of higher parent notation, mark if so.
# best to reset id here
$id = $currentnode->getAttribute('xml:id');
my $current_parent = $currentnode->parentNode;
my $index = 0;
my $index = 0;
# II.1 id-carrying nodes always point to their referrees.
if ($id) {
if ($dual_pres_node && (my $id = $currentnode->getAttribute('xml:id'))) {
# We already found the dual
my $content_child = $dual_pres_node->previousSibling;
my @content_args = getQName($content_child) eq 'ltx:XMApp' ? ($content_child->childNodes) : ($content_child);
my $dual_content_node = $dual_pres_node->previousSibling;
my @content_args = getQName($dual_content_node) eq 'ltx:XMApp' ? element_nodes($dual_content_node) : ($dual_content_node);
my $arg_count = scalar(@content_args);
# if no compound-apply, no need for top-level dual annotation, leave it to the descendants
my $index = 0;
while (my $c_arg = shift @content_args) {
my $idref = $c_arg->getAttribute('idref') || '';
if ($idref eq $id) {
if ($id eq ($c_arg->getAttribute('idref') || '')) {
$arg = $index || ($arg_count >= 2 ? 'op' : '1');
} else {
last;
} else { # note that if we never find the 'idref', arg is never set
$index++; } } }
# II.2. applications children are directly pointing to their parents
elsif (getQName($current_parent) eq 'ltx:XMApp') {
my $op_node = $current_parent->firstChild;
if ($op_node->getAttribute('meaning')) { # only annotated applications we understand
my $prev_sibling = $currentnode;
while ($prev_sibling = $prev_sibling->previousSibling) {
$index++; }
if ($index == 0) {
$arg = 'op'; }
else {
$arg = $index; } } }
p_setAttribute($node, 'data-arg', $arg) if ($arg);
# also fallback in the dual case, if the XMApp had an id but wasn't an arg
if (!$arg && (getQName($currentnode->parentNode) eq 'ltx:XMApp')) {
my $prev_sibling = $currentnode;
while ($prev_sibling = $prev_sibling->previousSibling) {
$index++; }
$arg = $index ? $index : 'op'; }
p_setAttribute($node, 'data-arg', $arg) if $arg;
return; }

#================================================================================
Expand Down

0 comments on commit b5a47ff

Please sign in to comment.