Simon Clematide
Add morphological features toggle, hover tooltip, and UD documentation links
ae56e3f
Raw History Blame Contribute Delete
5.8 kB
"""Documentation links for the UPOS and basic dependency labels in a parse."""
import html
POS_NAMES = {
"ADJ": "Adjective", "ADP": "Adposition", "ADV": "Adverb",
"AUX": "Auxiliary", "CCONJ": "Coordinating conjunction", "DET": "Determiner",
"INTJ": "Interjection", "NOUN": "Noun", "NUM": "Numeral", "PART": "Particle",
"PRON": "Pronoun", "PROPN": "Proper noun", "PUNCT": "Punctuation",
"SCONJ": "Subordinating conjunction", "SYM": "Symbol", "VERB": "Verb", "X": "Other",
}
RELATION_NAMES = {
"nsubj": "Nominal subject", "obj": "Object", "iobj": "Indirect object",
"csubj": "Clausal subject", "ccomp": "Clausal complement",
"xcomp": "Open clausal complement", "obl": "Oblique nominal",
"vocative": "Vocative", "expl": "Expletive", "dislocated": "Dislocated element",
"advcl": "Adverbial clause modifier", "advmod": "Adverbial modifier",
"discourse": "Discourse element", "nmod": "Nominal modifier",
"appos": "Appositional modifier", "nummod": "Numeric modifier",
"acl": "Clausal modifier of a noun", "amod": "Adjectival modifier",
"det": "Determiner", "clf": "Classifier", "case": "Case marking",
"conj": "Conjunct", "cc": "Coordinating conjunction",
"fixed": "Fixed multiword expression", "flat": "Flat multiword expression",
"compound": "Compound", "list": "List", "parataxis": "Parataxis",
"orphan": "Orphan", "goeswith": "Goes with", "reparandum": "Overridden disfluency",
"punct": "Punctuation", "root": "Root", "dep": "Unspecified dependency",
"aux": "Auxiliary", "cop": "Copula", "mark": "Marker",
}
# Verified universal subtype pages. Other subtypes link to their base relation
# rather than assuming that a universal page exists for a language-specific label.
SUBTYPE_NAMES = {
"acl:relcl": "Relative clause modifier", "aux:pass": "Passive auxiliary",
"csubj:pass": "Clausal passive subject", "nsubj:pass": "Passive nominal subject",
"nmod:poss": "Possessive nominal modifier",
}
# Curated Universal Dependencies morphological features for English and German.
FEATURE_NAMES = {
# 1. Core morphological features
"Case": "Case (e.g. nominative, accusative, dative, genitive)",
"Gender": "Gender (masculine, feminine, neuter)",
"Number": "Number (singular, plural)",
"Person": "Person (first, second, third)",
"Tense": "Tense (past, present, future)",
"Mood": "Mood (indicative, imperative, subjunctive)",
"VerbForm": "Verb form (finite, infinitive, participle, gerund)",
"Degree": "Degree of comparison (positive, comparative, superlative)",
# 2. Additional relevant features
"PronType": "Pronominal type (personal, relative, demonstrative, etc.)",
"Definite": "Definiteness (definite, indefinite)",
"Poss": "Possessive marker/determiner",
"Reflex": "Reflexive pronoun/marker",
"NumType": "Numeral type (cardinal, ordinal, etc.)",
"Voice": "Voice (active, passive)",
"Aspect": "Aspect (aspectual distinctions)",
"Polarity": "Polarity (positive, negative)",
# 3. Other features worth knowing
"Abbr": "Abbreviation",
"Foreign": "Foreign word",
"Typo": "Typographical error / unnormalized spelling",
"ExtPos": "External part of speech",
}
def render_ud_help(conllu_text, word_links_html=""):
"""Render expanded Help with deduplicated labels used by visible word rows."""
pos, relations, features = set(), set(), set()
for line in conllu_text.splitlines():
fields = line.split("\t")
# The renderer excludes multiword headers and empty nodes by default.
if len(fields) != 10 or not fields[0].isdigit():
continue
if fields[3] in POS_NAMES:
pos.add(fields[3])
if fields[7].split(":")[0] in RELATION_NAMES:
relations.add(fields[7])
# FEATS is column 5 (0-indexed)
feats_val = fields[5]
if feats_val and feats_val != "_":
for feat_pair in feats_val.split("|"):
if "=" in feat_pair:
feat_name = feat_pair.split("=")[0].strip()
if feat_name in FEATURE_NAMES:
features.add(feat_name)
def link(label, name, path):
return (
f'<li><a href="https://universaldependencies.org/u/{path}.html" '
f'target="_blank" rel="noopener noreferrer">{html.escape(label)}</a>'
f' — {html.escape(name)}</li>'
)
tags = "".join(link(tag, POS_NAMES[tag], "pos/" + tag) for tag in sorted(pos))
dependencies = []
for relation in sorted(relations):
base = relation.split(":")[0]
if relation in SUBTYPE_NAMES:
name, page = SUBTYPE_NAMES[relation], relation.replace(":", "-")
else:
name, page = RELATION_NAMES[base], base
if ":" in relation:
name += " (subtype; link explains the base relation)"
dependencies.append(link(relation, name, "dep/" + page))
feat_links = "".join(
link(feat, FEATURE_NAMES[feat], "feat/" + feat) for feat in sorted(features)
)
if not tags and not dependencies and not feat_links:
return ""
columns_html = ""
if tags:
columns_html += '<div><strong>POS tags in this sentence</strong><ul>' + tags + '</ul></div>'
if dependencies:
columns_html += '<div><strong>Relations in this sentence</strong><ul>' + "".join(dependencies) + '</ul></div>'
if feat_links:
columns_html += '<div><strong>Morphological features in this sentence</strong><ul>' + feat_links + '</ul></div>'
if word_links_html:
columns_html += word_links_html
return (
'<details class="ud-help" open><summary>Help</summary>'
f'<div class="ud-help-columns">{columns_html}</div></details>'
)