cjk-semantic-split 0.1.4__tar.gz → 0.2.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.
Files changed (47) hide show
  1. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/PKG-INFO +1 -1
  2. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/pyproject.toml +2 -2
  3. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/scripts/harvest.py +69 -29
  4. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/__init__.py +1 -1
  5. cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l1.txt +74410 -0
  6. cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l2.txt +6 -0
  7. cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_l3.txt +8 -0
  8. cjk_semantic_split-0.2.0/src/chinese_decompose/data/decomp_patches.txt +6 -0
  9. cjk_semantic_split-0.2.0/src/chinese_decompose/data/primitives.txt +1510 -0
  10. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/dsl.py +34 -3
  11. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/loader.py +64 -5
  12. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/primitives.py +25 -4
  13. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_decompose.py +7 -7
  14. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_harvest.py +13 -11
  15. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_primitives.py +13 -10
  16. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_smoke.py +1 -1
  17. cjk_semantic_split-0.1.4/src/chinese_decompose/data/decomp_l1.json +0 -1
  18. cjk_semantic_split-0.1.4/src/chinese_decompose/data/decomp_l2.json +0 -1
  19. cjk_semantic_split-0.1.4/src/chinese_decompose/data/decomp_l3.json +0 -5
  20. cjk_semantic_split-0.1.4/src/chinese_decompose/data/decomp_patches.json +0 -1
  21. cjk_semantic_split-0.1.4/src/chinese_decompose/data/primitives.json +0 -1
  22. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/.github/workflows/ci.yml +0 -0
  23. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/.gitignore +0 -0
  24. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/LICENSE +0 -0
  25. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/README.md +0 -0
  26. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/docs/component-position-encoding.md +0 -0
  27. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/docs/decomposition-logic.md +0 -0
  28. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/docs/superpowers/plans/2026-07-16-cjk-semantic-split-implementation.md +0 -0
  29. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/docs/superpowers/specs/2026-07-16-cjk-semantic-split-design.md +0 -0
  30. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/scripts/__init__.py +0 -0
  31. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/scripts/review_conflicts.py +0 -0
  32. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/__main__.py +0 -0
  33. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/api.py +0 -0
  34. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/errors.py +0 -0
  35. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/split.py +0 -0
  36. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/src/chinese_decompose/structures.py +0 -0
  37. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/__init__.py +0 -0
  38. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/conftest.py +0 -0
  39. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_api.py +0 -0
  40. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_cli.py +0 -0
  41. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_compose.py +0 -0
  42. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_dsl.py +0 -0
  43. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_loader.py +0 -0
  44. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_properties.py +0 -0
  45. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_read_structure.py +0 -0
  46. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_split.py +0 -0
  47. {cjk_semantic_split-0.1.4 → cjk_semantic_split-0.2.0}/tests/test_structures.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjk-semantic-split
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: Recursive atomic decomposition of CJK characters with 9-grid 2-digit spatial encoding. 将汉字递归拆解为原子部件,并在 9 宫格 2 位数字空间编码中标记每个组件的位置。
5
5
  Project-URL: Source, https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script.git
6
6
  Project-URL: Bug Tracker, https://github.com/yueguanyu/Semantic-Character-Splitting-and-Structural-Coding-for-Chinese-Script/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cjk-semantic-split"
