Skip to content

Commit 6f4e72d

Browse files
committed
Altera funções essenciais para marcação do docx.
2 parents 06d8d1a + 1cd4475 commit 6f4e72d

3 files changed

Lines changed: 651 additions & 183 deletions

File tree

markup_doc/labeling_utils.py

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
# Local application imports
1313
from model_ai.models import LlamaModel
14+
from .choices import order_labels
1415

1516

1617
MODEL_NAME_GEMINI = 'GEMINI'
@@ -836,3 +837,302 @@ def extract_keywords(text):
836837
clean_keywords = ", ".join(keywords)
837838

838839
return {"title": label, "keywords": clean_keywords}
840+
841+
842+
def create_special_content_object(item, stream_data_body, counts):
843+
"""Create objects for special content types (image, table, list, compound)"""
844+
obj = {}
845+
846+
if item.get('type') == 'image':
847+
obj = {}
848+
counts['numfig'] += 1
849+
obj['type'] = 'image'
850+
obj['value'] = {
851+
'figid' : f"f{counts['numfig']}",
852+
'label' : '<fig>',
853+
'image' : item.get('image')
854+
}
855+
856+
#Obitiene el elemento aterior
857+
try:
858+
prev_element = stream_data_body[-1]
859+
label_title = extract_label_and_title(prev_element['value']['paragraph'])
860+
obj['value']['figlabel'] = label_title['label']
861+
obj['value']['title'] = label_title['title']
862+
stream_data_body.pop(-1)
863+
except:
864+
pass
865+
866+
elif item.get('type') == 'table':
867+
obj = {}
868+
counts['numtab'] += 1
869+
obj['type'] = 'table'
870+
obj['value'] = {
871+
'tabid' : f"t{counts['numtab']}",
872+
'label' : '<table>',
873+
'content' : item.get('table')
874+
}
875+
876+
#Obitiene el elemento aterior
877+
try:
878+
prev_element = stream_data_body[-1]
879+
label_title = extract_label_and_title(prev_element['value']['paragraph'])
880+
obj['value']['tablabel'] = label_title['label']
881+
obj['value']['title'] = label_title['title']
882+
stream_data_body.pop(-1)
883+
except:
884+
#No hay elemento anterior
885+
pass
886+
887+
elif item.get('type') == 'list':
888+
obj = {}
889+
obj['type'] = 'paragraph'
890+
obj['value'] = {
891+
'label' : '<list>',
892+
'paragraph' : item.get('list')
893+
}
894+
895+
elif item.get('type') == 'compound':
896+
obj = {}
897+
counts['numeq'] += 1
898+
obj['type'] = 'compound_paragraph'
899+
obj['value'] = {
900+
'eid' : f"e{counts['numeq']}",
901+
#'label' : '<formula>',
902+
'content': item.get('text')
903+
}
904+
text_count = sum(
905+
1 for c in obj['value']['content']
906+
if c['type'] == 'text'
907+
)
908+
909+
if text_count > 1:
910+
obj['value']['label'] = '<inline-formula>'
911+
return obj, counts
912+
913+
if text_count == 0:
914+
obj['value']['label'] = '<disp-formula>'
915+
return obj, counts
916+
917+
text_value = next(
918+
item['value']
919+
for item in obj['value']['content']
920+
if item['type'] == 'text'
921+
)
922+
text = is_number_parenthesis(text_value)
923+
if text:
924+
obj['value']['label'] = '<disp-formula>'
925+
next(
926+
item
927+
for item in obj['value']['content']
928+
if item['type'] == 'text'
929+
)['value'] = text
930+
else:
931+
obj['value']['label'] = '<inline-formula>'
932+
933+
return obj, counts
934+
935+
936+
def extract_subsection(text):
937+
# Quitar punto final si existe
938+
text = text.strip()
939+
940+
# Ver si contiene una etiqueta con dos puntos
941+
match = re.match(r'(?i)\s*(.+?)\s*:\s*(.+)', text)
942+
943+
if match:
944+
label = match.group(1).strip()
945+
content = match.group(2).strip()
946+
else:
947+
label = None
948+
content = text
949+
950+
return {"title": label, "content": content}
951+
952+
953+
def search_special_id(data_body, label):
954+
for d in data_body:
955+
if d['type'] in ['image', 'table']:
956+
data = d['value']
957+
clean_label = re.sub(r'^[\s\.,;:–—-]+', '', label).capitalize()
958+
959+
if d['type'] == 'image':
960+
if clean_label == data['figlabel']:
961+
return data.get('figid')
962+
if data['figid'][0] == clean_label.lower()[0] and data['figid'][1] in clean_label.lower():
963+
return data.get('figid')
964+
965+
if d['type'] == 'table':
966+
if clean_label == data['tablabel']:
967+
return data.get('tabid')
968+
if data['tabid'][0] == clean_label.lower()[0] and data['tabid'][1] in clean_label.lower():
969+
return data.get('tabid')
970+
971+
for d in data_body:
972+
if d['type'] in ['compound_paragraph']:
973+
data = d['value']
974+
clean_label = re.sub(r'^[\s\.,;:–—-]+', '', label).lower()
975+
976+
if d['type'] == 'compound_paragraph':
977+
if data['eid'][0] in clean_label[0] and data['eid'][1] in clean_label:
978+
return data.get('eid')
979+
980+
return None
981+
982+
983+
def is_number_parenthesis(text):
984+
pattern = re.compile(r'^\s*\(\s*(\d+)\s*\)\s*$')
985+
match = pattern.fullmatch(text)
986+
if match:
987+
return f"({match.group(1)})"
988+
return None
989+
990+
991+
def remove_unpaired_tags(text):
992+
# Match opening/closing tags, capturing only the tag name (before any space or >)
993+
pattern = re.compile(r'<(/?)([a-zA-Z0-9]+)(?:\s[^>]*)?>')
994+
995+
result = []
996+
stack = [] # Stores (tag_name, position_in_result)
997+
998+
i = 0
999+
for match in pattern.finditer(text):
1000+
is_closing, tag_name = match.groups()
1001+
is_closing = bool(is_closing)
1002+
1003+
# Text between tags
1004+
if match.start() > i:
1005+
result.append(text[i:match.start()])
1006+
1007+
tag_text = text[match.start():match.end()]
1008+
1009+
if not is_closing:
1010+
# Opening tag
1011+
stack.append((tag_name, len(result)))
1012+
result.append(tag_text)
1013+
else:
1014+
# Closing tag
1015+
if stack and stack[-1][0] == tag_name:
1016+
stack.pop()
1017+
result.append(tag_text)
1018+
else:
1019+
# Orphan closing tag - skip
1020+
pass
1021+
1022+
i = match.end()
1023+
1024+
# Append remaining text
1025+
if i < len(text):
1026+
result.append(text[i:])
1027+
1028+
# Remove unclosed opening tags
1029+
for tag_name, pos in sorted(stack, reverse=True, key=lambda x: x[1]):
1030+
result.pop(pos)
1031+
1032+
return ''.join(result)
1033+
1034+
1035+
def append_fragment(node_dest, val):
1036+
if not val:
1037+
parent = node_dest.getparent()
1038+
if parent:
1039+
parent.remove(node_dest)
1040+
return
1041+
1042+
# 1) Limpiezas mínimas
1043+
# - eliminar <br> / <br/>
1044+
# - quitar saltos de línea
1045+
clean = re.sub(r"(?i)<br\s*/?>", "", val)
1046+
clean = clean.replace("\n", "")
1047+
1048+
# normaliza entidades problemáticas
1049+
clean = clean.replace("&nbsp;", " ")
1050+
clean = re.sub(r'&(?!\w+;|#\d+;)', '&amp;', clean)
1051+
1052+
clean = remove_unpaired_tags(clean)
1053+
1054+
if clean == "":
1055+
parent = node_dest.getparent()
1056+
if parent:
1057+
parent.remove(node_dest)
1058+
return
1059+
1060+
# 2) Si no hay etiquetas, es texto plano
1061+
if "<" not in clean:
1062+
node_dest.text = (node_dest.text or "") + clean
1063+
return
1064+
1065+
# 3) Envolver para que sea XML bien formado aunque empiece con texto
1066+
wrapper = etree.XML(f"<_wrap_>{clean}</_wrap_>")
1067+
1068+
# 4) Pasar el texto inicial (antes del primer tag)
1069+
if wrapper.text:
1070+
node_dest.text = (node_dest.text or "") + wrapper.text
1071+
1072+
# 5) Mover cada hijo al destino (sus .tail se conservan)
1073+
for child in list(wrapper):
1074+
node_dest.append(child)
1075+
1076+
1077+
def extract_label_and_title(text):
1078+
"""
1079+
Extrae el Label (Figura/Figure/Tabla/Table/Tabela + número) y el Title (resto del texto limpio).
1080+
Ignora mayúsculas y minúsculas y limpia puntuación/espacios entre el número y el título.
1081+
"""
1082+
# Acepta Figura/Figure y Tabla/Table/Tabela
1083+
pattern = r'\b(Imagen|Imágen|Image|Imagem|Figura|Figure|Tabla|Table|Tabela)\s+(\d+)\b'
1084+
match = re.search(pattern, text, re.IGNORECASE)
1085+
1086+
if match:
1087+
word = match.group(1).capitalize() # Normaliza capitalización
1088+
number = match.group(2)
1089+
label = f"{word} {number}"
1090+
1091+
# Texto después del número
1092+
rest = text[match.end():]
1093+
1094+
# Quita puntuación/espacios iniciales (.,;: guiones, etc.)
1095+
rest_clean = re.sub(r'^[\s\.,;:–—-]+', '', rest)
1096+
1097+
return {"label": label, "title": rest_clean.strip()}
1098+
else:
1099+
return {"label": None, "title": text.strip()}
1100+
1101+
1102+
def proccess_special_content(text, data_body):
1103+
# normaliza espacios no separables por si acaso
1104+
text = re.sub(r'[\u00A0\u2007\u202F]', ' ', text)
1105+
1106+
pattern = r"""
1107+
(?<!\w) # inicio no al medio de una palabra
1108+
(?:
1109+
Imagen|Imágen|Image|Imagem|
1110+
Figura|Figure|
1111+
Tabla|Table|Tabela|
1112+
Ecuaci[oó]n|Equa(?:ç[aã]o|cao)|Equation|
1113+
F[oó]rmula|Formula|
1114+
Eq\.|Ec\.|Form\.|F[óo]rm\.
1115+
)\s*
1116+
(?:\(\s*\d+\s*\)|\d+) # 1 o (1)
1117+
(?!\w) # que no siga una letra/número
1118+
"""
1119+
1120+
res = []
1121+
dict_type = {'f': 'fig', 't': 'table', 'e': 'disp-formula'}
1122+
1123+
try:
1124+
for match in re.finditer(pattern, text, re.IGNORECASE | re.UNICODE | re.VERBOSE):
1125+
label = match.group(0)
1126+
1127+
id = search_special_id(data_body, label)
1128+
1129+
res.append({
1130+
"label": label,
1131+
"id": id,
1132+
"reftype": dict_type.get(id[0].lower(), 'other')
1133+
})
1134+
except Exception as exc:
1135+
print(f'ERROR proccess_special_content: {exc}')
1136+
pass
1137+
1138+
return res

markup_doc/tasks.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
extract_keywords,
1717
create_labeled_object2,
1818
get_data_first_block,
19-
get_llm_model_name
19+
get_llm_model_name,
20+
create_special_content_object
2021
)
2122

2223
from markup_doc.models import ProcessStatus
@@ -42,7 +43,7 @@ def clean_labels(text):
4243

4344
# Quitar espacios al principio y final
4445
return text.strip()
45-
46+
4647

4748
@celery_app.task()
4849
def task_sync_journals_from_api():
@@ -224,6 +225,11 @@ def get_labels(title, user_id):
224225
#'original': aff['original']
225226
}
226227
stream_data.append(obj.copy())
228+
229+
if item.get('type') in ['image', 'table', 'list', 'compound']:
230+
obj, counts = create_special_content_object(item, stream_data_body, counts)
231+
stream_data_body.append(obj)
232+
continue
227233

228234
if item.get('text') is None or item.get('text') == '':
229235
state['label_next'] = state['label_next_reset'] if state['reset'] else state['label_next']

0 commit comments

Comments
 (0)