python-hwpx 2.2__tar.gz → 2.3__tar.gz

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.
Files changed (53) hide show
  1. {python_hwpx-2.2 → python_hwpx-2.3}/PKG-INFO +1 -1
  2. {python_hwpx-2.2 → python_hwpx-2.3}/pyproject.toml +1 -1
  3. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/document.py +78 -38
  4. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/PKG-INFO +1 -1
  5. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_document_formatting.py +42 -16
  6. {python_hwpx-2.2 → python_hwpx-2.3}/LICENSE +0 -0
  7. {python_hwpx-2.2 → python_hwpx-2.3}/README.md +0 -0
  8. {python_hwpx-2.2 → python_hwpx-2.3}/setup.cfg +0 -0
  9. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/__init__.py +0 -0
  10. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/data/Skeleton.hwpx +0 -0
  11. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/document.py +0 -0
  12. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/opc/package.py +0 -0
  13. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/opc/xml_utils.py +0 -0
  14. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/__init__.py +0 -0
  15. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/body.py +0 -0
  16. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/common.py +0 -0
  17. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/header.py +0 -0
  18. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/header_part.py +0 -0
  19. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/memo.py +0 -0
  20. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/paragraph.py +0 -0
  21. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/parser.py +0 -0
  22. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/schema.py +0 -0
  23. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/section.py +0 -0
  24. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/table.py +0 -0
  25. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/oxml/utils.py +0 -0
  26. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/package.py +0 -0
  27. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/py.typed +0 -0
  28. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/templates.py +0 -0
  29. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/__init__.py +0 -0
  30. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/_schemas/header.xsd +0 -0
  31. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/_schemas/section.xsd +0 -0
  32. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/object_finder.py +0 -0
  33. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/text_extractor.py +0 -0
  34. {python_hwpx-2.2 → python_hwpx-2.3}/src/hwpx/tools/validator.py +0 -0
  35. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/SOURCES.txt +0 -0
  36. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  37. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  38. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/requires.txt +0 -0
  39. {python_hwpx-2.2 → python_hwpx-2.3}/src/python_hwpx.egg-info/top_level.txt +0 -0
  40. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_coverage_targets.py +0 -0
  41. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_document_context_manager.py +0 -0
  42. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_document_save_api.py +0 -0
  43. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_inline_models.py +0 -0
  44. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_integration_hwpx_compatibility.py +0 -0
  45. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_memo_and_style_editing.py +0 -0
  46. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_opc_package.py +0 -0
  47. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_oxml_parsing.py +0 -0
  48. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_packaging_py_typed.py +0 -0
  49. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_repr_snapshots.py +0 -0
  50. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_section_headers.py +0 -0
  51. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_tables_default_border.py +0 -0
  52. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_text_extractor_annotations.py +0 -0
  53. {python_hwpx-2.2 → python_hwpx-2.3}/tests/test_version_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 2.2
3
+ Version: 2.3
4
4
  Summary: Hancom HWPX 패키지를 로드하고 편집하기 위한 Python 유틸리티 모음
5
5
  Author: python-hwpx Maintainers
6
6
  License: Non-Commercial License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "2.2"
7
+ version = "2.3"
8
8
  description = "Hancom HWPX 패키지를 로드하고 편집하기 위한 Python 유틸리티 모음"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -1782,33 +1782,71 @@ class HwpxOxmlTable:
1782
1782
  def rows(self) -> list[HwpxOxmlTableRow]:
1783
1783
  return [HwpxOxmlTableRow(row, self) for row in self.element.findall(f"{_HP}tr")]
1784
1784
 
