Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 43 additions & 29 deletions markup_doc/labeling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,17 @@ def create_labeled_object2(i, item, state, sections):
obj["type"] = "paragraph"
obj["value"] = {"label": state["label"], "paragraph": item.get("text")}

if state.get("body") and re.search(
r"^(refer[eê]nci|references?)\s*$", item.get("text").strip().lower()
):
state["label"] = "<sec>"
state["body"] = False
state["back"] = True
result = {"label": "<sec>", "body": False, "back": True}
obj["type"] = "paragraph"
obj["value"] = {"label": state["label"], "paragraph": item.get("text")}


if not result:
result = {"label": "<p>", "body": state["body"], "back": state["back"]}
state["label"] = result.get("label")
Expand Down Expand Up @@ -874,12 +885,12 @@ def get_data_first_block(text, metadata, user_id):
"Content-Type": "application/json",
}

resp_json = {}
response = requests.post(url, json=payload, headers=headers)

if response.status_code == 200:
response_json = response.json()
message_str = response_json["message"]

resp_json = json.loads(message_str)

return resp_json
Expand Down Expand Up @@ -1017,22 +1028,30 @@ def search_special_id(data_body, label):
clean_label = re.sub(r"^[\s\.,;:–—-]+", "", label).capitalize()

if d["type"] == "image":
if clean_label == data["figlabel"]:
return data.get("figid")
figlabel = data.get("figlabel") or ""
figid = data.get("figid") or ""
if clean_label == figlabel:
return figid or None
if (
data["figid"][0] == clean_label.lower()[0]
and data["figid"][1] in clean_label.lower()
figid
and len(figid) > 1
and figid[0] == clean_label.lower()[:1]
and figid[1] in clean_label.lower()
):
return data.get("figid")
return figid

if d["type"] == "table":
if clean_label == data["tablabel"]:
return data.get("tabid")
tablabel = data.get("tablabel") or ""
tabid = data.get("tabid") or ""
if clean_label == tablabel:
return tabid or None
if (
data["tabid"][0] == clean_label.lower()[0]
and data["tabid"][1] in clean_label.lower()
tabid
and len(tabid) > 1
and tabid[0] == clean_label.lower()[:1]
and tabid[1] in clean_label.lower()
):
return data.get("tabid")
return tabid

for d in data_body:
if d["type"] in ["compound_paragraph"]:
Expand Down Expand Up @@ -1279,6 +1298,7 @@ def append_fragment(node_dest, val):

clean = escape_angle_brackets_outside_tags(clean)
clean = remove_unpaired_tags(clean)
clean = re.sub(r'<(?![/a-zA-Z_])', '&lt;', clean)

if clean == "":
parent = node_dest.getparent()
Expand Down Expand Up @@ -1351,23 +1371,17 @@ def proccess_special_content(text, data_body):
res = []
dict_type = {"f": "fig", "t": "table", "e": "disp-formula"}

try:
for match in re.finditer(
pattern, text, re.IGNORECASE | re.UNICODE | re.VERBOSE
):
label = match.group(0)

id = search_special_id(data_body, label)

res.append(
{
"label": label,
"id": id,
"reftype": dict_type.get(id[0].lower(), "other"),
}
)
except Exception as exc:
print(f"ERROR proccess_special_content: {exc}")
pass
for match in re.finditer(pattern, text, re.IGNORECASE | re.UNICODE | re.VERBOSE):
label = match.group(0)
id = search_special_id(data_body, label)
if id is None:
continue
res.append(
{
"label": label,
"id": id,
"reftype": dict_type.get(id[0].lower(), "other"),
}
)

return res
18 changes: 18 additions & 0 deletions markup_doc/migrations/0003_articledocxmarkup_marked_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 6.0.5 on 2026-05-26 14:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('markup_doc', '0002_alter_articledocx_estatus_and_more'),
]

operations = [
migrations.AddField(
model_name='articledocxmarkup',
name='marked_file',
field=models.FileField(blank=True, null=True, upload_to='uploads_docx_marked/', verbose_name='Marked Document'),
),
]
16 changes: 16 additions & 0 deletions markup_doc/migrations/0004_articledocxmarkup_xref_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('markup_doc', '0003_articledocxmarkup_marked_file'),
]