7
- version = "0.1.4"
7
+ version = "0.2.0"
8
8
  description = "Recursive atomic decomposition of CJK characters with 9-grid 2-digit spatial encoding. 将汉字递归拆解为原子部件,并在 9 宫格 2 位数字空间编码中标记每个组件的位置。"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -40,7 +40,7 @@ dev = [
40
40
 
41
41
  [tool.hatch.build.targets.wheel]
42
42
  packages = ["src/chinese_decompose"]
43
- include = ["src/chinese_decompose/data/*.json"]
43
+ include = ["src/chinese_decompose/data/*.txt"]
44
44
 
45
45
  [tool.pytest.ini_options]
46
46
  testpaths = ["tests"]
@@ -7,7 +7,7 @@ notation (⿰⿱⿴⿵⿶⿷⿸⿹⿺⿾⿿, plus 3-cell and overlaid variants w
7
7
 
8
8
  Usage:
9
9
  python -m scripts.harvest --tier l1 \
10
- --output ../src/chinese_decompose/data/decomp_l1.json
10
+ --output ../src/chinese_decompose/data/decomp_l1.txt
11
11
 
12
12
  Source file format (tab-separated):
13
13
 
@@ -153,6 +153,11 @@ def _walk_kids(
153
153
  return False
154
154
  composed = compose_fn(parent_position, local_pos)
155
155
  if len(comp_str) == 1:
156
+ # Reject leaves that are themselves IDC codes (e.g. degenerate
157
+ # yi-bai entries where ⿰ appears as a slot — that's a parse
158
+ # artifact, not a real atom).
159
+ if comp_str in IDC_TABLE_2_CELL:
160
+ return False
156
161
  out.append((comp_str, composed))
157
162
  else:
158
163
  # Recurse into subtree
@@ -221,8 +226,40 @@ def parse_ids_source(text: str, compose_fn) -> tuple[dict[str, list[tuple[str, i
221
226
  return entries, refs
222
227
 
223
228
 
229
+ def _serialize_dsl(char: str, comps: list[tuple[str, int]]) -> str:
230
+ """Build the staggered DSL string for a character + its components.
231
+
232
+ Matches the format used by ``DecompositionTree.to_dsl()``:
233
+ "踩 [30]:足 [60]:爪 [80]:木" — the bracket carries the *next* leaf's
234
+ position; the last leaf is bare. A primitive char with no children
235
+ yields just the bare char.
236
+ """
237
+ if not comps:
238
+ return char # primitive — bare char
239
+ parts = [f"{char} [{comps[0][1]}]"]
240
+ leaves = [c for c, _ in comps]
241
+ leaf_positions = [p for _, p in comps]
242
+ for i in range(len(leaves) - 1):
243
+ parts.append(f"{leaves[i]} [{leaf_positions[i + 1]}]")
244
+ parts.append(leaves[-1])
245
+ return ":".join(parts)
246
+
247
+
248
+ def _load_existing_primitives_txt(path: Path) -> set[str]:
249
+ if not path.exists():
250
+ return set()
251
+ chars: set[str] = set()
252
+ for line in path.read_text(encoding="utf-8").splitlines():
253
+ line = line.strip()
254
+ if not line or line.startswith("#"):
255
+ continue
256
+ if len(line) == 1:
257
+ chars.add(line)
258
+ return chars
259
+
260
+
224
261
  def emit_tier1(output_path: Path, compose_fn) -> dict:
225
- """Download IDS dataset, parse, write decomp_l1.json and extend primitives.json."""
262
+ """Download IDS dataset, parse, write decomp_l1.txt and extend primitives.txt."""
226
263
  print(f"Downloading {IDS_SOURCES['lv0']}...", file=sys.stderr)
227
264
  text = fetch_text(IDS_SOURCES["lv0"])
228
265
  print(f" fetched {len(text.splitlines())} lines", file=sys.stderr)
@@ -232,18 +269,26 @@ def emit_tier1(output_path: Path, compose_fn) -> dict:
232
269
  print(f" entries: {len(entries)}, primitive refs: {len(refs)}", file=sys.stderr)
233
270
 
234
271
  output_path.parent.mkdir(parents=True, exist_ok=True)
235
- payload = {"_meta": {"tier": 1, "source": "github.com/yi-bai/ids", "license": "MIT"}}
236
- payload.update({char: [[c, p] for c, p in v] for char, v in entries.items()})
237
- output_path.write_text(
238
- json.dumps(payload, ensure_ascii=False, separators=(",", ":")), encoding="utf-8"
239
- )
272
+
273
+ # Emit Tier 1 as TXT (DSL format, one line per char). Format:
274
+ # <char>\t<DSL>
275
+ # Blank lines and lines starting with `#` are comments.
276
+ lines = [
277
+ f"# Tier 1 decomposition data harvested from yi-bai/ids (MIT) via jsDelivr",
278
+ "# Format per line: <char>\\t<DSL>",
279
+ "# DSL is the staggered form: each bracket carries the NEXT atom's",
280
+ "# position; the last atom is bare. Lines starting with # are comments.",
281
+ "# Empty DSL after the tab means the character is primitive at this tier.",
282
+ ]
283
+ for char in sorted(entries):
284
+ lines.append(f"{char}\t{_serialize_dsl(char, entries[char])}")
285
+ output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
240
286
  size_kb = output_path.stat().st_size // 1024
241
287
  print(f" wrote {output_path} ({size_kb} KB)", file=sys.stderr)
242
288
 
243
289
  # Find all characters that get further decomposed in `entries`
244
- # (recursively, since chars may appear nested). These MUST be removed
245
- # from the primitive set so walker behavior is unambiguous — if a char
246
- # is in both primitives and l1, l1 wins (per last-wins loader order).
290
+ # (recursively, since chars may appear nested). These MUST NOT be
291
+ # promoted into the primitive set.
247
292
  def _collect_decomposable(char: str, seen: set[str], into: set[str]) -> None:
248
293
  if char in seen:
249
294
  return
@@ -260,33 +305,28 @@ def emit_tier1(output_path: Path, compose_fn) -> dict:
260
305
  for char in entries:
261
306
  _collect_decomposable(char, set(), decomposable)
262
307
 
263
- # Primitive leaves: characters in `refs` that NEVER get decomposed (i.e.,
264
- # not in the `decomposable` set we just built). These stay in the
265
- # primitive set; the rest get removed.
266
308
  leaves_without_decomp = {
267
309
  c for c in refs if c not in decomposable and _is_data_char(c)
268
310
  }
269
311
 
270
- # Append new primitives to primitives.json. The bundled primitives.json is
271
- # the AUTHORITATIVE atomicity definition (per wikimedia commons' visual
272
- # decomposition view): any character in it is treated as atomic, even if
273
- # the IDS source happens to have a further decomposition. We only ADD
274
- # new leaves — never remove from the curated set.
312
+ # Append new leaves to primitives.txt. Per the wikimedia commons
313
+ # atomicity principle (§6.3 of the spec), primitives.txt is the
314
+ # authoritative atomicity definition we only ADD new leaves; never
315
+ # remove from the curated set.
275
316
  primitives_path = (
276
- Path(__file__).parent.parent / "src" / "chinese_decompose" / "data" / "primitives.json"
317
+ Path(__file__).parent.parent / "src" / "chinese_decompose" / "data" / "primitives.txt"
277
318
  )
278
- if primitives_path.exists():
279
- existing = set(json.loads(primitives_path.read_text(encoding="utf-8"))["primitives"])
280
- else:
281
- existing = set()
319
+ existing = _load_existing_primitives_txt(primitives_path)
282
320
  new_primitives = sorted(existing | leaves_without_decomp)
321
+ primitives_lines = [
322
+ "# chinese-decompose primitives (authoritative atomic components per wikimedia commons view)",
323
+ "# Format: one character per line. Lines starting with # are comments.",
324
+ "# Characters listed here are treated as atomic by the walker regardless",
325
+ "# of any deeper structure recorded in decomp_*.txt.",
326
+ ]
327
+ primitives_lines += new_primitives
283
328
  primitives_path.write_text(
284
- json.dumps(
285
- {"primitives": new_primitives},
286
- ensure_ascii=False,
287
- separators=(",", ":"),
288
- ),
289
- encoding="utf-8",
329
+ "\n".join(primitives_lines) + "\n", encoding="utf-8"
290
330
  )
291
331
  added = len(new_primitives) - len(existing)
292
332
  print(
@@ -1,5 +1,5 @@
1
1
  """chinese_decompose: recursive atomic decomposition of CJK characters."""
2
- __version__ = "0.1.4"
2
+ __version__ = "0.2.0"
3
3
 
4
4
  from chinese_decompose.api import (
5
5
  DecompositionTree,