PyREUser3 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,289 @@
1
+ """`.user.3` 对象引用树构建逻辑。
2
+
3
+ RSZ 实例之间通过实例编号互相引用,扁平存放。本模块负责从根实例出发,按
4
+ 给定深度把这些引用展开成嵌套 JSON 树,并处理循环引用、缺失实例、用户数据
5
+ 引用以及对象表为空时的根节点推断。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+
13
+ class ExporterTreeMixin:
14
+ """负责从实例表和引用关系中构造紧凑 JSON 树。"""
15
+
16
+ def _count_reference_links(self, value: Any) -> int:
17
+ """统计嵌套结构中的对象引用数量。
18
+
19
+ 参数:
20
+ value (Any): 任意嵌套值(dict / list / 标量)。
21
+
22
+ 返回:
23
+ int: ``ref_instance_id`` 出现的总次数。
24
+ """
25
+ if isinstance(value, dict):
26
+ if "ref_instance_id" in value and isinstance(value["ref_instance_id"], int):
27
+ return 1
28
+ total = 0
29
+ for child in value.values():
30
+ total += self._count_reference_links(child)
31
+ return total
32
+ if isinstance(value, list):
33
+ return sum(self._count_reference_links(child) for child in value)
34
+ return 0
35
+
36
+ def _collect_reference_ids(self, value: Any, out: set[int]) -> None:
37
+ """收集嵌套结构中引用到的实例编号。
38
+
39
+ 参数:
40
+ value (Any): 任意嵌套值(dict / list / 标量)。
41
+ out (set[int]): 用于保存实例编号的输出集合,原地写入。
42
+
43
+ 返回:
44
+ None: 结果通过 ``out`` 参数返回。
45
+ """
46
+ if isinstance(value, dict):
47
+ if "ref_instance_id" in value and isinstance(value["ref_instance_id"], int):
48
+ out.add(value["ref_instance_id"])
49
+ return
50
+ for child in value.values():
51
+ self._collect_reference_ids(child, out)
52
+ return
53
+ if isinstance(value, list):
54
+ for child in value:
55
+ self._collect_reference_ids(child, out)
56
+
57
+ def _infer_roots_when_object_table_empty(
58
+ self,
59
+ idx_map: dict[int, dict[str, Any]],
60
+ parsed_instances: list[dict[str, Any]],
61
+ ) -> list[int]:
62
+ """在对象表为空时推断可能的根实例。
63
+
64
+ 参数:
65
+ idx_map (dict[int, dict[str, Any]]): 以实例编号索引的已解析实例。
66
+ parsed_instances (list[dict[str, Any]]): 按实例表顺序排列的已解析实例列表。
67
+
68
+ 返回:
69
+ list[int]: 推断出的根实例编号;优先返回未被任何实例引用的候选,
70
+ 否则退回所有候选。
71
+ """
72
+ candidates = sorted(
73
+ idx
74
+ for idx, inst in idx_map.items()
75
+ if idx > 0 and isinstance(inst.get("data", {}).get("fields"), dict)
76
+ )
77
+ if not candidates:
78
+ return []
79
+
80
+ referenced: set[int] = set()
81
+ for inst in parsed_instances:
82
+ fields = inst.get("data", {}).get("fields")
83
+ if isinstance(fields, dict):
84
+ # 未被其他实例引用的候选实例通常就是根节点。
85
+ self._collect_reference_ids(fields, referenced)
86
+
87
+ inferred = [idx for idx in candidates if idx not in referenced]
88
+ if inferred:
89
+ return inferred
90
+ return candidates
91
+
92
+ def _auto_pick_tree_depth(
93
+ self, parsed_instances: list[dict[str, Any]], object_roots: list[int]
94
+ ) -> int:
95
+ """根据内容复杂度自动选择紧凑树展开深度。
96
+
97
+ 参数:
98
+ parsed_instances (list[dict[str, Any]]): 已解析实例列表。
99
+ object_roots (list[int]): 根实例编号列表。
100
+
101
+ 返回:
102
+ int: 自动选择的展开深度(1~4,复杂度越高深度越小)。
103
+ """
104
+ ref_links = 0
105
+ for inst in parsed_instances:
106
+ fields = inst.get("data", {}).get("fields")
107
+ if isinstance(fields, dict):
108
+ ref_links += self._count_reference_links(fields)
109
+
110
+ # 实例越多、引用越密集,就越应该降低展开深度,避免 JSON 爆炸。
111
+ complexity = max(len(parsed_instances), ref_links, len(object_roots) * 10)
112
+ if complexity <= 1500:
113
+ return 4
114
+ if complexity <= 8000:
115
+ return 3
116
+ if complexity <= 30000:
117
+ return 2
118
+ return 1
119
+
120
+ def _simplify_value_object(self, value: Any) -> Any:
121
+ """简化只包含 `_Value` 的包装对象。
122
+
123
+ 参数:
124
+ value (Any): 输入值。
125
+
126
+ 返回:
127
+ Any: 形状为 ``{"_Value": x}`` 时返回 ``x``,否则原样返回输入。
128
+ """
129
+ if isinstance(value, dict) and len(value) == 1 and "_Value" in value:
130
+ return value["_Value"]
131
+ return value
132
+
133
+ def _resolve_compact_value(
134
+ self,
135
+ value: Any,
136
+ idx_map: dict[int, dict[str, Any]],
137
+ depth: int,
138
+ visited: set[int],
139
+ ) -> Any:
140
+ """按剩余深度展开紧凑值中的对象引用。
141
+
142
+ 参数:
143
+ value (Any): 输入值(dict / list / 标量)。
144
+ idx_map (dict[int, dict[str, Any]]): 实例编号到已解析实例的映射。
145
+ depth (int): 剩余展开深度。
146
+ visited (set[int]): 当前递归路径已访问实例集合,用于循环检测。
147
+
148
+ 返回:
149
+ Any: 展开后的紧凑值;深度耗尽时保留 ``ref_instance_id`` 引用。
150
+ """
151
+ if isinstance(value, dict):
152
+ if "ref_instance_id" in value and isinstance(value["ref_instance_id"], int):
153
+ target_idx = value["ref_instance_id"]
154
+ if depth <= 0:
155
+ return {"ref_instance_id": target_idx}
156
+ # 每个递归分支都复制已访问集合,避免兄弟分支互相污染循环检测。
157
+ return self._build_compact_tree(
158
+ target_idx, idx_map, depth - 1, set(visited)
159
+ )
160
+ out: dict[str, Any] = {}
161
+ for k, v in value.items():
162
+ out[k] = self._resolve_compact_value(v, idx_map, depth, set(visited))
163
+ return out
164
+ if isinstance(value, list):
165
+ return [
166
+ self._resolve_compact_value(v, idx_map, depth, set(visited))
167
+ for v in value
168
+ ]
169
+ return value
170
+
171
+ def _build_compact_tree(
172
+ self,
173
+ idx: int,
174
+ idx_map: dict[int, dict[str, Any]],
175
+ depth: int,
176
+ instance_info_map: dict[int, dict[str, Any]] | None = None,
177
+ visited: set[int] | None = None,
178
+ ) -> dict[str, Any]:
179
+ """为一个根实例构造紧凑 JSON 树节点。
180
+
181
+ 参数:
182
+ idx (int): 根实例编号。
183
+ idx_map (dict[int, dict[str, Any]]): 已解析实例映射。
184
+ depth (int): 剩余展开深度。
185
+ instance_info_map (dict[int, dict[str, Any]] | None): 可选实例元数据映射,
186
+ 用于为未解析实例补充类名信息。
187
+ visited (set[int] | None): 当前递归路径已访问实例集合,用于循环检测。
188
+
189
+ 返回:
190
+ dict[str, Any]: 以类名为键包裹的紧凑 JSON 节点;遇到循环或缺失实例时
191
+ 返回带 ``cycle`` / ``missing`` / ``unparsed`` 标记的引用节点。
192
+ """
193
+ if visited is None:
194
+ visited = set()
195
+ if idx in visited:
196
+ # 保留引用而不是继续展开,避免循环引用导致无限递归。
197
+ return {"Ref": {"ref_instance_id": idx, "cycle": True}}
198
+ visited.add(idx)
199
+
200
+ inst = idx_map.get(idx)
201
+ if inst is None:
202
+ # 对未解析实例尽量保留类名和引用编号,方便用户定位。
203
+ if instance_info_map is not None and idx in instance_info_map:
204
+ class_name = instance_info_map[idx].get("class_name", "Unknown Class")
205
+ class_name = self._normalize_to_fixed_enum_type(class_name)
206
+ return {class_name: {"ref_instance_id": idx, "unparsed": True}}
207
+ return {"Ref": {"ref_instance_id": idx, "missing": True}}
208
+
209
+ if inst.get("is_userdata_reference"):
210
+ # RSZ 用户数据表中的外部用户数据引用没有内联字段,
211
+ # 导出为路径和引用编号形式。
212
+ class_name = inst.get("class_name", "Unknown Class")
213
+ class_name = self._normalize_to_fixed_enum_type(class_name)
214
+ return {
215
+ class_name: {
216
+ "ref_instance_id": idx,
217
+ "path": inst.get("path", ""),
218
+ }
219
+ }
220
+
221
+ data = inst.get("data", {})
222
+ class_name = data.get("_class", inst.get("class_name", "Unknown Class"))
223
+ class_name = self._normalize_to_fixed_enum_type(class_name)
224
+ fields = data.get("fields", {})
225
+ if not isinstance(fields, dict):
226
+ fields = {}
227
+
228
+ resolved = self._resolve_compact_value_with_info(
229
+ fields, idx_map, depth, instance_info_map, visited
230
+ )
231
+ resolved = self._simplify_value_object(resolved)
232
+
233
+ if isinstance(resolved, dict):
234
+ node_value: Any = resolved
235
+ else:
236
+ # 非字典结果统一包一层 value 键,保持节点结构一致。
237
+ node_value = {"value": resolved}
238
+
239
+ return {class_name: node_value}
240
+
241
+ def _resolve_compact_value_with_info(
242
+ self,
243
+ value: Any,
244
+ idx_map: dict[int, dict[str, Any]],
245
+ depth: int,
246
+ instance_info_map: dict[int, dict[str, Any]] | None,
247
+ visited: set[int],
248
+ ) -> Any:
249
+ """使用实例元数据展开紧凑值。
250
+
251
+ 与 :meth:`_resolve_compact_value` 类似,但在展开引用时把
252
+ ``instance_info_map`` 一并透传,便于为未解析实例补充类名。
253
+
254
+ 参数:
255
+ value (Any): 输入值(dict / list / 标量)。
256
+ idx_map (dict[int, dict[str, Any]]): 已解析实例映射。
257
+ depth (int): 剩余展开深度。
258
+ instance_info_map (dict[int, dict[str, Any]] | None): 实例元数据映射。
259
+ visited (set[int]): 当前路径已访问实例集合,用于循环检测。
260
+
261
+ 返回:
262
+ Any: 展开后的紧凑值;深度耗尽时保留 ``ref_instance_id`` 引用。
263
+ """
264
+ if isinstance(value, dict):
265
+ if "ref_instance_id" in value and isinstance(value["ref_instance_id"], int):
266
+ target_idx = value["ref_instance_id"]
267
+ if depth <= 0:
268
+ return {"ref_instance_id": target_idx}
269
+ return self._build_compact_tree(
270
+ target_idx,
271
+ idx_map,
272
+ depth - 1,
273
+ instance_info_map=instance_info_map,
274
+ visited=set(visited),
275
+ )
276
+ out: dict[str, Any] = {}
277
+ for k, v in value.items():
278
+ out[k] = self._resolve_compact_value_with_info(
279
+ v, idx_map, depth, instance_info_map, set(visited)
280
+ )
281
+ return out
282
+ if isinstance(value, list):
283
+ return [
284
+ self._resolve_compact_value_with_info(
285
+ v, idx_map, depth, instance_info_map, set(visited)
286
+ )
287
+ for v in value
288
+ ]
289
+ return value