70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""The target set of targeted F3: the nodes a team's claims, data and quotations rest on.
|
|
|
|
Non-normative; see spec/08-fidelity.md §8.10.
|
|
"""
|
|
|
|
from .model import LEAVES, MATH_KINDS
|
|
from .text import is_digit
|
|
|
|
DIGIT_RULES = ("all", "anchored-sections", "none")
|
|
# The kinds that count as the section a node sits in: a section, or the front or back matter outside any.
|
|
SECTION_KINDS = frozenset(["section", "front", "back"])
|
|
DIGIT_KINDS = frozenset(["sentence", "cell"])
|
|
DIGIT_RUNS = frozenset(["text", "math"])
|
|
|
|
|
|
def has_digit_run(node):
|
|
"""Whether a `text` or `math` run of the node holds a digit (General Category Nd)."""
|
|
for r in node.get("content") or []:
|
|
if not isinstance(r, dict) or r.get("kind") not in DIGIT_RUNS:
|
|
continue
|
|
s = r.get("text") if r["kind"] == "text" else r.get("source")
|
|
if any(is_digit(ch) for ch in s or ""):
|
|
return True
|
|
return False
|
|
|
|
|
|
def enclosing_section(doc, nid):
|
|
"""The nearest ancestor-or-self that is a section, or the front or back matter, else the root."""
|
|
if doc.nodes[nid]["kind"] in SECTION_KINDS:
|
|
return nid
|
|
for a in reversed(doc.ancestors(nid)):
|
|
if doc.nodes[a]["kind"] in SECTION_KINDS:
|
|
return a
|
|
return doc.root
|
|
|
|
|
|
def target_set(doc, anchored, digit_rule="all"):
|
|
"""The target set as `[[node, reason], …]` in document order, with `reason` the first that applies of
|
|
`anchored`, `equation` and `digits`.
|
|
|
|
`anchored` lists the live nodes that extracts, dataset values, confirmed claim links, draft quotations or
|
|
pinned citations anchor; a container among them contributes its leaves. Every equation and equation line
|
|
is a target. Under `all`, so is every sentence or cell with a digit in a text or math run; under
|
|
`anchored-sections`, only those in a section that holds an anchored node; under `none`, none.
|
|
"""
|
|
if digit_rule not in DIGIT_RULES:
|
|
raise ValueError("digit rule must be one of %s" % ", ".join(DIGIT_RULES))
|
|
anchored = set(anchored)
|
|
unknown = sorted(a for a in anchored if a not in doc.nodes)
|
|
if unknown:
|
|
raise ValueError("anchored nodes are not live: %s" % ", ".join(unknown))
|
|
reasons = {}
|
|
for nid in sorted(anchored):
|
|
if doc.nodes[nid]["kind"] in LEAVES:
|
|
reasons[nid] = "anchored"
|
|
else:
|
|
for leaf in doc.leaves(nid):
|
|
reasons.setdefault(leaf, "anchored")
|
|
for nid in doc.preorder():
|
|
if doc.nodes[nid]["kind"] in MATH_KINDS:
|
|
reasons.setdefault(nid, "equation")
|
|
if digit_rule != "none":
|
|
sections = {enclosing_section(doc, a) for a in anchored}
|
|
for nid in doc.preorder():
|
|
n = doc.nodes[nid]
|
|
if n["kind"] in DIGIT_KINDS and has_digit_run(n):
|
|
if digit_rule == "all" or enclosing_section(doc, nid) in sections:
|
|
reasons.setdefault(nid, "digits")
|
|
return [[nid, reasons[nid]] for nid in doc.preorder() if nid in reasons]
|