python-hwpx 5.4.0__py3-none-any.whl → 5.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hwpx/oxml/memo.py +26 -6
- hwpx/oxml/paragraph.py +101 -10
- hwpx/tools/markdown_export.py +6 -2
- hwpx/tools/text_extractor.py +14 -0
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/METADATA +1 -1
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/RECORD +11 -11
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/WHEEL +0 -0
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/entry_points.txt +0 -0
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/licenses/LICENSE +0 -0
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/licenses/NOTICE +0 -0
- {python_hwpx-5.4.0.dist-info → python_hwpx-5.5.0.dist-info}/top_level.txt +0 -0
hwpx/oxml/memo.py
CHANGED
|
@@ -14,7 +14,6 @@ from ._document_primitives import (
|
|
|
14
14
|
_default_sublist_attributes,
|
|
15
15
|
_element_local_name,
|
|
16
16
|
_memo_id,
|
|
17
|
-
_paragraph_id,
|
|
18
17
|
_sanitize_text,
|
|
19
18
|
)
|
|
20
19
|
|
|
@@ -213,14 +212,35 @@ class HwpxOxmlNote:
|
|
|
213
212
|
|
|
214
213
|
@text.setter
|
|
215
214
|
def text(self, value: str) -> None:
|
|
216
|
-
"""Replace the note body text.
|
|
215
|
+
"""Replace the note body text.
|
|
216
|
+
|
|
217
|
+
The gold-shaped body (각주/미주 style paragraph with the leading
|
|
218
|
+
``<hp:autoNum>`` control real Hancom requires to render the note) is
|
|
219
|
+
preserved: only text content is replaced.
|
|
220
|
+
"""
|
|
217
221
|
sublist = self.element.find(f"{_HP}subList")
|
|
218
222
|
if sublist is None:
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
attrs = _default_sublist_attributes()
|
|
224
|
+
attrs["vertAlign"] = "TOP"
|
|
225
|
+
sublist = _append_child(self.element, f"{_HP}subList", attrs)
|
|
226
|
+
paragraphs = sublist.findall(f"{_HP}p")
|
|
227
|
+
template = paragraphs[0] if paragraphs else None
|
|
228
|
+
p_attrs = (
|
|
229
|
+
dict(template.attrib)
|
|
230
|
+
if template is not None
|
|
231
|
+
else {"id": "0", **_DEFAULT_PARAGRAPH_ATTRS}
|
|
232
|
+
)
|
|
233
|
+
first_run = template.find(f"{_HP}run") if template is not None else None
|
|
234
|
+
run_attrs = dict(first_run.attrib) if first_run is not None else {"charPrIDRef": "0"}
|
|
235
|
+
auto_num = (
|
|
236
|
+
first_run.find(f"{_HP}ctrl") if first_run is not None else None
|
|
237
|
+
)
|
|
238
|
+
for p in paragraphs:
|
|
221
239
|
sublist.remove(p)
|
|
222
|
-
paragraph = _append_child(sublist, f"{_HP}p",
|
|
223
|
-
run = _append_child(paragraph, f"{_HP}run",
|
|
240
|
+
paragraph = _append_child(sublist, f"{_HP}p", p_attrs)
|
|
241
|
+
run = _append_child(paragraph, f"{_HP}run", run_attrs)
|
|
242
|
+
if auto_num is not None:
|
|
243
|
+
run.append(auto_num)
|
|
224
244
|
t = _append_child(run, f"{_HP}t", {})
|
|
225
245
|
t.text = _sanitize_text(value)
|
|
226
246
|
self.paragraph.section.mark_dirty()
|
hwpx/oxml/paragraph.py
CHANGED
|
@@ -23,7 +23,6 @@ from ._document_primitives import (
|
|
|
23
23
|
_default_sublist_attributes,
|
|
24
24
|
_is_tab_control_element,
|
|
25
25
|
_object_id,
|
|
26
|
-
_paragraph_id,
|
|
27
26
|
_sanitize_text,
|
|
28
27
|
)
|
|
29
28
|
from .memo import HwpxOxmlNote
|
|
@@ -1183,6 +1182,61 @@ class HwpxOxmlParagraph:
|
|
|
1183
1182
|
# Footnote / Endnote helpers
|
|
1184
1183
|
# ------------------------------------------------------------------
|
|
1185
1184
|
|
|
1185
|
+
_NOTE_STYLE_NAMES = {"footNote": ("각주", "Footnote"), "endNote": ("미주", "Endnote")}
|
|
1186
|
+
_NOTE_STYLE_FALLBACK = {"footNote": ("15", "10", "3"), "endNote": ("16", "10", "3")}
|
|
1187
|
+
|
|
1188
|
+
def _note_style_refs(self, tag: str) -> tuple[str, str, str]:
|
|
1189
|
+
"""(styleIDRef, paraPrIDRef, charPrIDRef) for the note body paragraph.
|
|
1190
|
+
|
|
1191
|
+
Real Hancom puts note bodies on the 각주/미주 paragraph styles. Resolve
|
|
1192
|
+
by style name from the header so documents with re-numbered styles stay
|
|
1193
|
+
correct; fall back to the fixed template coordinates.
|
|
1194
|
+
"""
|
|
1195
|
+
|
|
1196
|
+
korean, english = self._NOTE_STYLE_NAMES[tag]
|
|
1197
|
+
document = self.section.document
|
|
1198
|
+
headers = cast(
|
|
1199
|
+
"Sequence[object]",
|
|
1200
|
+
getattr(document, "_headers", []) if document is not None else [],
|
|
1201
|
+
)
|
|
1202
|
+
for header in headers:
|
|
1203
|
+
styles = getattr(header, "_styles_element", None)
|
|
1204
|
+
container = cast(
|
|
1205
|
+
"ET.Element | None", styles() if callable(styles) else None
|
|
1206
|
+
)
|
|
1207
|
+
if container is None:
|
|
1208
|
+
continue
|
|
1209
|
+
for style in container:
|
|
1210
|
+
if style.get("name") == korean or style.get("engName") == english:
|
|
1211
|
+
return (
|
|
1212
|
+
style.get("id") or self._NOTE_STYLE_FALLBACK[tag][0],
|
|
1213
|
+
style.get("paraPrIDRef") or self._NOTE_STYLE_FALLBACK[tag][1],
|
|
1214
|
+
style.get("charPrIDRef") or self._NOTE_STYLE_FALLBACK[tag][2],
|
|
1215
|
+
)
|
|
1216
|
+
return self._NOTE_STYLE_FALLBACK[tag]
|
|
1217
|
+
|
|
1218
|
+
def _note_suffix_char(self, tag: str) -> str:
|
|
1219
|
+
"""The note suffix character from secPr, defaulting to ")"."""
|
|
1220
|
+
|
|
1221
|
+
properties = self.section._section_properties_element()
|
|
1222
|
+
if properties is not None:
|
|
1223
|
+
pr = properties.find(f"{_HP}{tag}Pr")
|
|
1224
|
+
if pr is not None:
|
|
1225
|
+
fmt = pr.find(f"{_HP}autoNumFormat")
|
|
1226
|
+
if fmt is not None and fmt.get("suffixChar"):
|
|
1227
|
+
return fmt.get("suffixChar", ")")
|
|
1228
|
+
return ")"
|
|
1229
|
+
|
|
1230
|
+
def _next_note_number(self, tag: str) -> int:
|
|
1231
|
+
"""Document-continuous note number, counted per note type."""
|
|
1232
|
+
|
|
1233
|
+
document = self.section.document
|
|
1234
|
+
sections = document.sections if document is not None else [self.section]
|
|
1235
|
+
count = 0
|
|
1236
|
+
for section in sections:
|
|
1237
|
+
count += sum(1 for _ in section.element.iter(f"{_HP}{tag}"))
|
|
1238
|
+
return count + 1
|
|
1239
|
+
|
|
1186
1240
|
def _add_note(
|
|
1187
1241
|
self,
|
|
1188
1242
|
tag: str,
|
|
@@ -1191,17 +1245,54 @@ class HwpxOxmlParagraph:
|
|
|
1191
1245
|
run_attributes: dict[str, str] | None = None,
|
|
1192
1246
|
char_pr_id_ref: str | int | None = None,
|
|
1193
1247
|
) -> HwpxOxmlNote:
|
|
1194
|
-
"""Insert a ``<hp:footNote>`` or ``<hp:endNote>`` element.
|
|
1248
|
+
"""Insert a ``<hp:footNote>`` or ``<hp:endNote>`` element.
|
|
1195
1249
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1250
|
+
Emits the real-Hancom shape (gold-reversed): the note element is
|
|
1251
|
+
wrapped in ``<hp:ctrl>`` inside a body run, carries ``number`` and
|
|
1252
|
+
``suffixChar``, and its body paragraph uses the 각주/미주 style with a
|
|
1253
|
+
leading ``<hp:autoNum>`` control — without these real Hancom does not
|
|
1254
|
+
render the note at all.
|
|
1255
|
+
"""
|
|
1256
|
+
|
|
1257
|
+
number = self._next_note_number(tag)
|
|
1258
|
+
suffix = self._note_suffix_char(tag)
|
|
1259
|
+
style_ref, para_pr_ref, note_char_ref = self._note_style_refs(tag)
|
|
1260
|
+
if char_pr_id_ref is not None:
|
|
1261
|
+
note_char_ref = str(char_pr_id_ref)
|
|
1262
|
+
|
|
1263
|
+
runs = self._run_elements()
|
|
1264
|
+
if run_attributes is None and runs:
|
|
1265
|
+
run = runs[-1]
|
|
1266
|
+
else:
|
|
1267
|
+
run = self._create_run_for_object(run_attributes, char_pr_id_ref=char_pr_id_ref)
|
|
1268
|
+
ctrl = _append_child(run, f"{_HP}ctrl", {})
|
|
1269
|
+
note_element = _append_child(
|
|
1270
|
+
ctrl,
|
|
1271
|
+
f"{_HP}{tag}",
|
|
1272
|
+
{
|
|
1273
|
+
"number": str(number),
|
|
1274
|
+
"suffixChar": str(ord(suffix[0])) if suffix else "41",
|
|
1275
|
+
"instId": _object_id(),
|
|
1276
|
+
},
|
|
1277
|
+
)
|
|
1278
|
+
sublist_attrs = _default_sublist_attributes()
|
|
1279
|
+
sublist_attrs["vertAlign"] = "TOP"
|
|
1280
|
+
sublist = _append_child(note_element, f"{_HP}subList", sublist_attrs)
|
|
1281
|
+
p_attrs = dict(_DEFAULT_PARAGRAPH_ATTRS)
|
|
1282
|
+
p_attrs.update({"id": "0", "paraPrIDRef": para_pr_ref, "styleIDRef": style_ref})
|
|
1200
1283
|
paragraph = _append_child(sublist, f"{_HP}p", p_attrs)
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1284
|
+
note_run = _append_child(paragraph, f"{_HP}run", {"charPrIDRef": note_char_ref})
|
|
1285
|
+
num_ctrl = _append_child(note_run, f"{_HP}ctrl", {})
|
|
1286
|
+
auto_num = _append_child(
|
|
1287
|
+
num_ctrl,
|
|
1288
|
+
f"{_HP}autoNum",
|
|
1289
|
+
{"num": str(number), "numType": "FOOTNOTE" if tag == "footNote" else "ENDNOTE"},
|
|
1290
|
+
)
|
|
1291
|
+
_append_child(
|
|
1292
|
+
auto_num,
|
|
1293
|
+
f"{_HP}autoNumFormat",
|
|
1294
|
+
{"type": "DIGIT", "userChar": "", "prefixChar": "", "suffixChar": suffix, "supscript": "0"},
|
|
1295
|
+
)
|
|
1205
1296
|
t = _append_child(note_run, f"{_HP}t", {})
|
|
1206
1297
|
t.text = _sanitize_text(text)
|
|
1207
1298
|
self.section.mark_dirty()
|
hwpx/tools/markdown_export.py
CHANGED
|
@@ -228,7 +228,7 @@ def _md_push_text(state: _MdParagraphState, cpr, text) -> None:
|
|
|
228
228
|
state.items.append((cpr, text))
|
|
229
229
|
|
|
230
230
|
|
|
231
|
-
def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars) -> None:
|
|
231
|
+
def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars, doc=None, notes_out=None) -> None:
|
|
232
232
|
for gc in child:
|
|
233
233
|
gctag = _local_name(gc)
|
|
234
234
|
if gctag == "fieldBegin" and gc.get("type") == "HYPERLINK":
|
|
@@ -236,6 +236,9 @@ def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars) -> No
|
|
|
236
236
|
state.link_url = gc.get("name", "")
|
|
237
237
|
elif gctag == "fieldEnd":
|
|
238
238
|
_md_flush_link(state, base_cp, chars)
|
|
239
|
+
elif gctag in ("footNote", "endNote"):
|
|
240
|
+
# 실한컴 계약: 각주/미주는 run 안 <hp:ctrl>에 래핑되어 있다.
|
|
241
|
+
_md_handle_note_child(gc, gctag, state, base_cp, chars, doc, notes_out)
|
|
239
242
|
|
|
240
243
|
|
|
241
244
|
def _md_handle_note_child(
|
|
@@ -271,8 +274,9 @@ def _p_element_to_md(p_el, doc, notes_out: list | None = None) -> str:
|
|
|
271
274
|
if child.text:
|
|
272
275
|
_md_push_text(state, cpr, child.text)
|
|
273
276
|
elif tag == "ctrl":
|
|
274
|
-
_md_handle_ctrl_child(child, state, base_cp, chars)
|
|
277
|
+
_md_handle_ctrl_child(child, state, base_cp, chars, doc, notes_out)
|
|
275
278
|
elif tag in ("footNote", "endNote"):
|
|
279
|
+
# 구식(5.4 이전 자사 방출) 호환: run 직속 각주도 계속 읽는다.
|
|
276
280
|
_md_handle_note_child(child, tag, state, base_cp, chars, doc, notes_out)
|
|
277
281
|
|
|
278
282
|
_md_flush_items(state, base_cp, chars)
|
hwpx/tools/text_extractor.py
CHANGED
|
@@ -483,6 +483,20 @@ class TextExtractor:
|
|
|
483
483
|
annotations: Optional[AnnotationOptions],
|
|
484
484
|
preserve_breaks: bool,
|
|
485
485
|
) -> None:
|
|
486
|
+
# 실한컴 계약: 각주/미주는 <hp:ctrl> 래핑으로 나타난다 — annotations
|
|
487
|
+
# 유무와 무관하게 노트 분기로 위임한다(구식 run 직속도 별도 분기 유지).
|
|
488
|
+
for note_tag in ("footNote", "endNote"):
|
|
489
|
+
note = _first_child_by_local(element, note_tag)
|
|
490
|
+
if note is not None:
|
|
491
|
+
self._handle_note(
|
|
492
|
+
note,
|
|
493
|
+
fragments,
|
|
494
|
+
note_tag,
|
|
495
|
+
annotations=annotations,
|
|
496
|
+
preserve_breaks=preserve_breaks,
|
|
497
|
+
)
|
|
498
|
+
return
|
|
499
|
+
|
|
486
500
|
if annotations is None:
|
|
487
501
|
return
|
|
488
502
|
|
|
@@ -52,11 +52,11 @@ hwpx/oxml/document.py,sha256=0Ml-t2fwa4f2xMfu5jbkOoh_KDKCCf3gNpaYICRV-KY,1172
|
|
|
52
52
|
hwpx/oxml/document_parts.py,sha256=NSlCmXKfItIaTEYruu6zUh-XHZRRDI9XZ2U-5rD2UUU,49240
|
|
53
53
|
hwpx/oxml/header.py,sha256=4LtTDPe7YW0o_4jPv3Rd-vyv5suRfebecxtTWbF65Uw,50011
|
|
54
54
|
hwpx/oxml/header_part.py,sha256=JBOw3Ljkt0PCYYKP7IlFpxA1ZRbrBHqDo8hjvE-yMsk,51409
|
|
55
|
-
hwpx/oxml/memo.py,sha256=
|
|
55
|
+
hwpx/oxml/memo.py,sha256=jTtZIrR7LwWn_vvFN6Fr9yX2eSJfMPh8WnNW1DhD2Rs,10283
|
|
56
56
|
hwpx/oxml/namespaces.py,sha256=c7JfdOdJbzrhyHvjbxoeeeloRE3xPuoB7v3YI8SmKDk,6524
|
|
57
57
|
hwpx/oxml/numbering.py,sha256=9a0ARGW1DkKsTF7mEEXM4FA5loQmzIBuqE4APA8UFIQ,669
|
|
58
58
|
hwpx/oxml/objects.py,sha256=8ErDXWvkjLm3FTsx0Z_IovUXbiv2uc-j0B5Y2-OtihA,20864
|
|
59
|
-
hwpx/oxml/paragraph.py,sha256=
|
|
59
|
+
hwpx/oxml/paragraph.py,sha256=btJlX6WScB9gjKRu2uAs1qAdEQYar2BuKdSwNfdRm9U,53408
|
|
60
60
|
hwpx/oxml/parser.py,sha256=pIfyNdW3WdFkCcE8JFY7hn1fobRbMngzfhVZICvWeVs,2937
|
|
61
61
|
hwpx/oxml/run.py,sha256=F939J1W6zQjr-JQ1Z8-nKh2s7NQ_0Okov4QJAEyw_5M,15503
|
|
62
62
|
hwpx/oxml/schema.py,sha256=ElR3_IIhhPPZEqJtNKMNCHA-VFVLdhL454W_4spuvlc,1284
|
|
@@ -83,7 +83,7 @@ hwpx/tools/idempotence.py,sha256=kRZBWsPJFVprYRJkkxn3DHjrT6qX2nK7KvhmdgJ4JpA,501
|
|
|
83
83
|
hwpx/tools/ir_equality.py,sha256=NIjq33-4q7iTo_asv7WUvh_-EfB1JZMTwbLfYIw2c5w,4765
|
|
84
84
|
hwpx/tools/layout_preview.py,sha256=cHlBUEdMn_ac-hNFDUgF5r2PMM0cTw65l7sqq7Li8NM,22702
|
|
85
85
|
hwpx/tools/mail_merge.py,sha256=8kGlSk2GQL7cFxZgg6P_bKisj1zVUWy8qh8AXIZLK_w,18611
|
|
86
|
-
hwpx/tools/markdown_export.py,sha256=
|
|
86
|
+
hwpx/tools/markdown_export.py,sha256=_qhOqnYg2tFQa4-EuXyFdJvkc_-9x6ln9lx-MeqtNeY,20495
|
|
87
87
|
hwpx/tools/object_finder.py,sha256=ovXlbuPiQFdGzNSU08SuEzNFoeYcIb7GfF6vB9MRwBE,13800
|
|
88
88
|
hwpx/tools/package_reconcile.py,sha256=y1Hl7hbPh4YaV59LTdDLzQwgn4g1qEnFmSjmajnrEbA,2416
|
|
89
89
|
hwpx/tools/package_validator.py,sha256=fYkJevV_soQuM8LEUQMkwYaTy2k2HV4VWBwfIXFt29Y,33306
|
|
@@ -98,16 +98,16 @@ hwpx/tools/table_cleanup.py,sha256=0_f6NnvNp3QD4owKd_bRX6FZbeUmoQC7a4_VGzF2SCE,1
|
|
|
98
98
|
hwpx/tools/table_navigation.py,sha256=rtbrWFKpJhqC3LD0ZXImyHgjmDR2hjHCFy3_S-qNBwA,16479
|
|
99
99
|
hwpx/tools/template_analyzer.py,sha256=iH7egGRYNb8UesDB1TJ4tvW41-mlmv5Ap3VacqJFSlE,21355
|
|
100
100
|
hwpx/tools/text_extract_cli.py,sha256=BmsDAwNXpDPhEayb9ez2ORtGNzPd_Xxduy4_cLXhnUw,2188
|
|
101
|
-
hwpx/tools/text_extractor.py,sha256=
|
|
101
|
+
hwpx/tools/text_extractor.py,sha256=6Wtk3av69z848ZJAEh-fV4TU2BkAwk3qKJpIyUkYT_4,26122
|
|
102
102
|
hwpx/tools/toc_author.py,sha256=eS1Zm5OBoonuD_H92JxcN2o276d5XmcJDqpfscd4wSg,15445
|
|
103
103
|
hwpx/tools/toc_fidelity.py,sha256=rvoKH8QJ65WNVrxS0d-i2AtODvBy-4O8NRudfQ244v4,19890
|
|
104
104
|
hwpx/tools/validator.py,sha256=U856izL9NcJZOiKDYoCpwOSaFNQ2Un8Jn4pzL20A96Q,7100
|
|
105
105
|
hwpx/tools/_schemas/header.xsd,sha256=mJXuFMuHGT1JnFFaluUpYUglwjMCNlfbFCRVM26eHXE,664
|
|
106
106
|
hwpx/tools/_schemas/section.xsd,sha256=MgvavVHG05RDfUnVPxVU10H4FQOja5ON04_m9Uk_m7E,522
|
|
107
|
-
python_hwpx-5.
|
|
108
|
-
python_hwpx-5.
|
|
109
|
-
python_hwpx-5.
|
|
110
|
-
python_hwpx-5.
|
|
111
|
-
python_hwpx-5.
|
|
112
|
-
python_hwpx-5.
|
|
113
|
-
python_hwpx-5.
|
|
107
|
+
python_hwpx-5.5.0.dist-info/licenses/LICENSE,sha256=_ubz4wv-BkkT3l3gu-QuH7JGeVjuRYGZoZK95eNsCHU,9688
|
|
108
|
+
python_hwpx-5.5.0.dist-info/licenses/NOTICE,sha256=auRgKYGdrOgWrj4kZfAuXQnqgDnx5myI9TI17IztIzQ,3466
|
|
109
|
+
python_hwpx-5.5.0.dist-info/METADATA,sha256=8nE4IAXunnxWpqeKPO01ZzKLHA1mXKqZUb034C9gB9k,13162
|
|
110
|
+
python_hwpx-5.5.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
111
|
+
python_hwpx-5.5.0.dist-info/entry_points.txt,sha256=JUKRxbly9UaeHV7YzOea23y8IiqSTcrhUlooP3fS_Zc,405
|
|
112
|
+
python_hwpx-5.5.0.dist-info/top_level.txt,sha256=R1iToqDh80Nf2oQhRjTN0rbN2X6kyDUizIocZjkhuxc,5
|
|
113
|
+
python_hwpx-5.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|