Skip to content

Commit f4f923c

Browse files
authored
Merge pull request #28 from airmang:chore/license-metadata-alignment
Chore/license metadata alignment
2 parents 7b68c27 + e9cb9a2 commit f4f923c

12 files changed

Lines changed: 875 additions & 14 deletions

CHANGELOG.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22

33
모든 중요한 변경 사항은 이 문서에 기록됩니다. 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)[Semantic Versioning](https://semver.org/lang/ko/)을 따릅니다.
44

5-
## [Unreleased] - 2026-04-01
5+
## [2.9.0] - 2026-04-02
6+
### 추가
7+
- `HwpxDocument.get_table_map()`, `find_cell_by_label()`, `fill_by_path()`를 추가해 HWPX 양식/템플릿 표를 문서 순서 기반으로 탐색하고 채울 수 있게 했습니다.
8+
- `hwpx.tools.table_navigation` 모듈을 추가해 엔진 레벨에서 재사용 가능한 표 탐색, 라벨 정규화, 방향 이동, 배치 채우기 helper를 공개했습니다.
9+
10+
### 변경
11+
- 라벨 매칭이 공백 축약, 대소문자 무시, 후행 콜론 허용 규칙을 따르도록 정규화 로직을 추가했습니다.
12+
- 표 자동화 API에 대한 회귀 테스트와 README/API 레퍼런스 문서를 추가했습니다.
13+
14+
## [2.8.3] - 2026-03-10
615
### 변경
7-
- `hp:tab``ctrl id="tab"` 지원을 README와 usage 문서에 반영했습니다.
8-
- `Paragraph.text`, `TextExtractor`, 텍스트/HTML/Markdown exporter가 탭 의미를 `\t`로 보존한다는 점을 문서화했습니다.
9-
- `preserve_breaks` 옵션이 탭/줄바꿈 평탄화 여부를 제어한다는 설명을 보강했습니다.
16+
- 저장소와 배포 메타데이터의 라이선스 표기를 실제 `LICENSE` 파일과 일치하도록 정렬했습니다.
17+
- `pyproject.toml`을 PEP 639 방식의 `LicenseRef-python-hwpx-NonCommercial` + `license-files` 구성으로 갱신하고, 잘못된 MIT 분류자를 제거했습니다.
18+
- README 라이선스 배지/섹션을 커스텀 비상업적 라이선스 기준으로 수정하고, wheel/sdist 산출물의 라이선스 메타데이터를 검증하는 회귀 테스트를 추가했습니다.
1019

1120
## [2.8.2] - 2026-03-08
1221
### 변경

DevDoc/license-alignment-audit.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# License Alignment Audit
2+
3+
Date: 2026-03-10
4+
5+
## Files inspected
6+
7+
- `LICENSE`
8+
- `README.md`
9+
- `pyproject.toml`
10+
- `docs/conf.py`
11+
- `CONTRIBUTING.md`
12+
- `.github/workflows/release.yml`
13+
- `.github/workflows/tests.yml`
14+
- `scripts/build-and-publish.sh`
15+
- `tests/test_packaging_py_typed.py`
16+
- Repo-wide searches across `docs/`, `.github/`, `DevDoc/`, `CHANGELOG.md`, and the repository root for license-related metadata and MIT references
17+
18+
## Contradictions found before this change
19+
20+
- `LICENSE` defined a custom non-commercial license and named `python-hwpx Maintainers` as the copyright holder.
21+
- `README.md` showed an MIT badge and an MIT license section, which contradicted the actual license text.
22+
- `README.md` attributed the license line to `고규현 (Kyuhyun Koh)`, while the `LICENSE` file and package metadata used `python-hwpx Maintainers`.
23+
- `pyproject.toml` used the legacy `license = { file = "LICENSE" }` form and also published the classifier `License :: OSI Approved :: MIT License`, which falsely represented the distribution as MIT-licensed.
24+
25+
## Source of truth
26+
27+
- The repository root `LICENSE` file is the source of truth for license terms.
28+
- This audit treats the project as remaining under its existing custom non-commercial license. No evidence of an intentional relicensing to MIT was found elsewhere in the repository.
29+
30+
## Decision summary
31+
32+
- Preserve the current non-commercial custom license.
33+
- Align public-facing metadata and README wording to that license.
34+
- Use modern packaging metadata that points built distributions back to the root `LICENSE` file without inventing an OSI identifier.
35+
- Remove conflicting MIT wording and the MIT trove classifier rather than replacing it with another potentially ambiguous license classifier.
36+
37+
## Notes on surfaces inspected
38+
39+
- `docs/conf.py` already used `python-hwpx Maintainers` and did not restate MIT licensing.
40+
- No GitHub Pages or docs markdown pages were found to restate the project license.
41+
- The release workflow already builds distributions and runs `twine check`, so it was left in place and used for verification after the metadata update.

DevDoc/license-metadata-policy.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# License Metadata Policy
2+
3+
## Source of truth
4+
5+
- The root `LICENSE` file defines the project's license terms.
6+
- Metadata changes must reflect the current `LICENSE` text. Do not treat README text, badges, or historical PyPI metadata as authoritative.
7+
8+
## Packaging rule
9+
10+
- `pyproject.toml` must represent the current custom license with `project.license = "LicenseRef-python-hwpx-NonCommercial"`.
11+
- `pyproject.toml` must list `project.license-files = ["LICENSE"]` so both `sdist` and `wheel` carry the license file.
12+
- Keep the build backend compatible with that metadata format by requiring `setuptools>=77.0.0`.
13+
14+
## Classifier rule
15+
16+
- Do not add `License ::` trove classifiers for this project unless the `LICENSE` file changes to a classifier-backed license and the classifier is verified to be accurate.
17+
- For the current custom non-commercial license, leaving license classifiers unset is less ambiguous than picking an approximate classifier.
18+
19+
## README rule
20+
21+
- The README badge and license section must describe the project as using a custom non-commercial license and link to `LICENSE`.
22+
- If contact information is updated, keep it distinct from the copyright/licensing line unless the `LICENSE` file is updated too.
23+
24+
## Verification rule
25+
26+
- Before release or after touching license metadata, run `python -m build` and `twine check dist/*`.
27+
- Inspect built `PKG-INFO` and wheel `METADATA` for `License-Expression: LicenseRef-python-hwpx-NonCommercial` and `License-File: LICENSE`.
28+
- Confirm the wheel contains `.dist-info/licenses/LICENSE` and the sdist contains the root `LICENSE` file.

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<p align="center">
77
<a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/v/python-hwpx?color=blue&label=PyPI" alt="PyPI"></a>
88
<a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
9-
<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2Fairmang%2Fpython-hwpx%2Fblob%2Fmain%2FLICENSE"><img src="proxy.php?url=https%3A%2F%2Fimg.shields.io%2Fbadge%2Flicense-%3Cspan+class%3D"x x-first x-last">MIT-green" alt="License"></a>
9+
<a href="proxy.php?url=https%3A%2F%2Fgithub.com%2Fairmang%2Fpython-hwpx%2Fblob%2Fmain%2FLICENSE"><img src="proxy.php?url=https%3A%2F%2Fimg.shields.io%2Fbadge%2Flicense-%3Cspan+class%3D"x x-first x-last">Custom%20Noncommercial-orange" alt="License: Custom Non-Commercial"></a>
1010
<a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
1111
</p>
1212
</p>
@@ -86,6 +86,7 @@ doc.save_to_path("결과물.hwpx")
8686
| 📝 **단락** | 추가/삭제/편집/서식 | 텍스트 설정, 단락 삭제(`remove_paragraph`), 스타일 참조 |
8787
| ✏️ **Run** | 텍스트 조각 | 추가, 교체, 볼드/이탤릭/밑줄/색상 서식 |
8888
| 📊 **표(Table)** | 생성/편집/병합 | N×M 표 생성, 셀 텍스트, 셀 병합/분할, 중첩 테이블 |
89+
| 🧭 **표 자동화** | 탐색/채우기 | 테이블 맵, 라벨 기반 셀 탐색, 경로 기반 배치 채우기 |
8990
| 📑 **섹션** | 추가/삭제 | `add_section(after=)`, `remove_section()`, manifest 자동 관리 |
9091
| 🖼️ **이미지** | 임베드/삭제 | 바이너리 데이터 관리, manifest 자동 등록 |
9192
| ✏️ **도형** |/사각형/타원 | OWPML 명세 준수 도형 삽입 |
@@ -126,6 +127,17 @@ doc.set_footer_text("1 / 10", page_type="BOTH")
126127
# 표 셀 병합·분할
127128
table.merge_cells(0, 0, 1, 1) # (0,0)~(1,1) 병합
128129
table.set_cell_text(0, 0, "병합된 셀", logical=True, split_merged=True)
130+
131+
# 양식형 표 자동 채우기
132+
form = doc.add_table(2, 2)
133+
form.cell(0, 0).text = "성명:"
134+
form.cell(1, 0).text = "소속"
135+
136+
doc.find_cell_by_label("성명") # {"matches": [...], "count": 1}
137+
doc.fill_by_path({
138+
"성명 > right": "홍길동",
139+
"소속 > right": "플랫폼팀",
140+
})
129141
```
130142
131143
### 🔍 텍스트 추출 & 검색
@@ -257,13 +269,15 @@ pytest
257269

258270
## License
259271

260-
[MIT](LICENSE) © 고규현 (Kyuhyun Koh)
272+
[Custom Non-Commercial License](LICENSE) © python-hwpx Maintainers
273+
274+
Commercial use requires separate permission from the copyright holders.
261275

262276
<br>
263277

264-
## Author
278+
## Maintainer
265279

266-
**고규현** — 광교고등학교 정보·컴퓨터 교사
280+
Primary maintainer/contact: **고규현** — 광교고등학교 정보·컴퓨터 교사
267281

268282
269283
- 🐙 [@airmang](https://github.com/airmang)

docs/api_reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@
128128
- 섹션을 삭제합니다. 인스턴스 또는 인덱스를 받습니다. 마지막 섹션 삭제 시 `ValueError`가 발생합니다.
129129
- `add_table(rows, cols, ...) -> HwpxOxmlTable`
130130
- 단락을 삽입하고 그 안에 표 인라인 객체를 생성한 후, 표 래퍼를 반환합니다. `border_fill_id_ref`를 생략하면 헤더 참조 목록에 기본 실선 `borderFill`을 생성하고 표와 셀에 자동으로 연결합니다.
131+
- `get_table_map() -> dict`
132+
- 문서 순서대로 표를 스캔하고 `table_index`, `paragraph_index`, 행·열 수, 추정 헤더 텍스트, 첫 행 미리보기, 빈 표 여부를 반환합니다.
133+
- `find_cell_by_label(label_text, direction="right") -> dict`
134+
- 모든 표를 순회하며 라벨 셀을 찾고, `right`/`down` 방향으로 인접한 타깃 셀 정보를 모두 반환합니다. 라벨 매칭은 공백·대소문자·후행 콜론을 정규화합니다.
135+
- `fill_by_path(mappings) -> dict`
136+
- `"라벨 > 방향 > 방향"` 형식의 경로를 해석해 셀 값을 일괄 기록합니다. 라벨 미발견, 다중 후보, 범위 초과는 개별 실패 항목으로 보고하고 나머지 매핑은 계속 처리합니다.
131137
- `add_shape(shape_type, ...) -> HwpxOxmlInlineObject`
132138
- 새 단락에 태그 이름을 사용하여 인라인 그리기 요소를 삽입합니다.
133139
- `add_control(...) -> HwpxOxmlInlineObject`

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[build-system]
2-
requires = ["setuptools", "wheel"]
2+
requires = ["setuptools>=77.0.0", "wheel"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "python-hwpx"
7-
version = "2.8.2"
7+
version = "2.9.0"
88
description = "Hancom HWPX 패키지를 로드하고 편집하기 위한 Python 유틸리티 모음"
99
readme = { file = "README.md", content-type = "text/markdown" }
10-
license = { file = "LICENSE" }
10+
license = "LicenseRef-python-hwpx-NonCommercial"
11+
license-files = ["LICENSE"]
1112
requires-python = ">=3.10"
1213
authors = [
1314
{ name = "python-hwpx Maintainers" },
@@ -16,7 +17,6 @@ keywords = ["hwp", "hwpx", "hancom", "opc", "xml"]
1617
classifiers = [
1718
"Development Status :: 3 - Alpha",
1819
"Intended Audience :: Developers",
19-
"License :: OSI Approved :: MIT License",
2020
"Programming Language :: Python :: 3",
2121
"Programming Language :: Python :: 3.10",
2222
"Programming Language :: Python :: 3.11",
@@ -35,6 +35,7 @@ dev = [
3535
"pytest>=7.4",
3636
]
3737
test = [
38+
"build>=1.0",
3839
"pytest>=7.4",
3940
"pytest-cov>=5.0",
4041
]

src/hwpx/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ def _resolve_version() -> str:
1010
except PackageNotFoundError:
1111
return "0+unknown"
1212

13+
def __getattr__(name: str) -> object:
14+
"""Resolve dynamic module attributes."""
1315

14-
__version__ = _resolve_version()
16+
if name == "__version__":
17+
return _resolve_version()
18+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
1519

1620
from .tools.text_extractor import (
1721
DEFAULT_NAMESPACES,

src/hwpx/document.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import uuid
1111

1212
from os import PathLike
13-
from typing import Any, BinaryIO, Iterator, Sequence, overload
13+
from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Mapping, Sequence, overload
1414

1515
from lxml import etree
1616

@@ -53,6 +53,9 @@
5353

5454
logger = logging.getLogger(__name__)
5555

56+
if TYPE_CHECKING:
57+
from .tools.table_navigation import TableFillResult, TableLabelSearchResult, TableMapResult
58+
5659

5760
def _append_element(
5861
parent: Any,
@@ -741,6 +744,34 @@ def add_table(
741744
char_pr_id_ref=char_pr_id_ref,
742745
)
743746

747+
def get_table_map(self) -> TableMapResult:
748+
"""Return compact metadata for every table in document order."""
749+
750+
from .tools.table_navigation import get_table_map
751+
752+
return get_table_map(self)
753+
754+
def find_cell_by_label(
755+
self,
756+
label_text: str,
757+
direction: str = "right",
758+
) -> TableLabelSearchResult:
759+
"""Return every label/target cell pair that matches *label_text*."""
760+
761+
from .tools.table_navigation import find_cell_by_label
762+
763+
return find_cell_by_label(self, label_text, direction=direction)
764+
765+
def fill_by_path(
766+
self,
767+
mappings: Mapping[str, str],
768+
) -> TableFillResult:
769+
"""Fill table cells using ``label > direction > ...`` navigation paths."""
770+
771+
from .tools.table_navigation import fill_by_path
772+
773+
return fill_by_path(self, mappings)
774+
744775
def add_shape(
745776
self,
746777
shape_type: str,

src/hwpx/tools/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@
2525
describe_element_path,
2626
strip_namespace,
2727
)
28+
from .table_navigation import (
29+
TableCellReference,
30+
TableFillApplied,
31+
TableFillFailed,
32+
TableFillResult,
33+
TableLabelMatch,
34+
TableLabelSearchResult,
35+
TableMapEntry,
36+
TableMapResult,
37+
fill_by_path,
38+
find_cell_by_label,
39+
get_table_map,
40+
)
2841
from .validator import (
2942
DocumentSchemas,
3043
ValidationIssue,
@@ -41,6 +54,17 @@
4154
"build_parent_map",
4255
"describe_element_path",
4356
"strip_namespace",
57+
"TableCellReference",
58+
"TableFillApplied",
59+
"TableFillFailed",
60+
"TableFillResult",
61+
"TableLabelMatch",
62+
"TableLabelSearchResult",
63+
"TableMapEntry",
64+
"TableMapResult",
65+
"fill_by_path",
66+
"find_cell_by_label",
67+
"get_table_map",
4468
"FoundElement",
4569
"ObjectFinder",
4670
"PackageValidationIssue",

0 commit comments

Comments
 (0)