Spaces:
Sleeping
Sleeping
File size: 11,661 Bytes
a97d040 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
import json
import re
import textwrap
from graphviz import Digraph
import os
def wrap_text(text, max_chars):
"""
对文本进行自动换行包装,每行最大字符数为 max_chars。
"""
return textwrap.fill(text, width=max_chars)
def parse_md_refs(md_content):
"""
解析 Markdown 内容,提取以 x.y.z 格式标题对应的引用。
对于每个满足格式的 section,其内容中所有形如 [数字] 的引用
将被抽取出来,去重后按数字升序排序,生成类似 "[1,2,3]" 的引用字符串。
如果遇到 undesired header(如 "6 Future Directions" 或 "7 Conclusion"),
则停止后续内容的解析,确保最后一个 section 仅包含到该 header 之前的内容。
返回字典,键为 section 编号(例如 "3.1.1"),值为引用字符串(例如 "[1,2,3]")。
"""
ref_dict = {}
# 处理 Markdown 内容(按行拆分)
lines = md_content.split("\n") if md_content else []
# 匹配 Markdown 标题中以 x.y.z 开头的叶节点(例如 "5.1.1 Neural Topic...")
section_header_regex = re.compile(r'^\s*#+\s*(\d+\.\d+\.\d+).*')
# 匹配 undesired header,如 "6 Future Directions" 或 "7 Conclusion"
undesired_header_regex = re.compile(r'^\s*#+\s*(6 Future Directions|7 Conclusion)\b')
# 匹配引用,例如 [数字]
ref_pattern = re.compile(r'\[(\d+)\]')
current_section = None
current_content = []
for line in lines:
# 如果检测到 undesired header,则先处理当前 section,再退出循环
if undesired_header_regex.match(line):
break
header_match = section_header_regex.match(line)
if header_match:
# 处理上一个 section
if current_section is not None:
all_refs = [int(num) for content_line in current_content for num in ref_pattern.findall(content_line)]
if all_refs:
ref_dict[current_section] = "[" + ",".join(map(str, sorted(set(all_refs)))) + "]"
# 更新当前 section
current_section = header_match.group(1)
current_content = []
else:
if current_section is not None:
current_content.append(line)
# 处理最后一个 section
if current_section is not None and current_content:
all_refs = [int(num) for content_line in current_content for num in ref_pattern.findall(content_line)]
if all_refs:
ref_dict[current_section] = "[" + ",".join(map(str, sorted(set(all_refs)))) + "]"
return ref_dict
def generate_graphviz_png(json_path, output_png_path, md_content=None, title="Document Outline", max_root_chars=20):
"""
从 JSON 文件中读取大纲,构造树状结构,并生成 mindmap 的 PNG 图片。
如果提供了 md_content,则根据 Markdown 内容中以 x.y.z 格式标题对应的引用,
在生成 mindmap 时,对于叶节点(没有子节点且标题以 x.y.z 开头)的标签,
在原文本后追加一个换行,然后添加引用信息(例如 "[1,2,3]"),
且引用经过数字排序。
同时,仅对根节点文本进行自动换行包装,以限制根节点的最大宽度,
其它节点保持原始文本格式。
参数:
json_path: JSON 文件路径(包含大纲)
output_png_path: 输出 PNG 文件路径(不带后缀)
md_content: Markdown 文本内容(字符串),可选
title: 用于替换 mindmap 中根节点的标题,默认 "Document Outline"
max_root_chars: 限制根节点每行最大字符数,默认 20
"""
# 解析 Markdown 内容的引用
ref_dict = parse_md_refs(md_content) if md_content else {}
# 读取 JSON 大纲
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
outline_str = data.get("outline", "")
# 解析形如 [层级, '标题'] 的项
pattern = re.compile(r"\[(\d+),\s*'([^']+)'\]")
items = pattern.findall(outline_str)
items = [(int(level), title) for level, title in items]
# 不需要的标题关键词
undesired_keywords = {"Abstract", "Introduction", "Future Directions", "Conclusion"}
# 过滤掉不需要的条目
filtered_items = [
(lvl, title) for lvl, title in items
if not re.match(r"^\d+\s+(.+)", title) or re.match(r"^\d+\s+(.+)", title).group(1) not in undesired_keywords
]
# 构造树状结构
tree = []
stack = []
for lvl, title_item in filtered_items:
node = {"title": title_item, "children": []}
while stack and lvl <= stack[-1][0]:
stack.pop()
if stack:
stack[-1][1]["children"].append(node)
else:
tree.append(node)
stack.append((lvl, node))
# 生成 Mindmap
dot = Digraph(comment=title, format='png', engine='dot')
dot.graph_attr.update(rankdir='LR', splines='ortho', bgcolor='white', dpi="150")
dot.attr('node', shape='box', style='rounded,filled', fillcolor='white', color='gray')
dot.edge_attr.update(arrowhead='none', color="black")
# 处理根节点
wrapped_title = wrap_text(title, max_root_chars)
dot.node('root', label=wrapped_title, shape='ellipse', style='filled', fillcolor='lightgray')
node_counter = [0]
section_pattern = re.compile(r'^(\d+\.\d+\.\d+)')
def add_nodes(node, parent_id):
current_id = f'node_{node_counter[0]}'
node_counter[0] += 1
safe_label = node['title'].replace('"', r'\"')
# 如果是叶节点且标题以 x.y.z 开头,则追加引用信息(如果存在)
if not node["children"]:
m = section_pattern.match(safe_label)
if m:
section_id = m.group(1)
if section_id in ref_dict:
safe_label += "\n" + ref_dict[section_id]
dot.node(current_id, label=safe_label)
dot.edge(parent_id, current_id)
for child in node.get("children", []):
add_nodes(child, current_id)
for top_node in tree:
add_nodes(top_node, "root")
dot.render(output_png_path, cleanup=True)
print("生成 PNG 文件:", output_png_path + ".png")
return output_png_path + ".png"
def insert_outline_image(png_path, md_content, survey_title):
"""
在给定的 Markdown 内容字符串中查找 "2 Introduction" 这一行,
然后在该位置之前插入 outline 图片的 HTML 代码块,确保渲染时
HTML 块与后续 Markdown 内容间有足够空行分隔开。
参数:
png_path: 要插入的 PNG 图片路径,将作为 img 的 src 属性值。
md_content: Markdown 文件内容字符串。
survey_title: 用于生成图片说明文字的问卷标题。
插入的 HTML 格式如下:
<div style="text-align:center">
<img src="{png_path}" alt="Outline" style="width:100%;"/>
</div>
<div style="text-align:center">
Fig 1. The outline of the {survey_title}
</div>
函数返回更新后的 Markdown 内容字符串。
"""
# 将 Markdown 内容字符串分割成行(保留换行符)
lines = md_content.splitlines(keepends=True)
print(lines)
# 查找包含 "2 Introduction" 的行的索引
intro_index = None
for i, line in enumerate(lines):
if '2 Introduction' in line:
intro_index = i
break
if intro_index is None:
print("没有找到 '2 Introduction' 这一行!")
return md_content
# 确保路径中的反斜杠被替换成正斜杠
png_path_fixed = png_path.replace("\\", "/")
# 构造需要插入的 HTML 代码块,在前后增加空行
html_snippet = (
"\n\n" # 添加换行确保与上文/下文分隔
f'<div style="text-align:center">\n'
f' <img src="{png_path_fixed}" alt="Outline" style="width:100%;"/>\n'
f'</div>\n'
f'<div style="text-align:center">\n'
f' Fig 1. The outline of the {survey_title}\n'
f'</div>\n'
"\n" # 再添加一个空行确保与下方内容分隔
)
print(f"将在第 {intro_index} 行插入如下 HTML 代码块(插入在 '2 Introduction' 之前):\n{html_snippet}")
# 在找到的 "2 Introduction" 这一行之前插入 html_snippet
lines.insert(intro_index, html_snippet)
# 合并所有行,构造更新后的 Markdown 内容
updated_md = "".join(lines)
print("已在 Markdown 内容中插入 outline 图片。")
return updated_md
def insert_outline_figure(png_path, tex_content, survey_title):
"""
在给定的 TeX 文件内容字符串中查找 "2 Introduction" 这一行,
然后在其之前插入一个跨页(双栏)的 figure* 环境,包括整页显示的图片。
它将生成类似如下 LaTeX 片段:
\begin{figure*}[htbp]
\centering
\includegraphics[width=\textwidth]{path/to/xxx.png}
\caption{Fig 1. The outline of the XXX}
\end{figure*}
参数:
png_path: 要插入的 PNG 图片路径
tex_content: TeX 文件内容字符串
survey_title: 用于生成图片 caption 的文献/问卷标题
返回:
更新后的 TeX 文本字符串
"""
# 将 TeX 内容逐行分割(保留换行符)
lines = tex_content.splitlines(keepends=True)
# 查找包含 "2 Introduction" 的行索引
intro_index = None
for i, line in enumerate(lines):
if 'Introduction' in line:
intro_index = i
break
# 如果找不到,就直接返回原文
if intro_index is None:
print("没有找到 'Introduction' 这一行,未执行插入。")
return tex_content
# 构造 TeX 的 figure* 代码块
# 为确保整页,可用 [p] 或者 [htbp],具体可根据排版需要调整
# 也可替换成普通 \begin{figure} ... \end{figure},如果不需要跨双栏
figure_block = (
"\n" # 加一个空行,确保与上文分隔
"\\begin{figure*}[htbp]\n"
" \\centering\n"
f" \\includegraphics[width=\\textwidth]{{{png_path}}}\n"
f" \\caption{{The outline of our survey: {survey_title}}}\n"
"\\end{figure*}\n\n" # 再留一个空行分隔
)
# 在找到的 "2 Introduction" 所在行之前插入 figure 环境
lines.insert(intro_index, figure_block)
# 重新拼接所有行
updated_tex = "".join(lines)
return updated_tex
# 使用示例:
# if __name__ == "__main__":
# png_path = 'src/static/data/info/test_4/outline.png'
# md_content = ''
# survey_title = "My Survey Title"
# updated_md = insert_outline_image(png_path, md_content, survey_title)
# --------------------------
# 使用示例
# --------------------------
if __name__ == "__main__":
json_path = os.path.join("src", "static", "data", "txt", 'test_2', "outline.json")
output_png_path = os.path.join("src", "static", "data", "info", 'test_2', "outline")
md_path = os.path.join("src", "static", "data", "info", 'test_2', f"survey_{'test_2'}_processed.md")
flowchart_results_path = os.path.join("src", "static", "data", "info", 'test_2', "flowchart_results.json")
png_path = generate_graphviz_png(
json_path=json_path,
output_png_path=output_png_path,
md_path=md_path,
title='test',
max_root_chars=30
)
# generate_graphviz_png(json_file_path, output_png_file, md_file_path, title=mindmap_title, max_root_chars=20) |