operations = [
migrations.AddField(
model_name='articledocxmarkup',
name='xref_status',
field=models.JSONField(blank=True, null=True, verbose_name='XRef Status'),
),
]
119 changes: 115 additions & 4 deletions markup_doc/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import json
import os

from django import forms
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django.utils.html import format_html
from django.utils.html import format_html, mark_safe
from django.utils.translation import gettext_lazy as _
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import FieldPanel, ObjectList, TabbedInterface
from wagtail.admin.panels import FieldPanel, ObjectList, Panel, TabbedInterface
from wagtail.blocks import ChoiceBlock, StreamBlock, StructBlock, TextBlock
from wagtail.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
Expand All @@ -26,8 +29,6 @@ class ProcessStatus(models.IntegerChoices):
class ReadOnlyFileWidget(forms.Widget):
def render(self, name, value, attrs=None, renderer=None):
if value:
# Muestra el archivo como un enlace de descarga
# return format_html('<a href="{}" target="_blank" download>{}</a>', value.url, value.name.split('/')[-1])
instance = value.instance
url = reverse("generate_xml", args=[instance.pk])
return format_html(
Expand All @@ -36,6 +37,74 @@ def render(self, name, value, attrs=None, renderer=None):
return ""


class DownloadMarkedFileWidget(forms.Widget):
def render(self, name, value, attrs=None, renderer=None):
if value:
instance = value.instance
url = reverse("download_marked_docx", args=[instance.pk])
filename = os.path.basename(value.name)
return format_html(
'<a href="{}" target="_blank" download>{}</a>', url, filename
)
return mark_safe('<span style="color: gray;">Não disponível ainda</span>')


class XrefStatusWidget(forms.Widget):
def render(self, name, value, attrs=None, renderer=None):
if isinstance(value, str):
try:
value = json.loads(value)
except (json.JSONDecodeError, ValueError):
value = None
if not value:
return mark_safe('<span style="color: gray;">Não processado</span>')
valid = value.get("valid", False)
total_refs = value.get("total_references", 0)
total_cits = value.get("total_citations", 0)
orphaned_bk = len(value.get("orphaned_bookmarks", []))
orphaned_hl = value.get("orphaned_hyperlinks", [])
color = "green" if valid else "red"
status = "Válido" if valid else "Inválido"
html = format_html(
'<p><strong style="color:{};">{}</strong> &nbsp; {} referências | {} citações linkadas</p>',
color, status, total_refs, total_cits,
)
if orphaned_hl:
html += format_html(
'<p style="color:red;">Citações sem referência: {}</p>',
', '.join(orphaned_hl),
)
if orphaned_bk:
html += format_html(
'<p style="color:orange;">{} referência(s) sem citação no texto</p>',
orphaned_bk,
)
return html


class ReprocessButtonPanel(Panel):
def __init__(self, confirm_message="Reprocessar este documento?", **kwargs):
super().__init__(**kwargs)
self.confirm_message = confirm_message

def clone_kwargs(self):
return {**super().clone_kwargs(), "confirm_message": self.confirm_message}

class BoundPanel(Panel.BoundPanel):
def render_html(self, parent_context=None):
if not self.instance or not self.instance.pk:
return ""
url = reverse("reprocess", args=[self.instance.pk])
msg = self.panel.confirm_message.replace("'", "\\'")
return mark_safe(
f'<div style="margin:12px 0;">'
f'<a href="{url}" onclick="return confirm(\'{msg}\')" '
f'style="padding:8px 16px;background:#e9a000;color:white;'
f'font-weight:bold;border-radius:4px;text-decoration:none;">'
f'Reprocessar</a></div>'
)


class ArticleDocx(CommonControlField):
title = models.TextField(_("Document Title"), null=True, blank=True)
file = models.FileField(
Expand Down Expand Up @@ -347,6 +416,17 @@ class ArticleDocxMarkup(CommonControlField, ClusterableModel):
verbose_name=_("Document"),
upload_to="uploads_docx/",
)
marked_file = models.FileField(
null=True,
blank=True,
verbose_name=_("Marked Document"),
upload_to="uploads_docx_marked/",
)
xref_status = models.JSONField(
_("XRef Status"),
null=True,
blank=True,
)
estatus = models.IntegerField(
_("Process estatus"),
choices=ProcessStatus.choices,
Expand Down Expand Up @@ -448,6 +528,16 @@ def __str__(self):
title = self.title or ""
return f"{title} | {self.estatus}"

def get_marked_file_status(self):
if not self.marked_file:
return "Aguardando processamento"
if self.xref_status:
total = self.xref_status.get("total_references", 0)
cits = self.xref_status.get("total_citations", 0)
return f"✓ Disponível ({total} refs, {cits} citações)"
return "✓ Disponível"
get_marked_file_status.short_description = _("DOCX Marcado")

@property
def url_download(self):
return self.file_xml.url if self.file_xml else None
Expand Down Expand Up @@ -510,6 +600,9 @@ class MarkupXML(ArticleDocxMarkup):
panels_xml = [
FieldPanel("file_xml", widget=ReadOnlyFileWidget()),
FieldPanel("text_xml"),
ReprocessButtonPanel(
confirm_message="Isso irá descartar as edições manuais e reprocessar o DOCX original. Continuar?"
),
]

panels_details = [
Expand Down Expand Up @@ -551,3 +644,21 @@ class MarkupXML(ArticleDocxMarkup):

class Meta:
proxy = True


class ProcessedDocx(ArticleDocxMarkup):
panels_doc = [
FieldPanel("title"),
FieldPanel("marked_file", widget=DownloadMarkedFileWidget()),
FieldPanel("xref_status", widget=XrefStatusWidget()),
ReprocessButtonPanel(confirm_message="Reprocessar este documento?"),
]

edit_handler = TabbedInterface([
ObjectList(panels_doc, heading=_("DOCX Marcado")),
])

class Meta:
proxy = True
verbose_name = _("DOCX processado")
verbose_name_plural = _("DOCXs processados")
70 changes: 69 additions & 1 deletion markup_doc/static/js/xref-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,76 @@ function get_zip() {

// También en DOMContentLoaded por si basta
document.addEventListener("DOMContentLoaded", tryAttach);

// Llama una vez por si ya está listo
tryAttach();
})();


// Botão Reprocessar — aparece nas views de edição de ProcessedDocx e MarkupXML
(function () {
var path = window.location.pathname;
var isProcessedDocx = path.indexOf('processeddocx/edit/') !== -1;
var isMarkupXml = path.indexOf('markupxml/edit/') !== -1;

if (!isProcessedDocx && !isMarkupXml) return;

var match = path.match(/\/edit\/(\d+)\//);
if (!match) return;
var pk = match[1];

function makeBtn() {
var btn = document.createElement('button');
btn.type = 'button';
btn.id = 'reprocess-btn';
btn.textContent = 'Reprocessar';
btn.style.cssText = [
'padding:4px 12px',
'cursor:pointer',
'background:#e9a000',
'color:white',
'font-weight:bold',
'border:none',
'border-radius:4px',
'margin-left:8px',
'font-size:14px',
].join(';');
btn.addEventListener('mouseover', function () { btn.style.background = '#c98000'; });
btn.addEventListener('mouseout', function () { btn.style.background = '#e9a000'; });
btn.addEventListener('click', function () {
var msg = isMarkupXml
? 'Isso irá descartar as edições manuais e reprocessar o DOCX original. Continuar?'
: 'Reprocessar este documento?';
if (confirm(msg)) {
window.location.href = '/admin/reprocess/' + pk + '/';
}
});
return btn;
}

function tryInsert() {
if (document.getElementById('reprocess-btn')) return true;
// Tenta área de ações do cabeçalho Wagtail (v5/v6)
var actionArea = document.querySelector('.w-slim-header__action-buttons')
|| document.querySelector('[data-controller="w-slim-header"] .w-slim-header__title-wrapper');
if (actionArea) {
actionArea.appendChild(makeBtn());
return true;
}
// Fallback: insere após o primeiro botão submit (salvar)
var saveBtn = document.querySelector('button[type="submit"]');
if (saveBtn && saveBtn.parentNode) {
saveBtn.parentNode.insertBefore(makeBtn(), saveBtn.nextSibling);
return true;
}
return false;
}

if (!tryInsert()) {
var obs = new MutationObserver(function () {
if (tryInsert()) obs.disconnect();
});
obs.observe(document.documentElement, { childList: true, subtree: true });
}
})();

Loading
Loading