Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -47,7 +47,7 @@ The demo loops infinitely when more than 1 sentence is included on the line.
|
|
| 47 |
|
| 48 |
John, who lives in Wolverhampton, which is a town in the West Midlands, went home.
|
| 49 |
'''
|
| 50 |
-
|
| 51 |
import re
|
| 52 |
import sys
|
| 53 |
import torch
|
|
@@ -58,7 +58,6 @@ import spacy_alignments as tokenizations
|
|
| 58 |
import xml.dom.minidom
|
| 59 |
import pandas as pd
|
| 60 |
import gradio as gr
|
| 61 |
-
import os
|
| 62 |
|
| 63 |
from collections import deque # add this at the top of app.py if not already there
|
| 64 |
|
|
@@ -66,11 +65,6 @@ seen = set()
|
|
| 66 |
|
| 67 |
import io
|
| 68 |
|
| 69 |
-
hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
|
| 70 |
-
|
| 71 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 72 |
-
print("Device:", device)
|
| 73 |
-
|
| 74 |
# Fix for Windows Unicode printing issues
|
| 75 |
if sys.platform == "win32":
|
| 76 |
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
@@ -85,39 +79,46 @@ nlp = spacy.load("en_core_web_sm") # Assuming this is used; load once
|
|
| 85 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 86 |
|
| 87 |
sign_tagging_model_name = "RJ3vans/SignTagger"
|
| 88 |
-
SignTaggingTokenizer = AutoTokenizer.from_pretrained(sign_tagging_model_name
|
| 89 |
SignTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 90 |
-
sign_tagging_model_name
|
| 91 |
).to(device)
|
| 92 |
-
SignTaggingModel.eval()
|
| 93 |
|
| 94 |
ssccv_span_tagging_model_name = "RJ3vans/SSCCVspanTagger"
|
| 95 |
-
SSCCVspanTaggingTokenizer = AutoTokenizer.from_pretrained(ssccv_span_tagging_model_name
|
| 96 |
SSCCVspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 97 |
-
ssccv_span_tagging_model_name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
).to(device)
|
| 99 |
-
SSCCVspanTaggingModel.eval()
|
| 100 |
|
| 101 |
ccv_span_tagging_model_name = "RJ3vans/CCVspanTagger"
|
| 102 |
-
CCVspanTaggingTokenizer = AutoTokenizer.from_pretrained(ccv_span_tagging_model_name
|
| 103 |
CCVspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 104 |
-
ccv_span_tagging_model_name
|
| 105 |
).to(device)
|
| 106 |
-
CCVspanTaggingModel.eval()
|
| 107 |
|
| 108 |
cmv1_span_tagging_model_name = "RJ3vans/CMV1spanTagger"
|
| 109 |
-
CMV1spanTaggingTokenizer = AutoTokenizer.from_pretrained(cmv1_span_tagging_model_name
|
| 110 |
CMV1spanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 111 |
-
cmv1_span_tagging_model_name
|
| 112 |
).to(device)
|
| 113 |
-
CMV1spanTaggingModel.eval()
|
| 114 |
|
| 115 |
cmn1_span_tagging_model_name = "RJ3vans/CMN1spanTagger"
|
| 116 |
-
CMN1spanTaggingTokenizer = AutoTokenizer.from_pretrained(cmn1_span_tagging_model_name
|
| 117 |
CMN1spanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 118 |
-
cmn1_span_tagging_model_name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
).to(device)
|
| 120 |
-
CMN1spanTaggingModel.eval()
|
| 121 |
|
| 122 |
# Define label lists (extracted from notebook summary; expand with full 70+ if needed)
|
| 123 |
sign_label_list = [
|
|
@@ -265,6 +266,17 @@ SSCCVspan_label_list = [
|
|
| 265 |
"UNKNOWN",
|
| 266 |
]
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
CCVspan_label_list = [
|
| 269 |
"AFTER_COMPOUND",
|
| 270 |
"BEFORE_COMPOUND",
|
|
@@ -295,8 +307,19 @@ CMN1span_label_list = [
|
|
| 295 |
"UNKNOWN",
|
| 296 |
]
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
required_sign_tags = [
|
| 299 |
"M:Y_SSCCV",
|
|
|
|
| 300 |
"M:Y_CCV",
|
| 301 |
"M:Y_CMV1",
|
| 302 |
"M:Y_CMP",
|
|
@@ -592,6 +615,41 @@ def improve_predicted_tag_sequence(predictions):
|
|
| 592 |
|
| 593 |
###############################################################################
|
| 594 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
|
| 596 |
###############################################################################
|
| 597 |
def tag_SSCCV_span(sentence):
|
|
@@ -601,9 +659,9 @@ def tag_SSCCV_span(sentence):
|
|
| 601 |
tokens = SSCCVspanTaggingTokenizer.tokenize(
|
| 602 |
SSCCVspanTaggingTokenizer.decode(SSCCVspanTaggingTokenizer.encode(sentence))
|
| 603 |
)
|
| 604 |
-
inputs = SSCCVspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 605 |
-
|
| 606 |
-
|
| 607 |
predictions = torch.argmax(outputs, dim=2)
|
| 608 |
|
| 609 |
SSCCV_span_tagged_tokens = [
|
|
@@ -660,6 +718,58 @@ def tag_SSCCV_span(sentence):
|
|
| 660 |
|
| 661 |
###############################################################################
|
| 662 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
|
| 664 |
###############################################################################
|
| 665 |
def tag_CCV_span(sentence):
|
|
@@ -669,9 +779,9 @@ def tag_CCV_span(sentence):
|
|
| 669 |
tokens = CCVspanTaggingTokenizer.tokenize(
|
| 670 |
CCVspanTaggingTokenizer.decode(CCVspanTaggingTokenizer.encode(sentence))
|
| 671 |
)
|
| 672 |
-
inputs = CCVspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 673 |
-
|
| 674 |
-
|
| 675 |
predictions = torch.argmax(outputs, dim=2)
|
| 676 |
|
| 677 |
CCV_span_tagged_tokens = [
|
|
@@ -731,9 +841,9 @@ def tag_CMV1_span(sentence):
|
|
| 731 |
tokens = CMV1spanTaggingTokenizer.tokenize(
|
| 732 |
CMV1spanTaggingTokenizer.decode(CMV1spanTaggingTokenizer.encode(sentence))
|
| 733 |
)
|
| 734 |
-
inputs = CMV1spanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 735 |
-
|
| 736 |
-
|
| 737 |
predictions = torch.argmax(outputs, dim=2)
|
| 738 |
|
| 739 |
CMV1_span_tagged_tokens = [
|
|
@@ -793,9 +903,9 @@ def tag_CMN1_span(sentence):
|
|
| 793 |
tokens = CMN1spanTaggingTokenizer.tokenize(
|
| 794 |
CMN1spanTaggingTokenizer.decode(CMN1spanTaggingTokenizer.encode(sentence))
|
| 795 |
)
|
| 796 |
-
inputs = CMN1spanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 797 |
-
|
| 798 |
-
|
| 799 |
predictions = torch.argmax(outputs, dim=2)
|
| 800 |
|
| 801 |
CMN1_span_tagged_tokens = [
|
|
@@ -845,6 +955,68 @@ def tag_CMN1_span(sentence):
|
|
| 845 |
) # added return
|
| 846 |
|
| 847 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 848 |
###############################################################################
|
| 849 |
|
| 850 |
###############################################################################
|
|
@@ -884,9 +1056,9 @@ def tag_all_signs_in_sent(sent):
|
|
| 884 |
tokens = SignTaggingTokenizer.tokenize(
|
| 885 |
SignTaggingTokenizer.decode(SignTaggingTokenizer.encode(sent))
|
| 886 |
)
|
| 887 |
-
inputs = SignTaggingTokenizer.encode(sent, return_tensors="pt")
|
| 888 |
-
|
| 889 |
-
|
| 890 |
predictions = torch.argmax(outputs, dim=2)
|
| 891 |
|
| 892 |
sign_tagged_tokens = [
|
|
@@ -1205,7 +1377,96 @@ def SSCCVsimplify(xml_sent):
|
|
| 1205 |
|
| 1206 |
return simplification
|
| 1207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1209 |
###############################################################################
|
| 1210 |
|
| 1211 |
###############################################################################
|
|
@@ -1257,6 +1518,111 @@ def rewrite_residual_list_comma(sent, coordinator="and"):
|
|
| 1257 |
return out
|
| 1258 |
###############################################################################
|
| 1259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
###############################################################################
|
| 1261 |
def CompoundSimplify(xml_sent):
|
| 1262 |
"""
|
|
@@ -1322,25 +1688,27 @@ def CompoundSimplify(xml_sent):
|
|
| 1322 |
x for x in [before_text, aftersign_text, after_text] if x
|
| 1323 |
).strip()
|
| 1324 |
|
|
|
|
| 1325 |
def tidy(s):
|
|
|
|
|
|
|
|
|
|
| 1326 |
s = re.sub(r"\s+", " ", s).strip()
|
| 1327 |
s = re.sub(r"\s+([.,!?;:])", r"\1", s)
|
| 1328 |
s = re.sub(r"([.,!?;:])\s+", r"\1 ", s)
|
| 1329 |
-
|
| 1330 |
# decimals: 8 . 2 → 8.2
|
| 1331 |
s = re.sub(r"(\d)\s*\.\s*(\d)", r"\1.\2", s)
|
| 1332 |
-
|
| 1333 |
-
# list-coordinator debris: "43;." / "43,." / "43;;." → "43."
|
| 1334 |
s = re.sub(r"[,;:]+\s*\.", ".", s)
|
| 1335 |
-
# dangling separators at end before a period is added
|
| 1336 |
s = re.sub(r"[,;:]+\s*$", "", s)
|
| 1337 |
-
|
| 1338 |
if s and not s.endswith((".", "!", "?")):
|
| 1339 |
s += "."
|
| 1340 |
if s:
|
| 1341 |
s = s[0].upper() + s[1:]
|
| 1342 |
return s
|
| 1343 |
-
|
|
|
|
| 1344 |
|
| 1345 |
sent1 = tidy(sent1)
|
| 1346 |
sent2 = tidy(sent2)
|
|
@@ -1397,7 +1765,26 @@ def create_xml_sent(tokens, pos_tags, lemmas, predictions, sign_tag):
|
|
| 1397 |
)
|
| 1398 |
after_complex_constituent = ET.SubElement(xml_sent, "AFTER_COMPLEX_CONSTITUENT")
|
| 1399 |
|
| 1400 |
-
elif sign_tag in ["M:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1401 |
before_complex_constituent = ET.SubElement(
|
| 1402 |
xml_sent, "BEFORE_COMPOUND_CONSTITUENT"
|
| 1403 |
)
|
|
@@ -1452,6 +1839,17 @@ def create_xml_sent(tokens, pos_tags, lemmas, predictions, sign_tag):
|
|
| 1452 |
word.attrib["POS"] = pos_tags[x]
|
| 1453 |
word.attrib["LEMMA"] = lemmas[x]
|
| 1454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1455 |
elif "IN_" in predictions[x]:
|
| 1456 |
word = ET.SubElement(in_complex_constituent, "W")
|
| 1457 |
word.text = tokens[x]
|
|
@@ -1470,6 +1868,8 @@ def create_xml_sent(tokens, pos_tags, lemmas, predictions, sign_tag):
|
|
| 1470 |
word.attrib["POS"] = pos_tags[x]
|
| 1471 |
word.attrib["LEMMA"] = lemmas[x]
|
| 1472 |
|
|
|
|
|
|
|
| 1473 |
x += 1
|
| 1474 |
|
| 1475 |
# Print the XML (as you requested)
|
|
@@ -1489,6 +1889,8 @@ def get_COMPLEX_NP_type(xml_sent):
|
|
| 1489 |
final_iccb_pos_tag = "UNKNOWN"
|
| 1490 |
|
| 1491 |
punct = re.compile(r"^[\,\:\;]$")
|
|
|
|
|
|
|
| 1492 |
|
| 1493 |
for el in xml_sent:
|
| 1494 |
if el.tag == "IN_COMPLEX_CONSTITUENT_AFTERSIGN":
|
|
@@ -1498,7 +1900,11 @@ def get_COMPLEX_NP_type(xml_sent):
|
|
| 1498 |
if w.text and not punct.match(w.text.strip()):
|
| 1499 |
first_icca_pos_tag = w.attrib.get("POS", "UNKNOWN")
|
| 1500 |
break
|
| 1501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1502 |
except Exception:
|
| 1503 |
pass
|
| 1504 |
|
|
@@ -1514,15 +1920,9 @@ def get_COMPLEX_NP_type(xml_sent):
|
|
| 1514 |
|
| 1515 |
if final_iccb_pos_tag in ["IN"]:
|
| 1516 |
complex_NP_type = "PREPOSITION_OBJECT_RELATIVISED"
|
| 1517 |
-
elif first_icca_pos_tag in
|
| 1518 |
-
"NNP",
|
| 1519 |
-
"NNPS",
|
| 1520 |
-
"NN",
|
| 1521 |
-
"DT",
|
| 1522 |
-
"CD",
|
| 1523 |
-
] and final_icca_pos_tag in ["VB", "VBD", "VBG", "VBN", "VBZ"]:
|
| 1524 |
complex_NP_type = "VERB_OBJECT_RELATIVISED"
|
| 1525 |
-
elif first_icca_pos_tag in
|
| 1526 |
complex_NP_type = "SUBJECT_RELATIVISED"
|
| 1527 |
|
| 1528 |
return complex_NP_type
|
|
@@ -1633,12 +2033,14 @@ def build_single_sign_bracketed(tagged_list, mode="ccv"):
|
|
| 1633 |
mode = "ccv" → bracket only the rightmost CCV sign
|
| 1634 |
mode = "cmv1" → bracket only the rightmost CMV1 sign
|
| 1635 |
mode = "cmn1" → bracket only the rightmost CMN1 sign
|
|
|
|
| 1636 |
mode = "ssccv" → bracket one relative SSCCV trigger:
|
| 1637 |
- nested relatives (2+ opens before next ESCCV): innermost
|
| 1638 |
(rightmost preferred trigger before that ESCCV)
|
| 1639 |
- coordinated relatives (CCV between opens): leftmost
|
| 1640 |
- otherwise: leftmost relative trigger
|
| 1641 |
- fallback: leftmost SSCCV/ESCCV (e.g. bare "that")
|
|
|
|
| 1642 |
"""
|
| 1643 |
|
| 1644 |
def surface_of(item):
|
|
@@ -1652,8 +2054,10 @@ def build_single_sign_bracketed(tagged_list, mode="ccv"):
|
|
| 1652 |
ccv_indices = []
|
| 1653 |
cmv1_indices = []
|
| 1654 |
cmn1_indices = []
|
|
|
|
| 1655 |
ssccv_indices = []
|
| 1656 |
esccv_indices = []
|
|
|
|
| 1657 |
|
| 1658 |
for i, item in enumerate(tagged_list):
|
| 1659 |
s = str(item)
|
|
@@ -1663,13 +2067,17 @@ def build_single_sign_bracketed(tagged_list, mode="ccv"):
|
|
| 1663 |
cmv1_indices.append(i)
|
| 1664 |
if "<M:Y_CMN1>" in s or "M:Y_CMN1" in s:
|
| 1665 |
cmn1_indices.append(i)
|
|
|
|
|
|
|
| 1666 |
if "M:Y_SSCCV" in s:
|
| 1667 |
ssccv_indices.append(i)
|
| 1668 |
if "M:Y_ESCCV" in s:
|
| 1669 |
esccv_indices.append(i)
|
| 1670 |
if i not in ssccv_indices:
|
| 1671 |
ssccv_indices.append(i)
|
| 1672 |
-
|
|
|
|
|
|
|
| 1673 |
target = set()
|
| 1674 |
|
| 1675 |
if mode == "ccv" and ccv_indices:
|
|
@@ -1681,6 +2089,12 @@ def build_single_sign_bracketed(tagged_list, mode="ccv"):
|
|
| 1681 |
elif mode == "cmn1" and cmn1_indices:
|
| 1682 |
target.add(cmn1_indices[-1])
|
| 1683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1684 |
elif mode == "ssccv" and ssccv_indices:
|
| 1685 |
relative_pronouns = {"who", "which", "whom", "whose"}
|
| 1686 |
preferred = []
|
|
@@ -1767,7 +2181,6 @@ def build_single_sign_bracketed(tagged_list, mode="ccv"):
|
|
| 1767 |
|
| 1768 |
|
| 1769 |
###############################################################################
|
| 1770 |
-
@spaces.GPU(duration=120)
|
| 1771 |
def process_text(input_text):
|
| 1772 |
print("=== ITERATIVE SIMPLIFICATION (priority + clean tokens) ===")
|
| 1773 |
try:
|
|
@@ -1858,6 +2271,10 @@ def process_text(input_text):
|
|
| 1858 |
any(x in t for x in ["M:Y_SSCCV", "M:Y_ESCCV"])
|
| 1859 |
for t in tagged_list
|
| 1860 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1861 |
has_ccv = any(
|
| 1862 |
"<M:Y_CCV>" in t or "M:Y_CCV" in t for t in tagged_list
|
| 1863 |
)
|
|
@@ -1867,12 +2284,20 @@ def process_text(input_text):
|
|
| 1867 |
has_cmn1 = any(
|
| 1868 |
"<M:Y_CMN1>" in t or "M:Y_CMN1" in t for t in tagged_list
|
| 1869 |
)
|
|
|
|
|
|
|
|
|
|
| 1870 |
|
| 1871 |
if has_ssccv:
|
| 1872 |
mode = "ssccv"
|
| 1873 |
xml_tag = "M:Y_SSCCV"
|
| 1874 |
simplifier = SSCCVsimplify
|
| 1875 |
span_fn = tag_SSCCV_span
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1876 |
elif has_ccv:
|
| 1877 |
mode = "ccv"
|
| 1878 |
xml_tag = "M:Y_CCV"
|
|
@@ -1888,6 +2313,11 @@ def process_text(input_text):
|
|
| 1888 |
xml_tag = "M:Y_CMN1"
|
| 1889 |
simplifier = CompoundSimplify
|
| 1890 |
span_fn = tag_CMN1_span
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1891 |
else:
|
| 1892 |
sentence_final.append(current)
|
| 1893 |
continue
|
|
@@ -1922,11 +2352,12 @@ def process_text(input_text):
|
|
| 1922 |
)
|
| 1923 |
simps = simplifier(xml_sent)
|
| 1924 |
|
| 1925 |
-
# Residual comma → same conjunction (CCV / CMV1 / CMN1 only)
|
| 1926 |
if (
|
| 1927 |
simps
|
| 1928 |
and simplifier is CompoundSimplify
|
| 1929 |
-
and xml_tag
|
|
|
|
| 1930 |
):
|
| 1931 |
coord = extract_coordinator_from_xml(xml_sent)
|
| 1932 |
if coord:
|
|
@@ -1940,7 +2371,17 @@ def process_text(input_text):
|
|
| 1940 |
)
|
| 1941 |
rewritten.append(s2)
|
| 1942 |
simps = rewritten
|
| 1943 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1944 |
if not simps:
|
| 1945 |
print(
|
| 1946 |
"Simplifier returned []; forcing final (no re-queue)"
|
|
@@ -1975,14 +2416,12 @@ def process_text(input_text):
|
|
| 1975 |
if not queued_any:
|
| 1976 |
sentence_final.append(current)
|
| 1977 |
|
| 1978 |
-
# ★ CHANGE 1: order this original sentence's finals
|
| 1979 |
for s in order_sentences_for_discourse(sentence_final):
|
| 1980 |
s = clean_output(s)
|
| 1981 |
if s:
|
| 1982 |
para_simplified.append(s)
|
| 1983 |
|
| 1984 |
if para_simplified:
|
| 1985 |
-
# ★ CHANGE 2 (optional): order whole paragraph
|
| 1986 |
para_simplified = order_sentences_for_discourse(para_simplified)
|
| 1987 |
output_paragraphs.append(" ".join(para_simplified))
|
| 1988 |
|
|
@@ -2043,107 +2482,52 @@ custom_css = """
|
|
| 2043 |
}
|
| 2044 |
"""
|
| 2045 |
|
| 2046 |
-
with gr.Blocks(
|
|
|
|
|
|
|
|
|
|
| 2047 |
gr.Markdown("# Syntactic Sentence Simplifier")
|
| 2048 |
gr.Markdown(
|
| 2049 |
-
""
|
| 2050 |
-
This demo simplifies sentences containing compound constituents:
|
| 2051 |
-
|
| 2052 |
-
* *clauses*
|
| 2053 |
-
* *verb phrases*
|
| 2054 |
-
* *noun phrases*
|
| 2055 |
-
|
| 2056 |
-
and complex noun phrases modified by:
|
| 2057 |
-
|
| 2058 |
-
* *nominally bound non-restrictive finite relative clauses (subject relativised and verb/preposition object relativised)*
|
| 2059 |
-
|
| 2060 |
-
Paste text, or click examples to **add** them into the box below
|
| 2061 |
-
(you can edit the text afterwards).
|
| 2062 |
-
"""
|
| 2063 |
)
|
| 2064 |
|
| 2065 |
-
|
| 2066 |
-
# 1) Complex input
|
| 2067 |
input_text = gr.Textbox(
|
| 2068 |
-
lines=
|
| 2069 |
label="Original text",
|
| 2070 |
-
placeholder="Paste your text here
|
| 2071 |
elem_classes=["input-text"],
|
| 2072 |
)
|
| 2073 |
|
| 2074 |
-
# 2) Bridge button (directly under input)
|
| 2075 |
-
btn = gr.Button("Process & Simplify", variant="primary")
|
| 2076 |
-
|
| 2077 |
-
# 3) Simple output
|
| 2078 |
output_text = gr.Textbox(
|
| 2079 |
-
lines=
|
| 2080 |
label="Simplified text",
|
| 2081 |
elem_classes=["output-text"],
|
| 2082 |
)
|
| 2083 |
|
| 2084 |
-
btn.
|
| 2085 |
-
|
| 2086 |
-
# Examples / clear (under the whole stack, or under input if you prefer)
|
| 2087 |
-
EXAMPLE_SENTENCES = [
|
| 2088 |
-
"John kicked the ball and walked to Leeds.",
|
| 2089 |
-
"John bought two apples and three oranges.",
|
| 2090 |
-
"John left early and Mary stayed late.",
|
| 2091 |
-
"John, who lives in London, went home.",
|
| 2092 |
-
"Peter, who Sam likes, went home.",
|
| 2093 |
-
"Fred bought a computer and a printer and switched them on.",
|
| 2094 |
-
"Fred bought a computer and a printer, and Mary bought a PS5.",
|
| 2095 |
-
"Mary, who lives in London, went home, and Peter stayed.",
|
| 2096 |
-
"John, who kicked the ball and walked away, went home.",
|
| 2097 |
-
"John, who bought a computer and a printer, went home.",
|
| 2098 |
-
"John, who Steven tolerated and who mary despised, went home.",
|
| 2099 |
-
"John, who lives in Shifnal, which is a town in the Black Country, went home.",
|
| 2100 |
-
"Mary, who lives in London, kicked the ball and walked to Leeds, and John stayed home.",
|
| 2101 |
-
"Jackie, who lives in Manchester, bought a computer and a monitor, and Fred bought bread.",
|
| 2102 |
-
"Count Binface bought two apples and three pears and ate them, and Nigel left.",
|
| 2103 |
-
"Count Binface, who bought two apples and three pears and ate them, went home.",
|
| 2104 |
-
"Hadric Wode, who bought a mace and a shield and used them, left early, and Fenric, who was tired, stayed home.",
|
| 2105 |
-
"John is one candidate, Mary ignores signals, and Bill eats grass.",
|
| 2106 |
-
"John went to Essex, Bill set out for Sussex, and Mary returned from Halifax.",
|
| 2107 |
-
"Joshua told me that John, who lives in New York, is going to London, Jack loves Norwich, and Mary, who is extremely confident, adores Milton Keynes, just as Fred predicted.",
|
| 2108 |
-
"John kicked the ball and walked to Leeds, and Peter stayed home.",
|
| 2109 |
-
"John is one test, Mary, who lives in London, loves exams, and Bill eats bread.",
|
| 2110 |
-
"John went to Wolverhampton, Bill set out for Coventry, and Mary returned from Birmingham.",
|
| 2111 |
-
"John, who lives in London, is going to Wolverhampton, and Mary, who is a bit nervous, adores Birmingham.",
|
| 2112 |
-
"John kicked the ball and walked to Leeds. Mary bought a laptop and a mousemat.",
|
| 2113 |
-
"I love France.",
|
| 2114 |
-
"John met the dog, the cat, and the tortoise.",
|
| 2115 |
-
"The Environment Agency said record winter rainfall had helped to replenish reservoirs and groundwater, but reservoir storage has now fallen to 7.4% below the average for this time of year.",
|
| 2116 |
-
]
|
| 2117 |
|
| 2118 |
-
|
| 2119 |
-
|
| 2120 |
-
|
| 2121 |
-
|
| 2122 |
-
|
| 2123 |
-
if not current:
|
| 2124 |
-
return example
|
| 2125 |
-
return current + "\n\n" + example
|
| 2126 |
-
|
| 2127 |
-
gr.Markdown("### Add an example to the box above (click to append; then edit freely)")
|
| 2128 |
-
|
| 2129 |
-
clear_btn = gr.Button("Clear input", size="sm")
|
| 2130 |
-
clear_btn.click(fn=lambda: "", outputs=input_text)
|
| 2131 |
-
|
| 2132 |
-
for ex in EXAMPLE_SENTENCES:
|
| 2133 |
-
label = ex if len(ex) <= 100 else ex[:97] + "..."
|
| 2134 |
-
ex_btn = gr.Button(label, size="sm")
|
| 2135 |
-
ex_btn.click(
|
| 2136 |
-
fn=lambda current, s=ex: append_example(current, s),
|
| 2137 |
-
inputs=input_text,
|
| 2138 |
-
outputs=input_text,
|
| 2139 |
-
)
|
| 2140 |
|
| 2141 |
-
print("Gradio interface ready. Starting web server...")
|
| 2142 |
|
| 2143 |
if __name__ == "__main__":
|
| 2144 |
demo.launch(
|
| 2145 |
server_name="0.0.0.0",
|
| 2146 |
server_port=7860,
|
| 2147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2148 |
)
|
|
|
|
|
|
|
| 2149 |
|
|
|
|
| 47 |
|
| 48 |
John, who lives in Wolverhampton, which is a town in the West Midlands, went home.
|
| 49 |
'''
|
| 50 |
+
|
| 51 |
import re
|
| 52 |
import sys
|
| 53 |
import torch
|
|
|
|
| 58 |
import xml.dom.minidom
|
| 59 |
import pandas as pd
|
| 60 |
import gradio as gr
|
|
|
|
| 61 |
|
| 62 |
from collections import deque # add this at the top of app.py if not already there
|
| 63 |
|
|
|
|
| 65 |
|
| 66 |
import io
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
# Fix for Windows Unicode printing issues
|
| 69 |
if sys.platform == "win32":
|
| 70 |
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
|
|
| 79 |
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 80 |
|
| 81 |
sign_tagging_model_name = "RJ3vans/SignTagger"
|
| 82 |
+
SignTaggingTokenizer = AutoTokenizer.from_pretrained(sign_tagging_model_name)
|
| 83 |
SignTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 84 |
+
sign_tagging_model_name
|
| 85 |
).to(device)
|
|
|
|
| 86 |
|
| 87 |
ssccv_span_tagging_model_name = "RJ3vans/SSCCVspanTagger"
|
| 88 |
+
SSCCVspanTaggingTokenizer = AutoTokenizer.from_pretrained(ssccv_span_tagging_model_name)
|
| 89 |
SSCCVspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 90 |
+
ssccv_span_tagging_model_name
|
| 91 |
+
).to(device)
|
| 92 |
+
|
| 93 |
+
ssmn_span_tagging_model_name = "RJ3vans/SSMNspanTagger"
|
| 94 |
+
SSMNspanTaggingTokenizer = AutoTokenizer.from_pretrained(ssmn_span_tagging_model_name)
|
| 95 |
+
SSMNspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 96 |
+
ssmn_span_tagging_model_name
|
| 97 |
).to(device)
|
|
|
|
| 98 |
|
| 99 |
ccv_span_tagging_model_name = "RJ3vans/CCVspanTagger"
|
| 100 |
+
CCVspanTaggingTokenizer = AutoTokenizer.from_pretrained(ccv_span_tagging_model_name)
|
| 101 |
CCVspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 102 |
+
ccv_span_tagging_model_name
|
| 103 |
).to(device)
|
|
|
|
| 104 |
|
| 105 |
cmv1_span_tagging_model_name = "RJ3vans/CMV1spanTagger"
|
| 106 |
+
CMV1spanTaggingTokenizer = AutoTokenizer.from_pretrained(cmv1_span_tagging_model_name)
|
| 107 |
CMV1spanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 108 |
+
cmv1_span_tagging_model_name
|
| 109 |
).to(device)
|
|
|
|
| 110 |
|
| 111 |
cmn1_span_tagging_model_name = "RJ3vans/CMN1spanTagger"
|
| 112 |
+
CMN1spanTaggingTokenizer = AutoTokenizer.from_pretrained(cmn1_span_tagging_model_name)
|
| 113 |
CMN1spanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 114 |
+
cmn1_span_tagging_model_name
|
| 115 |
+
).to(device)
|
| 116 |
+
|
| 117 |
+
cln_span_tagging_model_name = "RJ3vans/CLNspanTagger"
|
| 118 |
+
CLNspanTaggingTokenizer = AutoTokenizer.from_pretrained(cmn1_span_tagging_model_name)
|
| 119 |
+
CLNspanTaggingModel = AutoModelForTokenClassification.from_pretrained(
|
| 120 |
+
cln_span_tagging_model_name
|
| 121 |
).to(device)
|
|
|
|
| 122 |
|
| 123 |
# Define label lists (extracted from notebook summary; expand with full 70+ if needed)
|
| 124 |
sign_label_list = [
|
|
|
|
| 266 |
"UNKNOWN",
|
| 267 |
]
|
| 268 |
|
| 269 |
+
SSMNspan_label_list = [
|
| 270 |
+
"AFTER_COMPLEX_NP",
|
| 271 |
+
"BEFORE_COMPLEX_NP",
|
| 272 |
+
"ERROR",
|
| 273 |
+
"IN_COMPLEX_NP",
|
| 274 |
+
"IN_COMPLEX_NP_AFTERSIGN",
|
| 275 |
+
"IN_COMPLEX_NP_BEFORESIGN",
|
| 276 |
+
"UNKNOWN",
|
| 277 |
+
]
|
| 278 |
+
|
| 279 |
+
|
| 280 |
CCVspan_label_list = [
|
| 281 |
"AFTER_COMPOUND",
|
| 282 |
"BEFORE_COMPOUND",
|
|
|
|
| 307 |
"UNKNOWN",
|
| 308 |
]
|
| 309 |
|
| 310 |
+
CLNspan_label_list = [
|
| 311 |
+
"AFTER_COMPOUND",
|
| 312 |
+
"BEFORE_COMPOUND",
|
| 313 |
+
"IN_COMPOUND",
|
| 314 |
+
"IN_COMPOUND_AFTERSIGN",
|
| 315 |
+
"IN_COMPOUND_BEFORESIGN",
|
| 316 |
+
"NOT_NOUN_COORDINATOR",
|
| 317 |
+
"UNKNOWN",
|
| 318 |
+
]
|
| 319 |
+
|
| 320 |
required_sign_tags = [
|
| 321 |
"M:Y_SSCCV",
|
| 322 |
+
"M:Y_SSMN",
|
| 323 |
"M:Y_CCV",
|
| 324 |
"M:Y_CMV1",
|
| 325 |
"M:Y_CMP",
|
|
|
|
| 615 |
|
| 616 |
###############################################################################
|
| 617 |
|
| 618 |
+
###############################################################################
|
| 619 |
+
def indefinite_article_for(word: str) -> str:
|
| 620 |
+
"""Choose a/an from the following orthographic word."""
|
| 621 |
+
if not word:
|
| 622 |
+
return "a"
|
| 623 |
+
w = re.sub(r"[^A-Za-z]", "", word).lower()
|
| 624 |
+
if not w:
|
| 625 |
+
return "a"
|
| 626 |
+
# simple orthographic rule (good enough for lecturer, hour, university, …)
|
| 627 |
+
if w[0] in "aeiou":
|
| 628 |
+
return "an"
|
| 629 |
+
return "a"
|
| 630 |
+
###############################################################################
|
| 631 |
+
|
| 632 |
+
###############################################################################
|
| 633 |
+
def make_indefinite_np(np_text: str) -> str:
|
| 634 |
+
"""
|
| 635 |
+
the famous lecturer → a famous lecturer
|
| 636 |
+
an old friend → an old friend (unchanged)
|
| 637 |
+
famous lecturer → a famous lecturer (if bare, add article)
|
| 638 |
+
"""
|
| 639 |
+
np_text = re.sub(r"\s+", " ", (np_text or "")).strip()
|
| 640 |
+
if not np_text:
|
| 641 |
+
return np_text
|
| 642 |
+
|
| 643 |
+
m = re.match(r"^(the|a|an)\s+(.*)$", np_text, flags=re.I)
|
| 644 |
+
if m:
|
| 645 |
+
rest = m.group(2).strip()
|
| 646 |
+
first = rest.split()[0] if rest else ""
|
| 647 |
+
return f"{indefinite_article_for(first)} {rest}".strip()
|
| 648 |
+
|
| 649 |
+
first = np_text.split()[0]
|
| 650 |
+
return f"{indefinite_article_for(first)} {np_text}".strip()
|
| 651 |
+
###############################################################################
|
| 652 |
+
|
| 653 |
|
| 654 |
###############################################################################
|
| 655 |
def tag_SSCCV_span(sentence):
|
|
|
|
| 659 |
tokens = SSCCVspanTaggingTokenizer.tokenize(
|
| 660 |
SSCCVspanTaggingTokenizer.decode(SSCCVspanTaggingTokenizer.encode(sentence))
|
| 661 |
)
|
| 662 |
+
inputs = SSCCVspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 663 |
+
|
| 664 |
+
outputs = SSCCVspanTaggingModel(inputs)[0]
|
| 665 |
predictions = torch.argmax(outputs, dim=2)
|
| 666 |
|
| 667 |
SSCCV_span_tagged_tokens = [
|
|
|
|
| 718 |
|
| 719 |
###############################################################################
|
| 720 |
|
| 721 |
+
###############################################################################
|
| 722 |
+
def tag_SSMN_span(sentence):
|
| 723 |
+
"""
|
| 724 |
+
sentence must contain the left appositive boundary in brackets, e.g.:
|
| 725 |
+
John[,] the famous teacher, went home.
|
| 726 |
+
"""
|
| 727 |
+
if re.search(r"\[\:\]\s*$", sentence):
|
| 728 |
+
sentence = sentence + " blah blah blah."
|
| 729 |
+
|
| 730 |
+
tokens = SSMNspanTaggingTokenizer.tokenize(
|
| 731 |
+
SSMNspanTaggingTokenizer.decode(SSMNspanTaggingTokenizer.encode(sentence))
|
| 732 |
+
)
|
| 733 |
+
inputs = SSMNspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 734 |
+
if device != "cpu":
|
| 735 |
+
inputs = inputs.to(device)
|
| 736 |
+
|
| 737 |
+
with torch.no_grad():
|
| 738 |
+
outputs = SSMNspanTaggingModel(inputs)[0]
|
| 739 |
+
predictions = torch.argmax(outputs, dim=2)
|
| 740 |
+
|
| 741 |
+
ssmn_span_tagged_predictions = [
|
| 742 |
+
SSMNspan_label_list[prediction]
|
| 743 |
+
for prediction in predictions[0].tolist()
|
| 744 |
+
]
|
| 745 |
+
|
| 746 |
+
aligned_tokens, aligned_predictions = [], []
|
| 747 |
+
aligned_pos_tags, aligned_lemmas = [], []
|
| 748 |
+
|
| 749 |
+
sc_sentence = (
|
| 750 |
+
clean_for_spaCy(sentence) if "[" in sentence and "]" in sentence else sentence
|
| 751 |
+
)
|
| 752 |
+
doc = nlp(sc_sentence)
|
| 753 |
+
spacy_pos_tags = [t.tag_ for t in doc]
|
| 754 |
+
spacy_tokens = [t.text for t in doc]
|
| 755 |
+
spacy_lemmas = [t.lemma_ for t in doc]
|
| 756 |
+
|
| 757 |
+
a2b, b2a = tokenizations.get_alignments(spacy_tokens, tokens)
|
| 758 |
+
|
| 759 |
+
for i in range(len(spacy_tokens)):
|
| 760 |
+
for j in a2b[i]:
|
| 761 |
+
if j < len(ssmn_span_tagged_predictions):
|
| 762 |
+
aligned_tokens.append(spacy_tokens[i])
|
| 763 |
+
aligned_pos_tags.append(spacy_pos_tags[i])
|
| 764 |
+
aligned_lemmas.append(spacy_lemmas[i])
|
| 765 |
+
aligned_predictions.append(ssmn_span_tagged_predictions[j])
|
| 766 |
+
break
|
| 767 |
+
|
| 768 |
+
return sign_cleaning(
|
| 769 |
+
aligned_tokens, aligned_pos_tags, aligned_lemmas, aligned_predictions
|
| 770 |
+
)
|
| 771 |
+
###############################################################################
|
| 772 |
+
|
| 773 |
|
| 774 |
###############################################################################
|
| 775 |
def tag_CCV_span(sentence):
|
|
|
|
| 779 |
tokens = CCVspanTaggingTokenizer.tokenize(
|
| 780 |
CCVspanTaggingTokenizer.decode(CCVspanTaggingTokenizer.encode(sentence))
|
| 781 |
)
|
| 782 |
+
inputs = CCVspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 783 |
+
|
| 784 |
+
outputs = CCVspanTaggingModel(inputs)[0]
|
| 785 |
predictions = torch.argmax(outputs, dim=2)
|
| 786 |
|
| 787 |
CCV_span_tagged_tokens = [
|
|
|
|
| 841 |
tokens = CMV1spanTaggingTokenizer.tokenize(
|
| 842 |
CMV1spanTaggingTokenizer.decode(CMV1spanTaggingTokenizer.encode(sentence))
|
| 843 |
)
|
| 844 |
+
inputs = CMV1spanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 845 |
+
|
| 846 |
+
outputs = CMV1spanTaggingModel(inputs)[0]
|
| 847 |
predictions = torch.argmax(outputs, dim=2)
|
| 848 |
|
| 849 |
CMV1_span_tagged_tokens = [
|
|
|
|
| 903 |
tokens = CMN1spanTaggingTokenizer.tokenize(
|
| 904 |
CMN1spanTaggingTokenizer.decode(CMN1spanTaggingTokenizer.encode(sentence))
|
| 905 |
)
|
| 906 |
+
inputs = CMN1spanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 907 |
+
|
| 908 |
+
outputs = CMN1spanTaggingModel(inputs)[0]
|
| 909 |
predictions = torch.argmax(outputs, dim=2)
|
| 910 |
|
| 911 |
CMN1_span_tagged_tokens = [
|
|
|
|
| 955 |
) # added return
|
| 956 |
|
| 957 |
|
| 958 |
+
###############################################################################
|
| 959 |
+
|
| 960 |
+
###############################################################################
|
| 961 |
+
def tag_CLN_span(sentence):
|
| 962 |
+
if re.search("\[\:\]\s*$", sentence):
|
| 963 |
+
sentence = sentence + " blah blah blah."
|
| 964 |
+
|
| 965 |
+
tokens = CLNspanTaggingTokenizer.tokenize(
|
| 966 |
+
CLNspanTaggingTokenizer.decode(CLNspanTaggingTokenizer.encode(sentence))
|
| 967 |
+
)
|
| 968 |
+
inputs = CLNspanTaggingTokenizer.encode(sentence, return_tensors="pt")
|
| 969 |
+
|
| 970 |
+
outputs = CLNspanTaggingModel(inputs)[0]
|
| 971 |
+
predictions = torch.argmax(outputs, dim=2)
|
| 972 |
+
|
| 973 |
+
CLN_span_tagged_tokens = [
|
| 974 |
+
(token, CLNspan_label_list[prediction])
|
| 975 |
+
for token, prediction in zip(tokens, predictions[0].tolist())
|
| 976 |
+
]
|
| 977 |
+
|
| 978 |
+
CLN_span_tagged_predictions = [p for t, p in CLN_span_tagged_tokens]
|
| 979 |
+
|
| 980 |
+
aligned_tokens = []
|
| 981 |
+
aligned_predictions = []
|
| 982 |
+
aligned_pos_tags = []
|
| 983 |
+
aligned_lemmas = []
|
| 984 |
+
|
| 985 |
+
sc_sentence = (
|
| 986 |
+
clean_for_spaCy(sentence) if "[" in sentence and "]" in sentence else sentence
|
| 987 |
+
)
|
| 988 |
+
doc = nlp(sc_sentence)
|
| 989 |
+
spacy_pos_tags = [t.tag_ for t in doc]
|
| 990 |
+
spacy_tokens = [t.text for t in doc]
|
| 991 |
+
spacy_lemmas = [t.lemma_ for t in doc]
|
| 992 |
+
|
| 993 |
+
a2b, b2a = tokenizations.get_alignments(spacy_tokens, tokens)
|
| 994 |
+
|
| 995 |
+
for i in range(len(spacy_tokens)):
|
| 996 |
+
for j in a2b[i]:
|
| 997 |
+
aligned_tokens.append(spacy_tokens[i])
|
| 998 |
+
aligned_pos_tags.append(spacy_pos_tags[i])
|
| 999 |
+
aligned_lemmas.append(spacy_lemmas[i])
|
| 1000 |
+
aligned_predictions.append(CLN_span_tagged_predictions[j])
|
| 1001 |
+
break
|
| 1002 |
+
|
| 1003 |
+
clean_tokens, clean_pos_tags, clean_lemmas, clean_predictions = sign_cleaning(
|
| 1004 |
+
aligned_tokens, aligned_pos_tags, aligned_lemmas, aligned_predictions
|
| 1005 |
+
)
|
| 1006 |
+
|
| 1007 |
+
simplifiable = all("COMPOUND" in p for p in clean_predictions)
|
| 1008 |
+
|
| 1009 |
+
if not simplifiable:
|
| 1010 |
+
True # placeholder for future logic
|
| 1011 |
+
|
| 1012 |
+
return (
|
| 1013 |
+
clean_tokens,
|
| 1014 |
+
clean_pos_tags,
|
| 1015 |
+
clean_lemmas,
|
| 1016 |
+
clean_predictions,
|
| 1017 |
+
) # added return
|
| 1018 |
+
|
| 1019 |
+
|
| 1020 |
###############################################################################
|
| 1021 |
|
| 1022 |
###############################################################################
|
|
|
|
| 1056 |
tokens = SignTaggingTokenizer.tokenize(
|
| 1057 |
SignTaggingTokenizer.decode(SignTaggingTokenizer.encode(sent))
|
| 1058 |
)
|
| 1059 |
+
inputs = SignTaggingTokenizer.encode(sent, return_tensors="pt")
|
| 1060 |
+
|
| 1061 |
+
outputs = SignTaggingModel(inputs)[0]
|
| 1062 |
predictions = torch.argmax(outputs, dim=2)
|
| 1063 |
|
| 1064 |
sign_tagged_tokens = [
|
|
|
|
| 1377 |
|
| 1378 |
return simplification
|
| 1379 |
|
| 1380 |
+
###############################################################################
|
| 1381 |
+
|
| 1382 |
+
###############################################################################
|
| 1383 |
+
def looks_like_proper_name_np(np_text: str) -> bool:
|
| 1384 |
+
toks = np_text.split()
|
| 1385 |
+
return bool(toks) and all(t[:1].isupper() for t in toks if t.isalpha())
|
| 1386 |
+
###############################################################################
|
| 1387 |
+
|
| 1388 |
+
###############################################################################
|
| 1389 |
+
def SSMNsimplify(xml_sent):
|
| 1390 |
+
"""
|
| 1391 |
+
Appositive / non-finite nominal modifier (SSMN):
|
| 1392 |
+
|
| 1393 |
+
John Smith, the famous lecturer, went home.
|
| 1394 |
+
→ John Smith went home.
|
| 1395 |
+
→ John Smith is a famous lecturer.
|
| 1396 |
+
"""
|
| 1397 |
+
simplification = []
|
| 1398 |
+
|
| 1399 |
+
try:
|
| 1400 |
+
if xml_sent.attrib.get("TYPE") not in ("COMPLEX_NP", "APPOSITIVE", "SSMN"):
|
| 1401 |
+
# still try if elements exist
|
| 1402 |
+
pass
|
| 1403 |
+
|
| 1404 |
+
def elem_text(elem):
|
| 1405 |
+
if elem is None:
|
| 1406 |
+
return ""
|
| 1407 |
+
parts = []
|
| 1408 |
+
for w in elem.findall("W"):
|
| 1409 |
+
if not w.text:
|
| 1410 |
+
continue
|
| 1411 |
+
t = str(w.text).strip()
|
| 1412 |
+
t = re.sub(r"[\[\]]+", "", t)
|
| 1413 |
+
if t:
|
| 1414 |
+
parts.append(t)
|
| 1415 |
+
return " ".join(parts).strip()
|
| 1416 |
+
|
| 1417 |
+
def strip_edge_commas(s: str) -> str:
|
| 1418 |
+
s = re.sub(r"\s+", " ", (s or "")).strip()
|
| 1419 |
+
s = re.sub(r"^[,;:\s]+", "", s)
|
| 1420 |
+
s = re.sub(r"[,;:\s]+$", "", s)
|
| 1421 |
+
return s.strip()
|
| 1422 |
+
|
| 1423 |
+
def tidy_sent(s: str) -> str:
|
| 1424 |
+
s = re.sub(r"\s+", " ", (s or "")).strip()
|
| 1425 |
+
s = re.sub(r"\s+([.,!?;:])", r"\1", s)
|
| 1426 |
+
s = re.sub(r"([.,!?;:])\s+", r"\1 ", s)
|
| 1427 |
+
s = re.sub(r",+\s*\.", ".", s)
|
| 1428 |
+
if s and not s.endswith((".", "!", "?")):
|
| 1429 |
+
s += "."
|
| 1430 |
+
if s:
|
| 1431 |
+
s = s[0].upper() + s[1:]
|
| 1432 |
+
return s
|
| 1433 |
+
|
| 1434 |
+
before = elem_text(xml_sent.find(".//BEFORE_COMPLEX_CONSTITUENT"))
|
| 1435 |
+
head = elem_text(xml_sent.find(".//IN_COMPLEX_CONSTITUENT_BEFORESIGN"))
|
| 1436 |
+
# trigger comma lives in IN_COMPLEX_CONSTITUENT — discarded
|
| 1437 |
+
appositive = elem_text(xml_sent.find(".//IN_COMPLEX_CONSTITUENT_AFTERSIGN"))
|
| 1438 |
+
after = elem_text(xml_sent.find(".//AFTER_COMPLEX_CONSTITUENT"))
|
| 1439 |
+
|
| 1440 |
+
head = strip_edge_commas(head)
|
| 1441 |
+
appositive = strip_edge_commas(appositive)
|
| 1442 |
+
before = strip_edge_commas(before)
|
| 1443 |
+
after = strip_edge_commas(after)
|
| 1444 |
+
|
| 1445 |
+
if not head:
|
| 1446 |
+
return []
|
| 1447 |
+
|
| 1448 |
+
# 1) Matrix clause without appositive
|
| 1449 |
+
matrix = " ".join(x for x in [before, head, after] if x).strip()
|
| 1450 |
+
matrix = tidy_sent(matrix)
|
| 1451 |
|
| 1452 |
+
# 2) Equative: Head is a/an …
|
| 1453 |
+
if looks_like_proper_name_np(appositive):
|
| 1454 |
+
pred_np = appositive
|
| 1455 |
+
else:
|
| 1456 |
+
pred_np = make_indefinite_np(appositive)
|
| 1457 |
+
equative = tidy_sent(f"{head} is {pred_np}")
|
| 1458 |
+
|
| 1459 |
+
if matrix:
|
| 1460 |
+
simplification.append(matrix)
|
| 1461 |
+
if equative and equative != matrix:
|
| 1462 |
+
simplification.append(equative)
|
| 1463 |
+
|
| 1464 |
+
print(f"SSMNsimplify produced {len(simplification)} sentences: {simplification}")
|
| 1465 |
+
return simplification
|
| 1466 |
+
|
| 1467 |
+
except Exception as e:
|
| 1468 |
+
print(f"SSMNsimplify error: {e}")
|
| 1469 |
+
return []
|
| 1470 |
###############################################################################
|
| 1471 |
|
| 1472 |
###############################################################################
|
|
|
|
| 1518 |
return out
|
| 1519 |
###############################################################################
|
| 1520 |
|
| 1521 |
+
###############################################################################
|
| 1522 |
+
def _singular_present_form(lemma: str) -> str:
|
| 1523 |
+
"""Map verb lemma → 3rd person singular present."""
|
| 1524 |
+
lemma = (lemma or "").lower()
|
| 1525 |
+
irregular = {
|
| 1526 |
+
"be": "is",
|
| 1527 |
+
"have": "has",
|
| 1528 |
+
"do": "does",
|
| 1529 |
+
"go": "goes",
|
| 1530 |
+
"say": "says",
|
| 1531 |
+
}
|
| 1532 |
+
if lemma in irregular:
|
| 1533 |
+
return irregular[lemma]
|
| 1534 |
+
if lemma.endswith(("s", "sh", "ch", "x", "z", "o")):
|
| 1535 |
+
return lemma + "es"
|
| 1536 |
+
if len(lemma) > 1 and lemma.endswith("y") and lemma[-2] not in "aeiou":
|
| 1537 |
+
return lemma[:-1] + "ies"
|
| 1538 |
+
return lemma + "s"
|
| 1539 |
+
|
| 1540 |
+
|
| 1541 |
+
def subject_is_singular(subj_token) -> bool:
|
| 1542 |
+
"""True if the nominal subject should take singular agreement."""
|
| 1543 |
+
# Coordinated subjects → plural
|
| 1544 |
+
if any(c.dep_ == "cc" for c in subj_token.subtree):
|
| 1545 |
+
return False
|
| 1546 |
+
# Explicit plural morph / tags
|
| 1547 |
+
num = subj_token.morph.get("Number")
|
| 1548 |
+
if num == ["Plur"]:
|
| 1549 |
+
return False
|
| 1550 |
+
if num == ["Sing"]:
|
| 1551 |
+
return True
|
| 1552 |
+
if subj_token.tag_ in ("NNS", "NNPS", "PRP") and subj_token.lower_ in {
|
| 1553 |
+
"we", "they", "you", "i"
|
| 1554 |
+
}:
|
| 1555 |
+
# I/you are special; treat as not 3sg
|
| 1556 |
+
return False
|
| 1557 |
+
if subj_token.tag_ in ("NNS", "NNPS"):
|
| 1558 |
+
return False
|
| 1559 |
+
if subj_token.lower_ in {"i", "you", "we", "they"}:
|
| 1560 |
+
return False
|
| 1561 |
+
# he/she/it / singular NN/NNP / this/that
|
| 1562 |
+
if subj_token.lower_ in {"he", "she", "it", "this", "that"}:
|
| 1563 |
+
return True
|
| 1564 |
+
if subj_token.tag_ in ("NN", "NNP", "DT", "PRP$"):
|
| 1565 |
+
return True
|
| 1566 |
+
# fallback: whole subject span text
|
| 1567 |
+
text = " ".join(t.text for t in subj_token.subtree).lower()
|
| 1568 |
+
if " and " in text:
|
| 1569 |
+
return False
|
| 1570 |
+
return True
|
| 1571 |
+
|
| 1572 |
+
|
| 1573 |
+
def fix_subject_verb_agreement(sent: str) -> str:
|
| 1574 |
+
"""
|
| 1575 |
+
If the subject is singular and the finite verb is a plural present (VBP),
|
| 1576 |
+
rewrite the verb to 3rd-person singular (lives, is, has, …).
|
| 1577 |
+
Leaves past (VBD), progressives, etc. unchanged.
|
| 1578 |
+
"""
|
| 1579 |
+
if not sent or not sent.strip():
|
| 1580 |
+
return sent
|
| 1581 |
+
|
| 1582 |
+
doc = nlp(sent)
|
| 1583 |
+
edits = [] # (token.i, new_text)
|
| 1584 |
+
|
| 1585 |
+
for tok in doc:
|
| 1586 |
+
if tok.dep_ != "ROOT" and tok.dep_ not in ("ccomp", "xcomp", "advcl"):
|
| 1587 |
+
# Prefer sentence ROOT finite verb
|
| 1588 |
+
pass
|
| 1589 |
+
|
| 1590 |
+
roots = [t for t in doc if t.dep_ == "ROOT"]
|
| 1591 |
+
if not roots:
|
| 1592 |
+
return sent
|
| 1593 |
+
verb = roots[0]
|
| 1594 |
+
|
| 1595 |
+
# Only repair untreated present plural / base used as present
|
| 1596 |
+
if verb.tag_ not in ("VBP", "VB"):
|
| 1597 |
+
return sent
|
| 1598 |
+
# Skip auxiliaries that are not the lexical main verb when better root exists
|
| 1599 |
+
# (spaCy ROOT is usually the main verb or the finite aux)
|
| 1600 |
+
|
| 1601 |
+
subjs = [c for c in verb.children if c.dep_ in ("nsubj", "nsubjpass")]
|
| 1602 |
+
if not subjs:
|
| 1603 |
+
return sent
|
| 1604 |
+
subj = subjs[0]
|
| 1605 |
+
|
| 1606 |
+
if not subject_is_singular(subj):
|
| 1607 |
+
return sent
|
| 1608 |
+
|
| 1609 |
+
lemma = verb.lemma_.lower()
|
| 1610 |
+
new_form = _singular_present_form(lemma)
|
| 1611 |
+
|
| 1612 |
+
# Preserve capitalisation
|
| 1613 |
+
if verb.text and verb.text[0].isupper():
|
| 1614 |
+
new_form = new_form[0].upper() + new_form[1:]
|
| 1615 |
+
|
| 1616 |
+
if new_form == verb.text:
|
| 1617 |
+
return sent
|
| 1618 |
+
|
| 1619 |
+
pieces = [t.text_with_ws for t in doc]
|
| 1620 |
+
# Replace only this token's text, keep its following whitespace
|
| 1621 |
+
ws = verb.whitespace_
|
| 1622 |
+
pieces[verb.i] = new_form + ws
|
| 1623 |
+
return "".join(pieces).strip()
|
| 1624 |
+
###############################################################################
|
| 1625 |
+
|
| 1626 |
###############################################################################
|
| 1627 |
def CompoundSimplify(xml_sent):
|
| 1628 |
"""
|
|
|
|
| 1688 |
x for x in [before_text, aftersign_text, after_text] if x
|
| 1689 |
).strip()
|
| 1690 |
|
| 1691 |
+
|
| 1692 |
def tidy(s):
|
| 1693 |
+
if not s:
|
| 1694 |
+
return ""
|
| 1695 |
+
s = str(s)
|
| 1696 |
s = re.sub(r"\s+", " ", s).strip()
|
| 1697 |
s = re.sub(r"\s+([.,!?;:])", r"\1", s)
|
| 1698 |
s = re.sub(r"([.,!?;:])\s+", r"\1 ", s)
|
|
|
|
| 1699 |
# decimals: 8 . 2 → 8.2
|
| 1700 |
s = re.sub(r"(\d)\s*\.\s*(\d)", r"\1.\2", s)
|
| 1701 |
+
# list debris only at the end / before final period
|
|
|
|
| 1702 |
s = re.sub(r"[,;:]+\s*\.", ".", s)
|
|
|
|
| 1703 |
s = re.sub(r"[,;:]+\s*$", "", s)
|
| 1704 |
+
s = s.strip()
|
| 1705 |
if s and not s.endswith((".", "!", "?")):
|
| 1706 |
s += "."
|
| 1707 |
if s:
|
| 1708 |
s = s[0].upper() + s[1:]
|
| 1709 |
return s
|
| 1710 |
+
|
| 1711 |
+
|
| 1712 |
|
| 1713 |
sent1 = tidy(sent1)
|
| 1714 |
sent2 = tidy(sent2)
|
|
|
|
| 1765 |
)
|
| 1766 |
after_complex_constituent = ET.SubElement(xml_sent, "AFTER_COMPLEX_CONSTITUENT")
|
| 1767 |
|
| 1768 |
+
elif sign_tag in ["M:Y_SSMN"]: # SSMA later if you want ages
|
| 1769 |
+
xml_sent.set("TYPE", "COMPLEX_NP") # or "APPOSITIVE"
|
| 1770 |
+
before_complex_constituent = ET.SubElement(
|
| 1771 |
+
xml_sent, "BEFORE_COMPLEX_CONSTITUENT"
|
| 1772 |
+
)
|
| 1773 |
+
in_complex_constituent_beforesign = ET.SubElement(
|
| 1774 |
+
xml_sent, "IN_COMPLEX_CONSTITUENT_BEFORESIGN"
|
| 1775 |
+
)
|
| 1776 |
+
in_complex_constituent = ET.SubElement(
|
| 1777 |
+
xml_sent, "IN_COMPLEX_CONSTITUENT"
|
| 1778 |
+
)
|
| 1779 |
+
in_complex_constituent_aftersign = ET.SubElement(
|
| 1780 |
+
xml_sent, "IN_COMPLEX_CONSTITUENT_AFTERSIGN"
|
| 1781 |
+
)
|
| 1782 |
+
after_complex_constituent = ET.SubElement(
|
| 1783 |
+
xml_sent, "AFTER_COMPLEX_CONSTITUENT"
|
| 1784 |
+
)
|
| 1785 |
+
|
| 1786 |
+
elif sign_tag in ["M:Y_CCV", "M:Y_CMV1", "M:Y_CMN1", "M:Y_CLN"]:
|
| 1787 |
+
xml_sent.set("TYPE", "COMPOUND")
|
| 1788 |
before_complex_constituent = ET.SubElement(
|
| 1789 |
xml_sent, "BEFORE_COMPOUND_CONSTITUENT"
|
| 1790 |
)
|
|
|
|
| 1839 |
word.attrib["POS"] = pos_tags[x]
|
| 1840 |
word.attrib["LEMMA"] = lemmas[x]
|
| 1841 |
|
| 1842 |
+
elif (
|
| 1843 |
+
"NOT_NOUN_COORDINATOR" in predictions[x]
|
| 1844 |
+
or "NOT_NP_COORDINATOR" in predictions[x]
|
| 1845 |
+
or "NOT_CLAUSE_COORDINATOR" in predictions[x]
|
| 1846 |
+
or "NOT_VP_COORDINATOR" in predictions[x]
|
| 1847 |
+
):
|
| 1848 |
+
word = ET.SubElement(in_complex_constituent_beforesign, "W")
|
| 1849 |
+
word.text = tokens[x]
|
| 1850 |
+
word.attrib["POS"] = pos_tags[x]
|
| 1851 |
+
word.attrib["LEMMA"] = lemmas[x]
|
| 1852 |
+
|
| 1853 |
elif "IN_" in predictions[x]:
|
| 1854 |
word = ET.SubElement(in_complex_constituent, "W")
|
| 1855 |
word.text = tokens[x]
|
|
|
|
| 1868 |
word.attrib["POS"] = pos_tags[x]
|
| 1869 |
word.attrib["LEMMA"] = lemmas[x]
|
| 1870 |
|
| 1871 |
+
|
| 1872 |
+
|
| 1873 |
x += 1
|
| 1874 |
|
| 1875 |
# Print the XML (as you requested)
|
|
|
|
| 1889 |
final_iccb_pos_tag = "UNKNOWN"
|
| 1890 |
|
| 1891 |
punct = re.compile(r"^[\,\:\;]$")
|
| 1892 |
+
VERB_TAGS = ["VB", "VBD", "VBG", "VBN", "VBZ", "VBP", "MD"]
|
| 1893 |
+
NOMINAL_TAGS = ["NNP", "NNPS", "NN", "NNS", "DT", "CD", "PRP", "PRP$"]
|
| 1894 |
|
| 1895 |
for el in xml_sent:
|
| 1896 |
if el.tag == "IN_COMPLEX_CONSTITUENT_AFTERSIGN":
|
|
|
|
| 1900 |
if w.text and not punct.match(w.text.strip()):
|
| 1901 |
first_icca_pos_tag = w.attrib.get("POS", "UNKNOWN")
|
| 1902 |
break
|
| 1903 |
+
# last *content* token, not a trailing comma
|
| 1904 |
+
for w in reversed(list(el)):
|
| 1905 |
+
if w.text and not punct.match(w.text.strip()):
|
| 1906 |
+
final_icca_pos_tag = w.attrib.get("POS", "UNKNOWN")
|
| 1907 |
+
break
|
| 1908 |
except Exception:
|
| 1909 |
pass
|
| 1910 |
|
|
|
|
| 1920 |
|
| 1921 |
if final_iccb_pos_tag in ["IN"]:
|
| 1922 |
complex_NP_type = "PREPOSITION_OBJECT_RELATIVISED"
|
| 1923 |
+
elif first_icca_pos_tag in NOMINAL_TAGS and final_icca_pos_tag in VERB_TAGS:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1924 |
complex_NP_type = "VERB_OBJECT_RELATIVISED"
|
| 1925 |
+
elif first_icca_pos_tag in VERB_TAGS:
|
| 1926 |
complex_NP_type = "SUBJECT_RELATIVISED"
|
| 1927 |
|
| 1928 |
return complex_NP_type
|
|
|
|
| 2033 |
mode = "ccv" → bracket only the rightmost CCV sign
|
| 2034 |
mode = "cmv1" → bracket only the rightmost CMV1 sign
|
| 2035 |
mode = "cmn1" → bracket only the rightmost CMN1 sign
|
| 2036 |
+
mode = "cln" → bracket only the rightmost CLN sign
|
| 2037 |
mode = "ssccv" → bracket one relative SSCCV trigger:
|
| 2038 |
- nested relatives (2+ opens before next ESCCV): innermost
|
| 2039 |
(rightmost preferred trigger before that ESCCV)
|
| 2040 |
- coordinated relatives (CCV between opens): leftmost
|
| 2041 |
- otherwise: leftmost relative trigger
|
| 2042 |
- fallback: leftmost SSCCV/ESCCV (e.g. bare "that")
|
| 2043 |
+
mode = "ssmn" → bracket only the leftmost SSMN sign
|
| 2044 |
"""
|
| 2045 |
|
| 2046 |
def surface_of(item):
|
|
|
|
| 2054 |
ccv_indices = []
|
| 2055 |
cmv1_indices = []
|
| 2056 |
cmn1_indices = []
|
| 2057 |
+
cln_indices = []
|
| 2058 |
ssccv_indices = []
|
| 2059 |
esccv_indices = []
|
| 2060 |
+
ssmn_indices = []
|
| 2061 |
|
| 2062 |
for i, item in enumerate(tagged_list):
|
| 2063 |
s = str(item)
|
|
|
|
| 2067 |
cmv1_indices.append(i)
|
| 2068 |
if "<M:Y_CMN1>" in s or "M:Y_CMN1" in s:
|
| 2069 |
cmn1_indices.append(i)
|
| 2070 |
+
if "<M:Y_CLN>" in s or "M:Y_CLN" in s:
|
| 2071 |
+
cln_indices.append(i)
|
| 2072 |
if "M:Y_SSCCV" in s:
|
| 2073 |
ssccv_indices.append(i)
|
| 2074 |
if "M:Y_ESCCV" in s:
|
| 2075 |
esccv_indices.append(i)
|
| 2076 |
if i not in ssccv_indices:
|
| 2077 |
ssccv_indices.append(i)
|
| 2078 |
+
if "M:Y_SSMN" in s:
|
| 2079 |
+
ssmn_indices.append(i)
|
| 2080 |
+
|
| 2081 |
target = set()
|
| 2082 |
|
| 2083 |
if mode == "ccv" and ccv_indices:
|
|
|
|
| 2089 |
elif mode == "cmn1" and cmn1_indices:
|
| 2090 |
target.add(cmn1_indices[-1])
|
| 2091 |
|
| 2092 |
+
elif mode == "cln" and cln_indices:
|
| 2093 |
+
target.add(cln_indices[-1])
|
| 2094 |
+
|
| 2095 |
+
elif mode == "ssmn" and ssmn_indices:
|
| 2096 |
+
target.add(ssmn_indices[0]) # leftmost appositive boundary
|
| 2097 |
+
|
| 2098 |
elif mode == "ssccv" and ssccv_indices:
|
| 2099 |
relative_pronouns = {"who", "which", "whom", "whose"}
|
| 2100 |
preferred = []
|
|
|
|
| 2181 |
|
| 2182 |
|
| 2183 |
###############################################################################
|
|
|
|
| 2184 |
def process_text(input_text):
|
| 2185 |
print("=== ITERATIVE SIMPLIFICATION (priority + clean tokens) ===")
|
| 2186 |
try:
|
|
|
|
| 2271 |
any(x in t for x in ["M:Y_SSCCV", "M:Y_ESCCV"])
|
| 2272 |
for t in tagged_list
|
| 2273 |
)
|
| 2274 |
+
has_ssmn = any(
|
| 2275 |
+
any(x in t for x in ["M:Y_SSMN", "M:Y_ESMN"])
|
| 2276 |
+
for t in tagged_list
|
| 2277 |
+
)
|
| 2278 |
has_ccv = any(
|
| 2279 |
"<M:Y_CCV>" in t or "M:Y_CCV" in t for t in tagged_list
|
| 2280 |
)
|
|
|
|
| 2284 |
has_cmn1 = any(
|
| 2285 |
"<M:Y_CMN1>" in t or "M:Y_CMN1" in t for t in tagged_list
|
| 2286 |
)
|
| 2287 |
+
has_cln = any(
|
| 2288 |
+
"<M:Y_CLN>" in t or "M:Y_CLN" in t for t in tagged_list
|
| 2289 |
+
)
|
| 2290 |
|
| 2291 |
if has_ssccv:
|
| 2292 |
mode = "ssccv"
|
| 2293 |
xml_tag = "M:Y_SSCCV"
|
| 2294 |
simplifier = SSCCVsimplify
|
| 2295 |
span_fn = tag_SSCCV_span
|
| 2296 |
+
elif has_ssmn:
|
| 2297 |
+
mode = "ssmn"
|
| 2298 |
+
xml_tag = "M:Y_SSMN"
|
| 2299 |
+
simplifier = SSMNsimplify
|
| 2300 |
+
span_fn = tag_SSMN_span
|
| 2301 |
elif has_ccv:
|
| 2302 |
mode = "ccv"
|
| 2303 |
xml_tag = "M:Y_CCV"
|
|
|
|
| 2313 |
xml_tag = "M:Y_CMN1"
|
| 2314 |
simplifier = CompoundSimplify
|
| 2315 |
span_fn = tag_CMN1_span
|
| 2316 |
+
elif has_cln:
|
| 2317 |
+
mode = "cln"
|
| 2318 |
+
xml_tag = "M:Y_CLN"
|
| 2319 |
+
simplifier = CompoundSimplify
|
| 2320 |
+
span_fn = tag_CLN_span
|
| 2321 |
else:
|
| 2322 |
sentence_final.append(current)
|
| 2323 |
continue
|
|
|
|
| 2352 |
)
|
| 2353 |
simps = simplifier(xml_sent)
|
| 2354 |
|
| 2355 |
+
# Residual comma → same conjunction (CCV / CMV1 / CMN1 / CLN only)
|
| 2356 |
if (
|
| 2357 |
simps
|
| 2358 |
and simplifier is CompoundSimplify
|
| 2359 |
+
and xml_tag
|
| 2360 |
+
in ("M:Y_CCV", "M:Y_CMV1", "M:Y_CMN1", "M:Y_CLN")
|
| 2361 |
):
|
| 2362 |
coord = extract_coordinator_from_xml(xml_sent)
|
| 2363 |
if coord:
|
|
|
|
| 2371 |
)
|
| 2372 |
rewritten.append(s2)
|
| 2373 |
simps = rewritten
|
| 2374 |
+
|
| 2375 |
+
# Subject–verb agreement after coordinated-NP (CMN1) splits
|
| 2376 |
+
if simps and xml_tag == "M:Y_CMN1":
|
| 2377 |
+
fixed = []
|
| 2378 |
+
for s in simps:
|
| 2379 |
+
s2 = fix_subject_verb_agreement(s)
|
| 2380 |
+
if s2 != s:
|
| 2381 |
+
print(f" Agreement fix: {s!r} → {s2!r}")
|
| 2382 |
+
fixed.append(s2)
|
| 2383 |
+
simps = fixed
|
| 2384 |
+
|
| 2385 |
if not simps:
|
| 2386 |
print(
|
| 2387 |
"Simplifier returned []; forcing final (no re-queue)"
|
|
|
|
| 2416 |
if not queued_any:
|
| 2417 |
sentence_final.append(current)
|
| 2418 |
|
|
|
|
| 2419 |
for s in order_sentences_for_discourse(sentence_final):
|
| 2420 |
s = clean_output(s)
|
| 2421 |
if s:
|
| 2422 |
para_simplified.append(s)
|
| 2423 |
|
| 2424 |
if para_simplified:
|
|
|
|
| 2425 |
para_simplified = order_sentences_for_discourse(para_simplified)
|
| 2426 |
output_paragraphs.append(" ".join(para_simplified))
|
| 2427 |
|
|
|
|
| 2482 |
}
|
| 2483 |
"""
|
| 2484 |
|
| 2485 |
+
with gr.Blocks(
|
| 2486 |
+
title="Syntactic Sentence Simplifier",
|
| 2487 |
+
css=custom_css,
|
| 2488 |
+
) as demo:
|
| 2489 |
gr.Markdown("# Syntactic Sentence Simplifier")
|
| 2490 |
gr.Markdown(
|
| 2491 |
+
"Paste or type your text below. Uses Hugging Face models + simplification logic."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2492 |
)
|
| 2493 |
|
|
|
|
|
|
|
| 2494 |
input_text = gr.Textbox(
|
| 2495 |
+
lines=10,
|
| 2496 |
label="Original text",
|
| 2497 |
+
placeholder="Paste your text here...",
|
| 2498 |
elem_classes=["input-text"],
|
| 2499 |
)
|
| 2500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2501 |
output_text = gr.Textbox(
|
| 2502 |
+
lines=10,
|
| 2503 |
label="Simplified text",
|
| 2504 |
elem_classes=["output-text"],
|
| 2505 |
)
|
| 2506 |
|
| 2507 |
+
btn = gr.Button("Process & Simplify", variant="primary")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2508 |
|
| 2509 |
+
btn.click(
|
| 2510 |
+
fn=process_text,
|
| 2511 |
+
inputs=input_text,
|
| 2512 |
+
outputs=output_text,
|
| 2513 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2514 |
|
| 2515 |
+
print(" Gradio interface ready. Starting web server...")
|
| 2516 |
|
| 2517 |
if __name__ == "__main__":
|
| 2518 |
demo.launch(
|
| 2519 |
server_name="0.0.0.0",
|
| 2520 |
server_port=7860,
|
| 2521 |
+
debug=True,
|
| 2522 |
+
share=False,
|
| 2523 |
+
)
|
| 2524 |
+
|
| 2525 |
+
|
| 2526 |
+
|
| 2527 |
+
'''
|
| 2528 |
+
test = (
|
| 2529 |
+
"Joshua told me that John, who lives in London, is going to Wolverhampton, Jack loves Norwich, and Mary, who is a bit nervous, adores Birmingham, just as Fred predicted."
|
| 2530 |
)
|
| 2531 |
+
print(process_text(test))
|
| 2532 |
+
'''
|
| 2533 |
|