licos-dev-sdk 0.3.1__tar.gz → 0.3.2__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 (25) hide show
  1. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/PKG-INFO +1 -1
  2. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/pyproject.toml +1 -1
  3. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/document_docx.py +13 -6
  4. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/document_docx_layout.py +24 -0
  5. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/tests/test_document_spreadsheet.py +12 -0
  6. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/.gitignore +0 -0
  7. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/__init__.py +0 -0
  8. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/_utils.py +0 -0
  9. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/archive.py +0 -0
  10. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/chart.py +0 -0
  11. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/data.py +0 -0
  12. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/diagram.py +0 -0
  13. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/document.py +0 -0
  14. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/document_common.py +0 -0
  15. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/image.py +0 -0
  16. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/model.py +0 -0
  17. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/observability.py +0 -0
  18. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/pdf_inspect.py +0 -0
  19. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/presentation.py +0 -0
  20. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/spreadsheet.py +0 -0
  21. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/spreadsheet_inspect.py +0 -0
  22. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/src/licos_dev_sdk/web.py +0 -0
  23. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/tests/test_model.py +0 -0
  24. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/tests/test_observability.py +0 -0
  25. {licos_dev_sdk-0.3.1 → licos_dev_sdk-0.3.2}/tests/test_output_paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: licos-dev-sdk
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: LICOS Dev SDK - file generation and model capability clients
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: beautifulsoup4>=4.12
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "licos-dev-sdk"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "LICOS Dev SDK - file generation and model capability clients"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -27,6 +27,7 @@ from .document_docx_layout import (
27
27
  first_font as _first_font,
28
28
  table_alignment as _table_alignment,
29
29
  table_cell_placements as _table_cell_placements,
30
+ word_safe_fragments as _word_safe_fragments,
30
31
  )
31
32
 
32
33
 
@@ -251,23 +252,21 @@ class _DocxRenderer:
251
252
  elif level == 2:
252
253
  _set_paragraph_border(p, self.theme["secondary"], side="left", size="20")
253
254
  p.paragraph_format.left_indent = Cm(0.18)
254
- run = p.add_run(text)
255
+ self._append_text(p, text, tag)
255
256
  if tag is not None:
256
257
  self._apply_paragraph_style(p, tag)
257
- self._apply_run_style(run, tag)
258
258
 
259
259
  def render_inline(self, paragraph, node: Tag | NavigableString) -> None:
260
260
  if isinstance(node, Comment):
261
261
  return
262
262
  if isinstance(node, NavigableString):
263
- paragraph.add_run(str(node))
263
+ self._append_text(paragraph, str(node))
264
264
  return
265
265
  for child in node.children:
266
266
  if isinstance(child, Comment):
267
267
  continue
268
268
  if isinstance(child, NavigableString):
269
- run = paragraph.add_run(str(child))
270
- self._apply_run_style(run, node)
269
+ self._append_text(paragraph, str(child), node)
271
270
  elif not isinstance(child, Tag):
272
271
  continue
273
272
  elif child.name == "br":
@@ -296,7 +295,7 @@ class _DocxRenderer:
296
295
  if isinstance(child, Tag) and child.name in {"ul", "ol"}:
297
296
  continue
298
297
  if isinstance(child, (Tag, NavigableString)):
299
- self.render_inline(p, child) if isinstance(child, Tag) else p.add_run(str(child))
298
+ self.render_inline(p, child) if isinstance(child, Tag) else self._append_text(p, str(child), li)
300
299
  for nested in li.find_all(["ul", "ol"], recursive=False):
301
300
  self._render_list(nested, ordered=nested.name == "ol", level=level + 1)
302
301
 
@@ -444,6 +443,14 @@ class _DocxRenderer:
444
443
  run.font.name = "Cascadia Mono"
445
444
  _set_run_shading(run, "F2F2F2")
446
445
 
446
+ def _append_text(self, paragraph, text: str, tag: Tag | None = None) -> None:
447
+ for value, color in _word_safe_fragments(text):
448
+ run = paragraph.add_run(value)
449
+ if tag is not None:
450
+ self._apply_run_style(run, tag)
451
+ if color is not None:
452
+ run.font.color.rgb = RGBColor.from_string(color)
453
+
447
454
  def _style(self, tag: Tag) -> dict[str, str]:
448
455
  inherited: dict[str, str] = {}
449
456
  if isinstance(tag.parent, Tag):
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Iterator
5
6
  import re
6
7
 
7
8
  from bs4 import Tag
@@ -77,6 +78,29 @@ def first_font(value: str | None) -> str | None:
77
78
  return value.split(",", 1)[0].strip().strip("\"'")
78
79
 
79
80
 
81
+ _WORD_SAFE_SYMBOLS: dict[str, tuple[str, str | None]] = {
82
+ "\U0001f4a1": ("提示:", None),
83
+ "\U0001f7e2": ("●", "70AD47"),
84
+ "\U0001f7e1": ("●", "FFC000"),
85
+ "\U0001f534": ("●", "C00000"),
86
+ }
87
+
88
+
89
+ def word_safe_fragments(text: str) -> Iterator[tuple[str, str | None]]:
90
+ """Replace color emoji that Word renders inconsistently across viewers."""
91
+ start = 0
92
+ for index, char in enumerate(text):
93
+ replacement = _WORD_SAFE_SYMBOLS.get(char)
94
+ if replacement is None:
95
+ continue
96
+ if index > start:
97
+ yield text[start:index], None
98
+ yield replacement
99
+ start = index + 1
100
+ if start < len(text):
101
+ yield text[start:], None
102
+
103
+
80
104
  def table_cell_placements(rows: list[Tag]) -> tuple[list[list[tuple[Tag, int, int, int]]], int]:
81
105
  placements: list[list[tuple[Tag, int, int, int]]] = [[] for _ in rows]
82
106
  occupied_until: dict[int, int] = {}
@@ -55,6 +55,7 @@ class DocumentSpreadsheetTests(unittest.TestCase):
55
55
  <tbody><tr><td rowspan="2">产线</td><td>OEE</td><td>85%</td></tr>
56
56
  <tr><td>良率</td><td>99.2%</td></tr></tbody></table>
57
57
  <p><mark>重点</mark> H<sub>2</sub>O 与 10<sup>2</sup></p>
58
+ <p>💡 跨平台提示:🟢 正常,🟡 关注,🔴 告警。</p>
58
59
  </body></html>
59
60
  """
60
61
  with tempfile.TemporaryDirectory() as tmp:
@@ -93,6 +94,17 @@ class DocumentSpreadsheetTests(unittest.TestCase):
93
94
  self.assertTrue(sub.font.subscript)
94
95
  self.assertTrue(sup.font.superscript)
95
96
 
97
+ status = next(p for p in doc.paragraphs if "跨平台提示" in p.text)
98
+ self.assertEqual(status.text, "提示: 跨平台提示:● 正常,● 关注,● 告警。")
99
+ self.assertNotIn("💡", status.text)
100
+ self.assertNotIn("🟢", status.text)
101
+ symbol_colors = [
102
+ str(run.font.color.rgb)
103
+ for run in status.runs
104
+ if run.text == "●"
105
+ ]
106
+ self.assertEqual(symbol_colors, ["70AD47", "FFC000", "C00000"])
107
+
96
108
  def test_create_docx_from_template_renders_placeholders(self) -> None:
97
109
  with tempfile.TemporaryDirectory() as tmp:
98
110
  tmp_path = Path(tmp)
File without changes