File size: 5,792 Bytes
6938a44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae56e3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6938a44
 
 
ae56e3f
6938a44
 
 
 
 
 
 
 
 
ae56e3f
 
 
 
 
 
 
 
6938a44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae56e3f
 
 
 
 
 
6938a44
ae56e3f
 
 
 
 
 
 
 
 
 
 
6938a44
1c44eff
ae56e3f
6938a44
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""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"><summary>Help</summary>'
        f'<div class="ud-help-columns">{columns_html}</div></details>'
    )