OVAPortableText 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,412 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Section-related models for OVAPortableText.
5
+ OVAPortableText 的 section 相关模型。
6
+
7
+ A section is the formal document-structure node.
8
+ section 是正式文档结构节点。
9
+
10
+ This version strengthens the ergonomics further.
11
+ 这一版继续强化易用性。
12
+
13
+ The goal is to let report-writing code read naturally, e.g.:
14
+ 目标是让写报告的代码读起来更自然,例如:
15
+
16
+ sec = doc.new_section(...)
17
+ sec.append_paragraph("Hello")
18
+ sec.append_image_with_caption(...)
19
+ sub = sec.new_subsection(...)
20
+ sub.append_bullet_item("Point A")
21
+
22
+ without constantly constructing nested body-item wrappers by hand.
23
+ 而不必不断手动构造嵌套 body-item 包装对象。
24
+ """
25
+
26
+ from typing import Literal
27
+
28
+ from pydantic import Field, model_validator
29
+
30
+ from .base import OvaBaseModel
31
+ from .block_objects import CalloutBlock, ChartBlock, ImageBlock, MathBlock, TableBlock
32
+ from .content import BlockElement, ContentItem, MarkDef, TextBlock, TextChild, TextStyle
33
+ from .text import ListItemStyle
34
+
35
+
36
+ class SubsectionItem(OvaBaseModel):
37
+ """
38
+ Wrapper item used inside `section.body` to hold a nested section.
39
+ 用于 `section.body` 中承载嵌套 section 的包装 item。
40
+ """
41
+
42
+ itemType: Literal["subsection"] = "subsection"
43
+ section: "Section"
44
+
45
+
46
+ class Section(OvaBaseModel):
47
+ """
48
+ Formal section node of the report structure.
49
+ 报告结构中的正式 section 节点。
50
+
51
+ Body can contain:
52
+ body 中可以包含:
53
+ - `ContentItem` for continuous content flow / 连续内容流
54
+ - `SubsectionItem` for nested formal sections / 嵌套正式子章节
55
+ """
56
+
57
+ id: str
58
+ level: int
59
+ title: str
60
+ numbering: str = "auto"
61
+ anchor: str | None = None
62
+ body: list[ContentItem | SubsectionItem] = Field(default_factory=list)
63
+
64
+ @model_validator(mode="after")
65
+ def set_default_anchor(self) -> "Section":
66
+ """
67
+ If anchor is omitted, reuse the section id.
68
+ 如果未显式提供 anchor,则默认复用 section id。
69
+ """
70
+ if self.anchor is None:
71
+ self.anchor = self.id
72
+ return self
73
+
74
+ def append_content(self, content: ContentItem) -> "Section":
75
+ """
76
+ Append a prepared `ContentItem`.
77
+ 追加一个已经构造好的 `ContentItem`。
78
+ """
79
+ self.body.append(content)
80
+ return self
81
+
82
+ def append_block(self, block: BlockElement) -> "Section":
83
+ """
84
+ Append one block-level element as a new content body item.
85
+ 将一个块级元素作为新的 content body item 追加到当前 section。
86
+
87
+ Why wrap one block into a new `ContentItem`?
88
+ 为什么要把单个 block 包成一个新的 `ContentItem`?
89
+ Because `section.body[]` stores content-flow chunks, and the most natural
90
+ append-style API is still "add one visible thing at a time".
91
+ 因为 `section.body[]` 存储的是内容流片段,而最自然的 append 风格
92
+ 仍然是“每次加一个可见内容单元”。
93
+ """
94
+ self.body.append(ContentItem(blocks=[block]))
95
+ return self
96
+
97
+ def append_blocks(self, *blocks: BlockElement) -> "Section":
98
+ """
99
+ Append multiple blocks into one continuous `ContentItem`.
100
+ 把多个 block 作为一个连续的 `ContentItem` 一次性追加。
101
+
102
+ This helper matters when adjacent blocks are semantically continuous,
103
+ e.g. an object immediately followed by its caption.
104
+ 当多个相邻 block 在语义上属于同一连续片段时,这个 helper 很有用,
105
+ 例如对象块后面紧跟题注。
106
+ """
107
+ self.body.append(ContentItem(blocks=list(blocks)))
108
+ return self
109
+
110
+ def append_text_block(self, block: TextBlock) -> "Section":
111
+ """
112
+ Append a prepared text block as one new content item.
113
+ 以新的 content item 形式追加一个现成文本块。
114
+ """
115
+ self.body.append(ContentItem(blocks=[block]))
116
+ return self
117
+
118
+ def append_paragraph(
119
+ self,
120
+ *parts: str | TextChild,
121
+ style: TextStyle = "normal",
122
+ mark_defs: list[MarkDef] | None = None,
123
+ ) -> "Section":
124
+ """
125
+ Append one paragraph-style text block.
126
+ 追加一个段落型文本块。
127
+ """
128
+ self.body.append(
129
+ ContentItem(
130
+ blocks=[
131
+ TextBlock.from_parts(*parts, style=style, mark_defs=mark_defs)
132
+ ]
133
+ )
134
+ )
135
+ return self
136
+
137
+ def append_list_item(
138
+ self,
139
+ *parts: str | TextChild,
140
+ list_item: ListItemStyle = "bullet",
141
+ level: int = 1,
142
+ mark_defs: list[MarkDef] | None = None,
143
+ ) -> "Section":
144
+ """
145
+ Append one list-item block.
146
+ 追加一个列表项 block。
147
+ """
148
+ return self.append_text_block(
149
+ TextBlock.list_block(*parts, list_item=list_item, level=level, mark_defs=mark_defs)
150
+ )
151
+
152
+ def append_bullet_item(
153
+ self,
154
+ *parts: str | TextChild,
155
+ level: int = 1,
156
+ mark_defs: list[MarkDef] | None = None,
157
+ ) -> "Section":
158
+ """
159
+ Append one bullet-list item.
160
+ 追加一个无序列表项。
161
+ """
162
+ return self.append_list_item(*parts, list_item="bullet", level=level, mark_defs=mark_defs)
163
+
164
+ def append_number_item(
165
+ self,
166
+ *parts: str | TextChild,
167
+ level: int = 1,
168
+ mark_defs: list[MarkDef] | None = None,
169
+ ) -> "Section":
170
+ """
171
+ Append one numbered-list item.
172
+ 追加一个有序列表项。
173
+ """
174
+ return self.append_list_item(*parts, list_item="number", level=level, mark_defs=mark_defs)
175
+
176
+ def append_paragraphs(self, *paragraphs: str) -> "Section":
177
+ """
178
+ Append multiple plain paragraphs in order.
179
+ 按顺序追加多个普通段落。
180
+
181
+ Why provide this small helper?
182
+ 为什么增加这个小 helper?
183
+ In real report authoring, users often already have text split into several
184
+ paragraph strings. Writing ``append_paragraph`` repeatedly is fine, but a
185
+ batch helper makes migration code cleaner and easier to read.
186
+ 在真实报告编写中,用户经常已经拿到若干段落字符串。逐个调用
187
+ ``append_paragraph`` 当然可以,但批量 helper 会让迁移代码更干净、
188
+ 更容易阅读。
189
+ """
190
+ for paragraph in paragraphs:
191
+ self.append_paragraph(paragraph)
192
+ return self
193
+
194
+ def append_bullet_items(self, *items: str, level: int = 1) -> "Section":
195
+ """
196
+ Append multiple bullet-list items in order.
197
+ 按顺序追加多个无序列表项。
198
+ """
199
+ for item in items:
200
+ self.append_bullet_item(item, level=level)
201
+ return self
202
+
203
+ def append_number_items(self, *items: str, level: int = 1) -> "Section":
204
+ """
205
+ Append multiple numbered-list items in order.
206
+ 按顺序追加多个有序列表项。
207
+ """
208
+ for item in items:
209
+ self.append_number_item(item, level=level)
210
+ return self
211
+
212
+ def append_subheading(self, text: str) -> "Section":
213
+ """
214
+ Append a non-formal subheading inside this section.
215
+ 在当前 section 内追加一个非正式小标题。
216
+ """
217
+ return self.append_paragraph(text, style="subheading")
218
+
219
+ def append_blockquote(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
220
+ """
221
+ Append a blockquote-style text block.
222
+ 追加一个 blockquote 风格文本块。
223
+ """
224
+ return self.append_paragraph(*parts, style="blockquote", mark_defs=mark_defs)
225
+
226
+ def append_lead(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
227
+ """
228
+ Append a lead paragraph.
229
+ 追加一个导语段落。
230
+ """
231
+ return self.append_paragraph(*parts, style="lead", mark_defs=mark_defs)
232
+
233
+ def append_smallprint(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
234
+ """
235
+ Append a smallprint paragraph.
236
+ 追加一个 smallprint 段落。
237
+ """
238
+ return self.append_paragraph(*parts, style="smallprint", mark_defs=mark_defs)
239
+
240
+ def append_caption(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
241
+ """
242
+ Append a generic caption block.
243
+ 追加一个通用 caption 文本块。
244
+ """
245
+ return self.append_paragraph(*parts, style="caption", mark_defs=mark_defs)
246
+
247
+ def append_figure_caption(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
248
+ """
249
+ Convenience method for `figure_caption` text blocks.
250
+ `figure_caption` 文本块的便捷方法。
251
+ """
252
+ return self.append_paragraph(*parts, style="figure_caption", mark_defs=mark_defs)
253
+
254
+ def append_table_caption(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
255
+ """
256
+ Convenience method for `table_caption` text blocks.
257
+ `table_caption` 文本块的便捷方法。
258
+ """
259
+ return self.append_paragraph(*parts, style="table_caption", mark_defs=mark_defs)
260
+
261
+ def append_equation_caption(self, *parts: str | TextChild, mark_defs: list[MarkDef] | None = None) -> "Section":
262
+ """
263
+ Convenience method for `equation_caption` text blocks.
264
+ `equation_caption` 文本块的便捷方法。
265
+ """
266
+ return self.append_paragraph(*parts, style="equation_caption", mark_defs=mark_defs)
267
+
268
+ def append_image(self, *, id: str, image_ref: str, anchor: str | None = None) -> "Section":
269
+ """
270
+ Create and append an image block in one step.
271
+ 一步创建并追加一个 image 块。
272
+ """
273
+ return self.append_block(ImageBlock(id=id, anchor=anchor, imageRef=image_ref))
274
+
275
+ def append_chart(self, *, id: str, chart_ref: str, anchor: str | None = None) -> "Section":
276
+ """
277
+ Create and append a chart block in one step.
278
+ 一步创建并追加一个 chart 块。
279
+ """
280
+ return self.append_block(ChartBlock(id=id, anchor=anchor, chartRef=chart_ref))
281
+
282
+ def append_table(self, *, id: str, table_ref: str, anchor: str | None = None) -> "Section":
283
+ """
284
+ Create and append a table block in one step.
285
+ 一步创建并追加一个 table 块。
286
+ """
287
+ return self.append_block(TableBlock(id=id, anchor=anchor, tableRef=table_ref))
288
+
289
+ def append_math(self, *, id: str, latex: str, anchor: str | None = None) -> "Section":
290
+ """
291
+ Create and append a math block in one step.
292
+ 一步创建并追加一个 math_block。
293
+ """
294
+ return self.append_block(MathBlock(id=id, anchor=anchor, latex=latex))
295
+
296
+ def append_callout(self, callout: CalloutBlock) -> "Section":
297
+ """
298
+ Append a prepared callout block.
299
+ 追加一个已经构造好的 callout 块。
300
+ """
301
+ return self.append_block(callout)
302
+
303
+ def append_image_with_caption(
304
+ self,
305
+ *,
306
+ id: str,
307
+ image_ref: str,
308
+ caption: str,
309
+ anchor: str | None = None,
310
+ ) -> "Section":
311
+ """
312
+ Append an image block followed by a figure-caption block.
313
+ 追加一个 image 块,并紧接着追加 figure_caption 块。
314
+
315
+ Why does this helper exist if the protocol keeps caption adjacent rather than embedded?
316
+ 为什么协议既然强调 caption 与对象相邻而不是内嵌,还要做这个 helper?
317
+ Because this helper only automates the recommended adjacent pattern;
318
+ it does not change the underlying protocol shape.
319
+ 因为这个 helper 只是自动化协议推荐的“相邻模式”,
320
+ 并没有改变底层协议结构。
321
+ """
322
+ return self.append_blocks(
323
+ ImageBlock(id=id, anchor=anchor, imageRef=image_ref),
324
+ TextBlock.from_parts(caption, style="figure_caption"),
325
+ )
326
+
327
+ def append_chart_with_caption(
328
+ self,
329
+ *,
330
+ id: str,
331
+ chart_ref: str,
332
+ caption: str,
333
+ anchor: str | None = None,
334
+ ) -> "Section":
335
+ """
336
+ Append a chart block followed by a figure-caption block.
337
+ 追加一个 chart 块,并紧接着追加 figure_caption 块。
338
+ """
339
+ return self.append_blocks(
340
+ ChartBlock(id=id, anchor=anchor, chartRef=chart_ref),
341
+ TextBlock.from_parts(caption, style="figure_caption"),
342
+ )
343
+
344
+ def append_table_with_caption(
345
+ self,
346
+ *,
347
+ id: str,
348
+ table_ref: str,
349
+ caption: str,
350
+ anchor: str | None = None,
351
+ ) -> "Section":
352
+ """
353
+ Append a table block followed by a table-caption block.
354
+ 追加一个 table 块,并紧接着追加 table_caption 块。
355
+ """
356
+ return self.append_blocks(
357
+ TableBlock(id=id, anchor=anchor, tableRef=table_ref),
358
+ TextBlock.from_parts(caption, style="table_caption"),
359
+ )
360
+
361
+ def append_math_with_caption(
362
+ self,
363
+ *,
364
+ id: str,
365
+ latex: str,
366
+ caption: str,
367
+ anchor: str | None = None,
368
+ ) -> "Section":
369
+ """
370
+ Append a math block followed by an equation-caption block.
371
+ 追加一个 math_block,并紧接着追加 equation_caption 块。
372
+ """
373
+ return self.append_blocks(
374
+ MathBlock(id=id, anchor=anchor, latex=latex),
375
+ TextBlock.from_parts(caption, style="equation_caption"),
376
+ )
377
+
378
+ def append_subsection(self, section: "Section") -> "Section":
379
+ """
380
+ Append a nested formal subsection.
381
+ 追加一个嵌套正式子章节。
382
+ """
383
+ self.body.append(SubsectionItem(section=section))
384
+ return self
385
+
386
+ def new_subsection(
387
+ self,
388
+ *,
389
+ id: str,
390
+ title: str,
391
+ numbering: str = "auto",
392
+ anchor: str | None = None,
393
+ append: bool = True,
394
+ ) -> "Section":
395
+ """
396
+ Create a new direct child section.
397
+ 创建一个新的直接子章节。
398
+
399
+ The child level defaults to `self.level + 1`, which matches the protocol's
400
+ recommended parent/child level relation.
401
+ 子章节层级默认使用 `self.level + 1`,这与协议建议的父子层级关系一致。
402
+ """
403
+ child = Section(
404
+ id=id,
405
+ level=self.level + 1,
406
+ title=title,
407
+ numbering=numbering,
408
+ anchor=anchor,
409
+ )
410
+ if append:
411
+ self.append_subsection(child)
412
+ return child