cjk-semantic-split 0.2.0__tar.gz → 0.5.0__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.
- cjk_semantic_split-0.5.0/PKG-INFO +266 -0
- cjk_semantic_split-0.5.0/README.md +219 -0
- cjk_semantic_split-0.5.0/data/_sources/gf0013-2013.tsv +8105 -0
- cjk_semantic_split-0.5.0/data/_sources/kangxi-radicals.tsv +214 -0
- cjk_semantic_split-0.5.0/docs/superpowers/plans/2026-07-17-script-aware-data-partition.md +1765 -0
- cjk_semantic_split-0.5.0/docs/superpowers/plans/2026-07-20-v030-scope-revision.md +1065 -0
- cjk_semantic_split-0.5.0/docs/superpowers/plans/2026-07-20-v050-primitive-id-codec.md +784 -0
- cjk_semantic_split-0.5.0/docs/superpowers/specs/2026-07-17-script-aware-data-partition-design.md +383 -0
- cjk_semantic_split-0.5.0/docs/superpowers/specs/2026-07-20-v030-scope-revision.md +303 -0
- cjk_semantic_split-0.5.0/docs/superpowers/specs/2026-07-20-v040-cjk-removal.md +132 -0
- cjk_semantic_split-0.5.0/docs/superpowers/specs/2026-07-20-v050-primitive-id-codec.md +240 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/pyproject.toml +3 -3
- cjk_semantic_split-0.5.0/scripts/classify.py +551 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/scripts/harvest.py +4 -4
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/__init__.py +13 -2
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/__main__.py +21 -15
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/api.py +12 -4
- cjk_semantic_split-0.5.0/src/chinese_decompose/codec.py +128 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/data/decomp_patches.txt +2 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/data/decomp_zh-Hans-general.txt +7601 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/data/decomp_zh-Hant-general.txt +7601 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/data/primitives_zh-Hans.txt +215 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/data/primitives_zh-Hant.txt +215 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/dsl.py +32 -16
- cjk_semantic_split-0.5.0/src/chinese_decompose/loader.py +318 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/primitives.py +138 -0
- cjk_semantic_split-0.5.0/src/chinese_decompose/script.py +28 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/split.py +11 -2
- cjk_semantic_split-0.5.0/tests/test_api_script.py +80 -0
- cjk_semantic_split-0.5.0/tests/test_classify.py +97 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_cli.py +34 -10
- cjk_semantic_split-0.5.0/tests/test_codec.py +82 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_decompose.py +16 -12
- cjk_semantic_split-0.5.0/tests/test_gf_table_present.py +14 -0
- cjk_semantic_split-0.5.0/tests/test_kangxi_source_present.py +38 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_loader.py +35 -36
- cjk_semantic_split-0.5.0/tests/test_loader_legacy.py +36 -0
- cjk_semantic_split-0.5.0/tests/test_loader_rare_removed.py +28 -0
- cjk_semantic_split-0.5.0/tests/test_loader_script.py +63 -0
- cjk_semantic_split-0.5.0/tests/test_partition_files_present.py +32 -0
- cjk_semantic_split-0.5.0/tests/test_primitive_count.py +29 -0
- cjk_semantic_split-0.5.0/tests/test_primitive_ids.py +74 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_primitives.py +6 -6
- cjk_semantic_split-0.5.0/tests/test_primitives_script.py +40 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_properties.py +15 -11
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_read_structure.py +16 -12
- cjk_semantic_split-0.5.0/tests/test_script.py +23 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_smoke.py +1 -1
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_split.py +17 -12
- cjk_semantic_split-0.5.0/tests/test_version.py +15 -0
- cjk_semantic_split-0.2.0/PKG-INFO +0 -152
- cjk_semantic_split-0.2.0/README.md +0 -105
- cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l1.txt +0 -74410
- cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l2.txt +0 -6
- cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l3.txt +0 -8
- cjk_semantic_split-0.2.0/src/chinese_decompose/data/primitives.txt +0 -1510
- cjk_semantic_split-0.2.0/src/chinese_decompose/loader.py +0 -191
- cjk_semantic_split-0.2.0/src/chinese_decompose/primitives.py +0 -62
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/.github/workflows/ci.yml +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/.gitignore +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/LICENSE +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/docs/component-position-encoding.md +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/docs/decomposition-logic.md +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/docs/superpowers/plans/2026-07-16-cjk-semantic-split-implementation.md +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/scripts/__init__.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/scripts/review_conflicts.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/errors.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/src/chinese_decompose/structures.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/__init__.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/conftest.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_api.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_compose.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_dsl.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_harvest.py +0 -0
- {cjk_semantic_split-0.2.0 → cjk_semantic_split-0.5.0}/tests/test_structures.py +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cjk-semantic-split
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Recursive atomic decomposition of 汉字 (Chinese characters) with 9-grid 2-digit spatial encoding, distinguishing simplified and traditional forms. 将汉字递归拆解为原子部件,并在 9 宫格 2 位数字空间编码中标记每个组件的位置。
|
|
5
|
+
Project-URL: Source, https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script.git
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/issues
|
|
7
|
+
Author-email: Guanyu Yue <yue.guanyu@hotmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: chinese,decomposition,hanzi,ideograph
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
|
+
Requires-Python: >=3.9
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: hatch>=1.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: hypothesis>=6.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# cjk-semantic-split
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 中文简介
|
|
55
|
+
|
|
56
|
+
`cjk-semantic-split` 是一个 Python 库,用于将**汉字** (通用规范汉字表 GF 0013-2013, 简体和繁体) 递归拆分为原子部件,并使用 **9 宫格 2 位数字空间编码**标记每个组件的位置。
|
|
57
|
+
|
|
58
|
+
**核心特性**
|
|
59
|
+
|
|
60
|
+
- **递归拆解**:例如 `踩 → 足 + 采 → 爪 + 木`,中间非叶节点(如 `采`)完整保留
|
|
61
|
+
- **位置编码**:每个组件的 2 位数字代号(`00/10/20/30/40` 四个基本方位 + `50/60/70/80` 四个角方位 + `14/16/34/36` 四分拆壁 + `90–99` 包围框)
|
|
62
|
+
- **组合算子 `⊙`**:父子位置按空间几何合成(详见 [`docs/component-position-encoding.md`](docs/component-position-encoding.md))
|
|
63
|
+
- **多重数据源**:内置四级 — Unihan kIDS / wikimedia Commons / 下一层采集 / 手工修补
|
|
64
|
+
- **零运行时依赖**:纯 Python
|
|
65
|
+
|
|
66
|
+
**主要 API**
|
|
67
|
+
|
|
68
|
+
- `decompose(text)` → 字符串(默认 JSON 输出)
|
|
69
|
+
- `read_structure(text)` → `list[DecompositionTree]`,保留完整嵌套层级(包含中间非叶节点)
|
|
70
|
+
- `python -m chinese_decompose <字符>` → 命令行
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from chinese_decompose import read_structure
|
|
74
|
+
|
|
75
|
+
trees = read_structure("踩")
|
|
76
|
+
# trees[0].atoms == ('足', '爪', '木')
|
|
77
|
+
# trees[0].positions == (30, 60, 80)
|
|
78
|
+
# 采 作为中间节点保留在 children[1] 中
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
详细算法参见 [`docs/decomposition-logic.md`](docs/decomposition-logic.md);完整位置编码表参见 [`docs/component-position-encoding.md`](docs/component-position-encoding.md)。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
Recursive atomic decomposition of 汉字 (Chinese characters, simplified and traditional) with 9-grid spatial position encoding.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install cjk-semantic-split
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from chinese_decompose import decompose
|
|
93
|
+
|
|
94
|
+
decompose("踩")
|
|
95
|
+
# '踩 [30]:足 [60]:爪 [80]:木'
|
|
96
|
+
|
|
97
|
+
decompose("囚")
|
|
98
|
+
# '囚 [90]:囗 [0]:人'
|
|
99
|
+
|
|
100
|
+
decompose("踩好", as_tree=True)
|
|
101
|
+
# [DecompositionTree('踩'), DecompositionTree('好')]
|
|
102
|
+
|
|
103
|
+
# Read the full structural decomposition (preserves intermediate nodes
|
|
104
|
+
# like 采 inside 踩; each tree node carries its 2-digit position code).
|
|
105
|
+
from chinese_decompose import read_structure
|
|
106
|
+
trees = read_structure("踩")
|
|
107
|
+
trees[0].atoms # ('足', '爪', '木')
|
|
108
|
+
trees[0].positions # (30, 60, 80)
|
|
109
|
+
trees[0].children[1].char # '采' (intermediate node preserved!)
|
|
110
|
+
trees[0].children[1].position # 40
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
- [Component Position Encoding](docs/component-position-encoding.md) — full position-code reference (00/10/20/30/40, corners, 4-cell strips, surrounds) and the composition operator.
|
|
116
|
+
- [Decomposition Logic](docs/decomposition-logic.md) — algorithm walkthrough, data flow, worked example `踩 → 足 + 采 → 爪 + 木`.
|
|
117
|
+
|
|
118
|
+
## Position Encoding
|
|
119
|
+
|
|
120
|
+
Every component sits at a 2-digit position code: cardinals (`10/20/30/40`), single-cell corners (`50/60/70/80`), 4-cell layout strips (`14/16/34/36`), or surround envelopes (`90`–`99`). See the [design spec](docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md) for full details.
|
|
121
|
+
|
|
122
|
+
## CLI
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m chinese_decompose 好 # JSON output (default)
|
|
126
|
+
python -m chinese_decompose 好 --format dsl # DSL string
|
|
127
|
+
python -m chinese_decompose 好 --format tree # repr() of nested trees
|
|
128
|
+
python -m chinese_decompose 好 --script zh-Hant # traditional
|
|
129
|
+
python -m chinese_decompose --self-test # coverage report
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Installation
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install cjk-semantic-split
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
From source:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script.git
|
|
142
|
+
cd Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script
|
|
143
|
+
pip install -e .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Requires Python ≥ 3.9. No third-party runtime dependencies.
|
|
147
|
+
|
|
148
|
+
## Quick Start
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from chinese_decompose import decompose
|
|
152
|
+
|
|
153
|
+
print(decompose("好"))
|
|
154
|
+
# '好 [30]:女 [40]:子'
|
|
155
|
+
|
|
156
|
+
print(decompose("字"))
|
|
157
|
+
# '字 [10]:宀 [20]:子'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
For traditional forms:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from chinese_decompose import decompose
|
|
164
|
+
print(decompose("好", script="zh-Hant"))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
For unknown chars, raise or skip:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from chinese_decompose import decompose, UnknownCharacterError
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
decompose("龘") # outside GF 0013-2013
|
|
174
|
+
except UnknownCharacterError:
|
|
175
|
+
print("not in dictionary")
|
|
176
|
+
|
|
177
|
+
decompose("龘", strict=False) # → '?[?]:?'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## API Reference
|
|
181
|
+
|
|
182
|
+
### Public Symbols
|
|
183
|
+
|
|
184
|
+
| Symbol | Signature | Purpose |
|
|
185
|
+
|--------|-----------|---------|
|
|
186
|
+
| `decompose` | `(text, *, as_tree=False, strict=True, script=None, loader=None)` | Primary API; returns DSL string or list of `DecompositionTree`. |
|
|
187
|
+
| `read_structure` | `(text, *, strict=True, script=None, loader=None)` | Returns a list of `DecompositionTree` with full nested hierarchy. |
|
|
188
|
+
| `make_loader` | `(*, script=None, patches_path=None)` | Construct (or return cached singleton) `Loader`. |
|
|
189
|
+
| `clear_cache` | `()` | Reset all loader singletons. |
|
|
190
|
+
| `get_primitives` | `(*, script=None)` | Active primitive set for `script`. |
|
|
191
|
+
| `set_primitives` | `(iterable, *, script=None)` | Process-local override; test affordance. |
|
|
192
|
+
| `reset_primitives` | `(*, script=None)` | Clear override(s); `script=None` clears all scripts. |
|
|
193
|
+
| `is_primitive` | `(char, *, script=None)` | Primitive membership check. |
|
|
194
|
+
| `get_primitive_id` | `(char, *, script=None)` | Char → 0-213 Kangxi ID; raises `ValueError` if not a Kangxi radical. |
|
|
195
|
+
| `get_primitive_by_id` | `(id, *, script=None)` | ID → char; raises `ValueError` if `id ∉ [0, 213]`. |
|
|
196
|
+
| `list_primitives` | `(*, script=None)` | 214 primitive chars in 康熙原序. |
|
|
197
|
+
| `list_primitives_with_ids` | `(*, script=None)` | `((id, char), ...)` in 康熙原序. |
|
|
198
|
+
| `encode_codec` | `(text, *, script="zh-Hans", strict=True)` | DSL → ID-based compact format. |
|
|
199
|
+
| `decode_codec` | `(encoded, *, script="zh-Hans")` | ID-based → text (round-trips). |
|
|
200
|
+
| `parse_dsl` | `(dsl_str)` | Parse a DSL string into a `DecompositionTree`. |
|
|
201
|
+
| `serialize_tree` | `(tree)` | Serialize a `DecompositionTree` to DSL. |
|
|
202
|
+
| `compose` | `(parent, child)` | Position composition operator. |
|
|
203
|
+
| `DecompositionTree` | dataclass | Immutable decomposition node (char, children, position, atoms, positions). |
|
|
204
|
+
|
|
205
|
+
### Codec Example
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from chinese_decompose import encode_codec, decode_codec
|
|
209
|
+
|
|
210
|
+
encoded = encode_codec("好字")
|
|
211
|
+
# '好,37,30,38,40|字,39,10,38,20'
|
|
212
|
+
print(decode_codec(encoded))
|
|
213
|
+
# '好字'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Primitive-only chars encode as the bare char (no IDs):
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
encode_codec("木") == "木"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For non-strict mode on unknown chars:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
encode_codec("好龘", strict=False)
|
|
226
|
+
# '好,37,30,38,40|?'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Errors
|
|
230
|
+
|
|
231
|
+
| Exception | When |
|
|
232
|
+
|-----------|------|
|
|
233
|
+
| `UnknownCharacterError` | `strict=True` and a char has no decomposition data. |
|
|
234
|
+
| `ValueError` | Bad script name; ID out of range; malformed codec entry. |
|
|
235
|
+
| `InvalidPositionCodeError` | Data file has a position outside the 2-digit valid set. |
|
|
236
|
+
| `MalformedDSLParseError` | `parse_dsl` on a bad DSL string. |
|
|
237
|
+
| `CompositionError` | `compose(parent, child)` for a geometrically invalid pair. |
|
|
238
|
+
|
|
239
|
+
## Scope (v0.4.0)
|
|
240
|
+
|
|
241
|
+
The bundled data covers the **通用规范汉字表 (GF 0013-2013)** — 8,105 chars
|
|
242
|
+
mandated by the PRC Ministry of Education for general Chinese text — and
|
|
243
|
+
nothing else. This package supports **汉字 (Chinese characters, simplified
|
|
244
|
+
and traditional) only**. Japanese (仮名, 漢字), Korean (한글, 漢字), and other
|
|
245
|
+
CJK region scripts are not supported.
|
|
246
|
+
The primitive (基础偏旁部首) set is the canonical **康熙字典 214 部首**,
|
|
247
|
+
identical in both Hans and Hant files.
|
|
248
|
+
|
|
249
|
+
Bundled data files (`src/chinese_decompose/data/`):
|
|
250
|
+
|
|
251
|
+
- `decomp_zh-Hans-general.txt` — simplified GF 0013-2013 (~8k chars)
|
|
252
|
+
- `decomp_zh-Hant-general.txt` — traditional GF 0013-2013 (~8k chars)
|
|
253
|
+
- `decomp_patches.txt` — manual overrides (wins over all)
|
|
254
|
+
- `primitives_zh-Hans.txt` / `primitives_zh-Hant.txt` — 214 Kangxi radicals each
|
|
255
|
+
|
|
256
|
+
## Configuration
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from chinese_decompose import set_primitives
|
|
260
|
+
|
|
261
|
+
set_primitives({"木", "火", "水", ...}) # process-local override
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# cjk-semantic-split
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 中文简介
|
|
8
|
+
|
|
9
|
+
`cjk-semantic-split` 是一个 Python 库,用于将**汉字** (通用规范汉字表 GF 0013-2013, 简体和繁体) 递归拆分为原子部件,并使用 **9 宫格 2 位数字空间编码**标记每个组件的位置。
|
|
10
|
+
|
|
11
|
+
**核心特性**
|
|
12
|
+
|
|
13
|
+
- **递归拆解**:例如 `踩 → 足 + 采 → 爪 + 木`,中间非叶节点(如 `采`)完整保留
|
|
14
|
+
- **位置编码**:每个组件的 2 位数字代号(`00/10/20/30/40` 四个基本方位 + `50/60/70/80` 四个角方位 + `14/16/34/36` 四分拆壁 + `90–99` 包围框)
|
|
15
|
+
- **组合算子 `⊙`**:父子位置按空间几何合成(详见 [`docs/component-position-encoding.md`](docs/component-position-encoding.md))
|
|
16
|
+
- **多重数据源**:内置四级 — Unihan kIDS / wikimedia Commons / 下一层采集 / 手工修补
|
|
17
|
+
- **零运行时依赖**:纯 Python
|
|
18
|
+
|
|
19
|
+
**主要 API**
|
|
20
|
+
|
|
21
|
+
- `decompose(text)` → 字符串(默认 JSON 输出)
|
|
22
|
+
- `read_structure(text)` → `list[DecompositionTree]`,保留完整嵌套层级(包含中间非叶节点)
|
|
23
|
+
- `python -m chinese_decompose <字符>` → 命令行
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from chinese_decompose import read_structure
|
|
27
|
+
|
|
28
|
+
trees = read_structure("踩")
|
|
29
|
+
# trees[0].atoms == ('足', '爪', '木')
|
|
30
|
+
# trees[0].positions == (30, 60, 80)
|
|
31
|
+
# 采 作为中间节点保留在 children[1] 中
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
详细算法参见 [`docs/decomposition-logic.md`](docs/decomposition-logic.md);完整位置编码表参见 [`docs/component-position-encoding.md`](docs/component-position-encoding.md)。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Recursive atomic decomposition of 汉字 (Chinese characters, simplified and traditional) with 9-grid spatial position encoding.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install cjk-semantic-split
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from chinese_decompose import decompose
|
|
46
|
+
|
|
47
|
+
decompose("踩")
|
|
48
|
+
# '踩 [30]:足 [60]:爪 [80]:木'
|
|
49
|
+
|
|
50
|
+
decompose("囚")
|
|
51
|
+
# '囚 [90]:囗 [0]:人'
|
|
52
|
+
|
|
53
|
+
decompose("踩好", as_tree=True)
|
|
54
|
+
# [DecompositionTree('踩'), DecompositionTree('好')]
|
|
55
|
+
|
|
56
|
+
# Read the full structural decomposition (preserves intermediate nodes
|
|
57
|
+
# like 采 inside 踩; each tree node carries its 2-digit position code).
|
|
58
|
+
from chinese_decompose import read_structure
|
|
59
|
+
trees = read_structure("踩")
|
|
60
|
+
trees[0].atoms # ('足', '爪', '木')
|
|
61
|
+
trees[0].positions # (30, 60, 80)
|
|
62
|
+
trees[0].children[1].char # '采' (intermediate node preserved!)
|
|
63
|
+
trees[0].children[1].position # 40
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Documentation
|
|
67
|
+
|
|
68
|
+
- [Component Position Encoding](docs/component-position-encoding.md) — full position-code reference (00/10/20/30/40, corners, 4-cell strips, surrounds) and the composition operator.
|
|
69
|
+
- [Decomposition Logic](docs/decomposition-logic.md) — algorithm walkthrough, data flow, worked example `踩 → 足 + 采 → 爪 + 木`.
|
|
70
|
+
|
|
71
|
+
## Position Encoding
|
|
72
|
+
|
|
73
|
+
Every component sits at a 2-digit position code: cardinals (`10/20/30/40`), single-cell corners (`50/60/70/80`), 4-cell layout strips (`14/16/34/36`), or surround envelopes (`90`–`99`). See the [design spec](docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md) for full details.
|
|
74
|
+
|
|
75
|
+
## CLI
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m chinese_decompose 好 # JSON output (default)
|
|
79
|
+
python -m chinese_decompose 好 --format dsl # DSL string
|
|
80
|
+
python -m chinese_decompose 好 --format tree # repr() of nested trees
|
|
81
|
+
python -m chinese_decompose 好 --script zh-Hant # traditional
|
|
82
|
+
python -m chinese_decompose --self-test # coverage report
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install cjk-semantic-split
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
From source:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git clone https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script.git
|
|
95
|
+
cd Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script
|
|
96
|
+
pip install -e .
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Requires Python ≥ 3.9. No third-party runtime dependencies.
|
|
100
|
+
|
|
101
|
+
## Quick Start
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from chinese_decompose import decompose
|
|
105
|
+
|
|
106
|
+
print(decompose("好"))
|
|
107
|
+
# '好 [30]:女 [40]:子'
|
|
108
|
+
|
|
109
|
+
print(decompose("字"))
|
|
110
|
+
# '字 [10]:宀 [20]:子'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For traditional forms:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from chinese_decompose import decompose
|
|
117
|
+
print(decompose("好", script="zh-Hant"))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For unknown chars, raise or skip:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from chinese_decompose import decompose, UnknownCharacterError
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
decompose("龘") # outside GF 0013-2013
|
|
127
|
+
except UnknownCharacterError:
|
|
128
|
+
print("not in dictionary")
|
|
129
|
+
|
|
130
|
+
decompose("龘", strict=False) # → '?[?]:?'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API Reference
|
|
134
|
+
|
|
135
|
+
### Public Symbols
|
|
136
|
+
|
|
137
|
+
| Symbol | Signature | Purpose |
|
|
138
|
+
|--------|-----------|---------|
|
|
139
|
+
| `decompose` | `(text, *, as_tree=False, strict=True, script=None, loader=None)` | Primary API; returns DSL string or list of `DecompositionTree`. |
|
|
140
|
+
| `read_structure` | `(text, *, strict=True, script=None, loader=None)` | Returns a list of `DecompositionTree` with full nested hierarchy. |
|
|
141
|
+
| `make_loader` | `(*, script=None, patches_path=None)` | Construct (or return cached singleton) `Loader`. |
|
|
142
|
+
| `clear_cache` | `()` | Reset all loader singletons. |
|
|
143
|
+
| `get_primitives` | `(*, script=None)` | Active primitive set for `script`. |
|
|
144
|
+
| `set_primitives` | `(iterable, *, script=None)` | Process-local override; test affordance. |
|
|
145
|
+
| `reset_primitives` | `(*, script=None)` | Clear override(s); `script=None` clears all scripts. |
|
|
146
|
+
| `is_primitive` | `(char, *, script=None)` | Primitive membership check. |
|
|
147
|
+
| `get_primitive_id` | `(char, *, script=None)` | Char → 0-213 Kangxi ID; raises `ValueError` if not a Kangxi radical. |
|
|
148
|
+
| `get_primitive_by_id` | `(id, *, script=None)` | ID → char; raises `ValueError` if `id ∉ [0, 213]`. |
|
|
149
|
+
| `list_primitives` | `(*, script=None)` | 214 primitive chars in 康熙原序. |
|
|
150
|
+
| `list_primitives_with_ids` | `(*, script=None)` | `((id, char), ...)` in 康熙原序. |
|
|
151
|
+
| `encode_codec` | `(text, *, script="zh-Hans", strict=True)` | DSL → ID-based compact format. |
|
|
152
|
+
| `decode_codec` | `(encoded, *, script="zh-Hans")` | ID-based → text (round-trips). |
|
|
153
|
+
| `parse_dsl` | `(dsl_str)` | Parse a DSL string into a `DecompositionTree`. |
|
|
154
|
+
| `serialize_tree` | `(tree)` | Serialize a `DecompositionTree` to DSL. |
|
|
155
|
+
| `compose` | `(parent, child)` | Position composition operator. |
|
|
156
|
+
| `DecompositionTree` | dataclass | Immutable decomposition node (char, children, position, atoms, positions). |
|
|
157
|
+
|
|
158
|
+
### Codec Example
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from chinese_decompose import encode_codec, decode_codec
|
|
162
|
+
|
|
163
|
+
encoded = encode_codec("好字")
|
|
164
|
+
# '好,37,30,38,40|字,39,10,38,20'
|
|
165
|
+
print(decode_codec(encoded))
|
|
166
|
+
# '好字'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Primitive-only chars encode as the bare char (no IDs):
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
encode_codec("木") == "木"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
For non-strict mode on unknown chars:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
encode_codec("好龘", strict=False)
|
|
179
|
+
# '好,37,30,38,40|?'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Errors
|
|
183
|
+
|
|
184
|
+
| Exception | When |
|
|
185
|
+
|-----------|------|
|
|
186
|
+
| `UnknownCharacterError` | `strict=True` and a char has no decomposition data. |
|
|
187
|
+
| `ValueError` | Bad script name; ID out of range; malformed codec entry. |
|
|
188
|
+
| `InvalidPositionCodeError` | Data file has a position outside the 2-digit valid set. |
|
|
189
|
+
| `MalformedDSLParseError` | `parse_dsl` on a bad DSL string. |
|
|
190
|
+
| `CompositionError` | `compose(parent, child)` for a geometrically invalid pair. |
|
|
191
|
+
|
|
192
|
+
## Scope (v0.4.0)
|
|
193
|
+
|
|
194
|
+
The bundled data covers the **通用规范汉字表 (GF 0013-2013)** — 8,105 chars
|
|
195
|
+
mandated by the PRC Ministry of Education for general Chinese text — and
|
|
196
|
+
nothing else. This package supports **汉字 (Chinese characters, simplified
|
|
197
|
+
and traditional) only**. Japanese (仮名, 漢字), Korean (한글, 漢字), and other
|
|
198
|
+
CJK region scripts are not supported.
|
|
199
|
+
The primitive (基础偏旁部首) set is the canonical **康熙字典 214 部首**,
|
|
200
|
+
identical in both Hans and Hant files.
|
|
201
|
+
|
|
202
|
+
Bundled data files (`src/chinese_decompose/data/`):
|
|
203
|
+
|
|
204
|
+
- `decomp_zh-Hans-general.txt` — simplified GF 0013-2013 (~8k chars)
|
|
205
|
+
- `decomp_zh-Hant-general.txt` — traditional GF 0013-2013 (~8k chars)
|
|
206
|
+
- `decomp_patches.txt` — manual overrides (wins over all)
|
|
207
|
+
- `primitives_zh-Hans.txt` / `primitives_zh-Hant.txt` — 214 Kangxi radicals each
|
|
208
|
+
|
|
209
|
+
## Configuration
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from chinese_decompose import set_primitives
|
|
213
|
+
|
|
214
|
+
set_primitives({"木", "火", "水", ...}) # process-local override
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT — see `LICENSE`.
|