1785
- def _build_cell_grid(self) -> dict[tuple[int, int], HwpxTableGridPosition]:
1786
- mapping: dict[tuple[int, int], HwpxTableGridPosition] = {}
1787
- for row in self.element.findall(f"{_HP}tr"):
1788
- for cell_element in row.findall(f"{_HP}tc"):
1789
- wrapper = HwpxOxmlTableCell(cell_element, self, row)
1790
- start_row, start_col = wrapper.address
1791
- span_row, span_col = wrapper.span
1792
- for logical_row in range(start_row, start_row + span_row):
1793
- for logical_col in range(start_col, start_col + span_col):
1794
- key = (logical_row, logical_col)
1795
- existing = mapping.get(key)
1796
- entry = HwpxTableGridPosition(
1785
+ def _build_cell_grid(self) -> dict[tuple[int, int], HwpxTableGridPosition]:
1786
+ mapping: dict[tuple[int, int], HwpxTableGridPosition] = {}
1787
+
1788
+ def _is_deactivated_cell(
1789
+ cell: HwpxOxmlTableCell, span: tuple[int, int]
1790
+ ) -> bool:
1791
+ span_row, span_col = span
1792
+ if span_row != 1 or span_col != 1:
1793
+ return False
1794
+ if cell.width != 0 or cell.height != 0:
1795
+ return False
1796
+ for text_element in cell.element.findall(f".//{_HP}t"):
1797
+ if text_element.text:
1798
+ return False
1799
+ return True
1800
+
1801
+ for row in self.element.findall(f"{_HP}tr"):
1802
+ for cell_element in row.findall(f"{_HP}tc"):
1803
+ wrapper = HwpxOxmlTableCell(cell_element, self, row)
1804
+ start_row, start_col = wrapper.address
1805
+ span_row, span_col = wrapper.span
1806
+ wrapper_span = (span_row, span_col)
1807
+ wrapper_is_deactivated = _is_deactivated_cell(wrapper, wrapper_span)
1808
+ for logical_row in range(start_row, start_row + span_row):
1809
+ for logical_col in range(start_col, start_col + span_col):
1810
+ key = (logical_row, logical_col)
1811
+ existing = mapping.get(key)
1812
+ entry = HwpxTableGridPosition(
1797
1813
  row=logical_row,
1798
1814
  column=logical_col,
1799
1815
  cell=wrapper,
1800
1816
  anchor=(start_row, start_col),
1801
- span=(span_row, span_col),
1802
- )
1803
- if (
1804
- existing is not None
1805
- and existing.cell.element is not wrapper.element
1806
- ):
1807
- raise ValueError(
1808
- "table grid contains overlapping cell spans"
1809
- )
1810
- mapping[key] = entry
1811
- return mapping
1817
+ span=(span_row, span_col),
1818
+ )
1819
+ if (
1820
+ existing is not None
1821
+ and existing.cell.element is not wrapper.element
1822
+ ):
1823
+ existing_span = existing.span
1824
+ existing_spans_multiple = (
1825
+ existing_span[0] != 1 or existing_span[1] != 1
1826
+ )
1827
+ wrapper_spans_multiple = (
1828
+ wrapper_span[0] != 1 or wrapper_span[1] != 1
1829
+ )
1830
+ existing_is_deactivated = _is_deactivated_cell(
1831
+ existing.cell, existing_span
1832
+ )
1833
+
1834
+ if (
1835
+ wrapper_is_deactivated
1836
+ and existing_spans_multiple
1837
+ ):
1838
+ continue
1839
+ if (
1840
+ existing_is_deactivated
1841
+ and wrapper_spans_multiple
1842
+ ):
1843
+ mapping[key] = entry
1844
+ continue
1845
+ raise ValueError(
1846
+ "table grid contains overlapping cell spans"
1847
+ )
1848
+ mapping[key] = entry
1849
+ return mapping
1812
1850
 
1813
1851
  def _grid_entry(self, row_index: int, col_index: int) -> HwpxTableGridPosition:
1814
1852
  if row_index < 0 or col_index < 0:
@@ -2096,21 +2134,23 @@ class HwpxOxmlTable:
2096
2134
  if cell.element is not target.element:
2097
2135
  removal_elements.add(cell.element)
2098
2136
 
2099
- if not removal_elements and target.span == (new_row_span, new_col_span):
2100
- return target
2101
-
2102
- for element in removal_elements:
2103
- row_element = element_to_row.get(element)
2104
- if row_element is not None:
2105
- try:
2106
- row_element.remove(element)
2107
- except ValueError:
2108
- continue
2109
-
2110
- target.set_span(new_row_span, new_col_span)
2111
- target.set_size(total_width or target.width, total_height or target.height)
2112
- self.mark_dirty()
2113
- return target
2137
+ if not removal_elements and target.span == (new_row_span, new_col_span):
2138
+ return target
2139
+
2140
+ for element in removal_elements:
2141
+ row_element = element_to_row.get(element)
2142
+ if row_element is None:
2143
+ continue
2144
+ wrapper = HwpxOxmlTableCell(element, self, row_element)
2145
+ wrapper.set_span(1, 1)
2146
+ wrapper.set_size(0, 0)
2147
+ for text_element in element.findall(f".//{_HP}t"):
2148
+ text_element.text = ""
2149
+
2150
+ target.set_span(new_row_span, new_col_span)
2151
+ target.set_size(total_width or target.width, total_height or target.height)
2152
+ self.mark_dirty()
2153
+ return target
2114
2154
 
