ADR-004: Element-level API β Run / Paragraph / Cell / Section
λ§₯λ½ (Context)
v3 μ ADR-003 μΌλ‘ λ€μ€ λ¬Έμκ° κΉλν μ μ°©νκ³ , κ° Document μ 7κ° μ»¬λ μ
(fields, bookmarks, hyperlinks, images, paragraphs, styles, tables) μ΄ μ리μ‘μ μ΅λλ€. κ·Έλ¬λ 컬λ μ
μ΄ λ°ννλ κ°λ³ element λ€μ μ¬μ ν λΉμ½ν©λλ€:
doc.paragraphs[0]βParagraphβ νμ¬text,indexμ λλ§doc.tables[0]βTableβcell(r, c)μ λλ§doc.tables[0].cell(1, 1)βCellβtext,border,fillsetter κ° μ½μλ§ μμ (migration v1βv2 κ°μ΄λ μ°Έμ‘°)Run(κΈμ λ¨μ νμ λ¨μ) ν΄λμ€ μμ²΄κ° μμ
κ²°κ³Όμ μΌλ‘ μ¬μ©μκ° βμ΄ λ¨λ½μ μ λ ¬μ λ°κΎΈκ³ κΈμ μλ§ λ°κΎΈκ³ μΆλ€β κ°μ μμ
μ ν λ μ¬μ ν doc.actions.X.run() raw λ‘ λ΄λ €κ°μΌ ν©λλ€. v3 μ doc.X μΌκ΄μ±μ΄ element λ 벨μμ κΉ¨μ§λ μ
μ
λλ€.
xlwings μ λΉκ΅:
# xlwings β element κΉμ§ ν API
sheet.range("A1:B5").value = [[1,2],[3,4]]
sheet.range("A1").color = (255, 200, 200)
sheet.range("A1").font.bold = Truehwpapi κ° λλ¬ν΄μΌ ν λΉμ·ν μμ€:
# λͺ©ν (v3.x β μ΄ ADR)
para = doc.paragraphs[0]
para.text = "μ λ³Έλ¬Έ"
para.parashape.align = "center"
for run in para.runs:
run.charshape.bold = True
cell = doc.tables[0].cell(1, 1)
cell.text = "Q1"
cell.fill = "#FFE0E0"
cell.border = {"top": "double", "bottom": "solid"}κ²°μ (Decision)
4 μ’ element ν΄λμ€λ₯Ό μ μ μ μ + κ°μ μκΈ° μ± μ λ²μ λͺ νν. λͺ¨λ element λ ADR-003 μ doc-activate ν¨ν΄μ κ·Έλλ‘ λ°λ¦ β element μ λ©μλ μ§μ μ owning doc μλ νμ±ν.
Element λ§€νΈλ¦μ€
| Element | 컬λ μ μ§μ | μ± μ |
|---|---|---|
Paragraph |
doc.paragraphs[i] |
λ¨λ½ ν μ€νΈ, ParaShape, runs 컬λ μ |
Run |
paragraph.runs[i] |
ν κΈμνμ ꡬκ°μ text, CharShape |
Table |
doc.tables[i] |
rows/cols, cell(r,c), ν λ¨μ BorderFill |
Cell |
table.cell(r, c) |
text, fill, border, charshape, parashape |
Section (보λ₯) |
doc.sections[i] |
νμ΄μ§ μ€μ , 머리λ§/κΌ¬λ¦¬λ§ (v3.1) |
νλ©΄ (κ° element μ ν΅μ¬ surface)
class Paragraph:
index # λ¨λ½ λ²νΈ (0-based)
text # r/w property
parashape # ParaShape (r/w)
charshape # λ¨λ½ μμ κΈμμ CharShape (r/w convenience)
runs # RunCollection
insert_text(s) # μ΄ λ¨λ½ λμ μΆκ°
delete() # μ΄ λ¨λ½ μ κ±°
activate() # 컀μλ₯Ό μ΄ λ¨λ½μΌλ‘
class Run:
paragraph # owning Paragraph
text # r/w
charshape # CharShape (r/w)
start, end # λ¨λ½ μμ κΈμ μμΉ λ²μ
class Table:
index, rows, cols
cell(r, c) β Cell
iter_cells() # row-major
delete()
border_fill # ν μ 체 BorderFill
activate()
class Cell:
table # owning Table
row, col, address (e.g. "B2")
text # r/w
charshape / parashape # r/w
fill # str | Color | None β μ
λ°°κ²½
border # dict ν setter ({"top": "solid", ...})
width / height # r/w (HWPUNIT)
activate() # μ΄ μ
λ‘ μ»€μ μ΄λ
doc-activate ν¨ν΄ μ μ©
λͺ¨λ element λ owning doc μ reference λ₯Ό κ°κ³ , μκΈ° λ©μλ/setter μ§μ μ doc.activate() λ₯Ό νΈμΆν©λλ€ (ADR-003 μ _DocActions μ λμΌ ν¨ν΄):
class Paragraph:
def __init__(self, doc, index):
self._doc = doc
self._index = index
@property
def text(self):
self._doc.activate()
# ... GetTextFile + λ²μ μΆμΆ
return ...
@text.setter
def text(self, value):
self._doc.activate()
# ... select range + insert_textλ€μ€ λ¬Έμ νκ²½μμ for p in doc1.paragraphs: p.text μ for p in doc2.paragraphs: p.text κ° μλ doc-switching λλ©΄μ μμ νκ² λμ.
CharShape / ParaShape β κΈ°μ‘΄ ParameterSet μ¬μ¬μ©
v2 μμ μ΄λ―Έ hwpapi.low.parametersets.CharShape / ParaShape κ° μμ. element μ .charshape / .parashape λ μ΄λ€μ μΈμ€ν΄μ€λ₯Ό λ°ν/ν λΉ. μ¬μ©μλ μ΅μν dict-like μ κ·Ό:
cs = run.charshape # CharShape μΈμ€ν΄μ€
cs.bold = True
cs.height = 1400
run.charshape = cs # setter β μ μ©
# λλ dict νλ°©
run.charshape = {"bold": True, "height": 1400}μ¬μ©μ λ©ν λͺ¨λΈ
App (process)
βββ Document (per-doc)
βββ 컬λ μ
(fields, bookmarks, ...)
βββ paragraphs : Collection[Paragraph]
β βββ runs : Collection[Run]
βββ tables : Collection[Table]
βββ cell(r, c) : Cell
κ° λ μ΄μ΄λ μκΈ° μ± μλ§ β App μ lifecycle, Document λ doc-scoped ops, Element λ element-scoped ops.
λ§μ΄κ·Έλ μ΄μ (Phases)
Phase 1 β Paragraph + Run
Paragraph.textgetter/setterParagraph.parashapegetter/setterParagraph.runs(RunCollection)Run.text,Run.charshapegetter/setter- ν
μ€νΈ:
tests/test_paragraph.py,tests/test_run.py
Phase 2 β Table + Cell
Table.cell(r, c)κ°Cellλ°νCell.text,Cell.fill,Cell.border(border dict ν)Cell.charshape,Cell.parashapeCell.activate()κ° μ»€μλ₯Ό μ λ‘- ν
μ€νΈ:
tests/test_table.py,tests/test_cell.py
Phase 3 β RunCollection + iter_cells
paragraph.runsμ iteration / μΈλ±μ±Table.iter_cells()row-major + filter- ν μ€νΈ μΆκ°
Phase 4 β Section (보λ₯)
- νμ΄μ§ μ€μ , 머리λ§/κΌ¬λ¦¬λ§ β v3.1 λλ λ³λ ADR
μ¬μ© μ
λ¨λ½ / Run
doc = app.docs.open("report.hwp")
# 1λ¨λ½ ν
μ€νΈμ μ λ ¬
para = doc.paragraphs[0]
para.text = "1λΆκΈ° λ³΄κ³ "
para.parashape = {"align": "center", "line_spacing": 180}
# μμ μλ λͺ¨λ run μ κ΅΅κ²
for run in para.runs:
run.charshape = {"bold": True}
# λλ ν λ¨λ½ μμ μΌλΆλ§
para.runs[0].charshape = {"text_color": "#E74C3C"}Table / Cell
doc.insert_table(rows=4, cols=4)
table = doc.tables[-1] # λ°©κΈ λ§λ ν
table.cell(0, 0).text = "μ ν"
table.cell(0, 1).text = "Q1"
table.cell(0, 2).text = "Q2"
table.cell(0, 3).text = "Q3"
# ν€λ μ
μΌκ΄ μμ
for c in range(4):
cell = table.cell(0, c)
cell.fill = "#34495E"
cell.charshape = {"bold": True, "text_color": "#FFFFFF"}
# 첫 μ΄λ§ κ΅΅μ ν
λ리
for r in range(1, 4):
table.cell(r, 0).border = {"right": "double"}λ€μ€ λ¬Έμ + element
src = app.docs.open("source.hwp")
dst = app.docs.add()
# doc-activate μλ β element κ° owning doc μΌλ‘ μλ μ ν
for para in src.paragraphs:
if para.text.startswith("β‘"):
# μ΄ μμ μ src νμ±, src.paragraphs[i].text λ μ μ
pass
# dst μμ
β μλ μ ν
for row in [["A", "B"], ["C", "D"]]:
dst.insert_table(rows=1, cols=2)
for c, val in enumerate(row):
dst.tables[-1].cell(0, c).text = valκ²°κ³Ό (Consequences)
κΈμ
- API μΌκ΄μ± β
doc.X,paragraph.X,cell.Xλͺ¨λ κ°μ ν¨ν΄ - λ€μ€ λ¬Έμ μμ β element λ doc-activate μλ
raw actionμμ‘΄λ κ°μ β Cell μμ λ± μΌμ μμ μ΄ element λ©μλλ‘ κ°λ₯- xlwings/openpyxl μ¬μ©μ μΉμ β sheet.range, cell.font λ±κ³Ό λν
- ν μ€νΈ κ°λ₯ β Mock μΌλ‘ element λ¨μ κ²μ¦
λΆμ / μν
- ν΄λμ€ μ μ¦κ° β Paragraph/Run/Table/Cell 4κ° μ κ·. 컬λ μ ν¬ν¨ 8κ° (RunCollection, CellCollection λ) μ΄λ―Έ λμ΄λ¨.
- HWP μ element μΈλ±μ± λΆμμ β λ¬Έμ νΈμ§ μ€ paragraph index κ° λ°λ¦΄ μ μμ. cell λ ν ν/μ΄ μΆκ° μ λ³λ. μμ μ μλ³μκ° νμν κ²½μ° μ¬μ©μκ° reload ν΄μΌ ν¨ (xlwings λ λμΌ).
- CharShape/ParaShape μ process-wide pset λ¬Έμ β element λ§λ€ μκΈ° pset μ 보μ νμ§ μκ³ process λ 벨 곡μ . doc-activate μ κ²°ν©ν΄ μμ νλ, λμμ±μ μ¬μ ν 보μ₯ μ λ¨ (Python GIL κ°μ ).
- Phase 1/2 ꡬν λΉμ© β κ° element μ λν΄ ν μ€νΈ λ²μ μΆμ , selection κ΄λ¦¬, charshape μ μ©/μ½κΈ° μΈνλΌ νμ. μ½ 600~1000 LOC μΆμ .
λ―Έν΄κ²° μ§λ¬Έ (Open questions)
- Run μ κ²½κ³ μ μ β HWP μμ βλμΌ charshape μ μ°μ κΈμβ κ° run μ μμ°μ€λ¬μ΄ μ μμ΄μ§λ§, λ¨λ½ μμμ μ΄λ₯Ό ν¨μ¨μ μΌλ‘ μννλ API κ° μλμ§ κ²μ¦ νμ. μμΌλ©΄
paragraph.textλ₯Ό μΊλ¦ν° λ¨μλ‘ μ€μΊνλ©° charshape λΉκ΅λ‘ κ΅¬κ° κ²°μ (λλ¦Ό). - Cell.border μ dict ν μ€κ³ β
{"top": "solid"}κ° λ¨μΌ λ³,{"all": "solid"}κ° 4λ³ μΌκ΄,{"outer": "solid", "inner": "dot"}κ°μ alias κΉμ§ μ§μν μ§. - Element μ λμΌμ± (
==) β κ°μ νμ κ°μ μ μ λ λ² κ°μ Έμ¨Cellλ κ°κ°==μΈκ°? row/col λΉκ΅? raw COM νΈλ€ λΉκ΅? - Section / Header / Footer β μ΄λ² ADR λ²μμ ν¬ν¨ μ¬λΆ. λΆλ¦¬ (v3.1) κΆμ₯.
- iter λ°©μμ μμ μ± β
for p in doc.paragraphs:μ€μ λ¨λ½μ΄ μΆκ°/μμ λλ©΄? generator-based μΌλ‘ lazy νκ² κ°μ§, snapshot μΌλ‘ κ³ μ ν μ§.
μ°Έκ³
- ADR-003: Multi-document redesign β μ΄ ADR μ ν λ (doc-activate ν¨ν΄, _DocActions proxy)
- v2βv3 migration κ°μ΄λ
- xlwings Range/Cell API
- openpyxl Cell API