2115
2155
  @dataclass
2116
2156
  class HwpxOxmlParagraph:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 2.2
3
+ Version: 2.3
4
4
  Summary: Hancom HWPX 패키지를 로드하고 편집하기 위한 Python 유틸리티 모음
5
5
  Author: python-hwpx Maintainers
6
6
  License: Non-Commercial License
@@ -413,28 +413,54 @@ def test_table_cell_text_marks_cell_dirty_attribute() -> None:
413
413
  assert table.cell(0, 0).element.get("dirty") == "1"
414
414
 
415
415
 
416
- def test_table_merge_cells_updates_spans_and_structure() -> None:
416
+ def test_table_merge_cells_updates_spans_and_structure() -> None:
417
417
  section_element = ET.Element(f"{HS}sec")
418
418
  section = HwpxOxmlSection("section0.xml", section_element)
419
419
  manifest = ET.Element("manifest")
420
420
  root = HwpxOxmlDocument(manifest, [section], [])
421
421
  document = HwpxDocument(cast(HwpxPackage, object()), root)
422
422
 
423
- table = document.add_table(3, 3, section=section)
424
- initial_width = table.cell(0, 0).width
425
- initial_height = table.cell(0, 0).height
426
- section.reset_dirty()
427
-
428
- merged = table.merge_cells(0, 0, 1, 1)
429
-
430
- assert merged.span == (2, 2)
431
- assert merged.width >= initial_width
432
- assert merged.height >= initial_height
433
- assert table.cell(0, 1).element is merged.element
434
- assert table.cell(1, 0).element is merged.element
435
- assert len(table.rows[0].cells) == 2
436
- assert len(table.rows[1].cells) == 1
437
- assert section.dirty is True
423
+ table = document.add_table(3, 3, section=section)
424
+ initial_width = table.cell(0, 0).width
425
+ initial_height = table.cell(0, 0).height
426
+ table.set_cell_text(0, 1, "Top Right")
427
+ table.set_cell_text(1, 0, "Bottom Left")
428
+ table.set_cell_text(1, 1, "Bottom Right")
429
+ section.reset_dirty()
430
+
431
+ merged = table.merge_cells(0, 0, 1, 1)
432
+
433
+ physical_by_address = {
434
+ cell.address: cell for row in table.rows for cell in row.cells
435
+ }
436
+ covered_top_right = physical_by_address[(0, 1)]
437
+ covered_bottom_left = physical_by_address[(1, 0)]
438
+ covered_bottom_right = physical_by_address[(1, 1)]
439
+
440
+ assert merged.span == (2, 2)
441
+ assert merged.width >= initial_width
442
+ assert merged.height >= initial_height
443
+ assert table.cell(0, 1).element is merged.element
444
+ assert table.cell(1, 0).element is merged.element
445
+ assert table.cell(1, 1).element is merged.element
446
+ assert len(table.rows[0].cells) == 3
447
+ assert len(table.rows[1].cells) == 3
448
+ assert covered_top_right.element is not merged.element
449
+ assert covered_top_right.span == (1, 1)
450
+ assert covered_top_right.width == 0
451
+ assert covered_top_right.height == 0
452
+ assert covered_top_right.text == ""
453
+ assert covered_bottom_left.element is not merged.element
454
+ assert covered_bottom_left.span == (1, 1)
455
+ assert covered_bottom_left.width == 0
456
+ assert covered_bottom_left.height == 0
457
+ assert covered_bottom_left.text == ""
458
+ assert covered_bottom_right.element is not merged.element
459
+ assert covered_bottom_right.span == (1, 1)
460
+ assert covered_bottom_right.width == 0
461
+ assert covered_bottom_right.height == 0
462
+ assert covered_bottom_right.text == ""
463
+ assert section.dirty is True
438
464
 
439
465
 
440
466
  def test_table_merge_cells_rejects_partial_overlap() -> None:
File without changes
File without changes
File without changes
File without changes
File without changes