Files
k3GPT/main/parse_html.py
2025-11-19 19:42:43 +08:00

979 lines
45 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from lxml import html,etree
from init import gcfg
import json
#自动生成跟目录和引导页
def parse_html_to_ppt(html_string):
"""
<h1> ppt 文件主标题
<h2> ppt 单页主标
<h3> ppt 单页副标/或文件副标题
<h4> ppt
<h5> 一级
<li> 二级
<p> 三级
slide type:
main -- 封面
text
img
table
chart
guide --引导页 <h2的开始页>
catalog --目录页 <h2>标记的集合
"""
# 解析 XML 字符串
root = html.fromstring(html_string)
ppt=[]
slide={"type":"text","title":"引言","subtitle":None,"content":[]} #单页
catalogs=[]
guide =1
catalog_auto = True #自动生成目录
guide_auto = True #自动生成引导
# 使用 iter() 遍历所有节点
for element in root.iter():
print(f"标签: {element.tag}, 属性: {element.attrib}, 文本: {element.text.strip() if element.text else ''}")
all_text = element.xpath("string()")
if element.tag=="h2": #单页
if slide not in ppt:
ppt.append(slide)
if guide_auto and (len(catalogs)==0 or catalogs[-1]!=all_text): #新的页
catalogs.append(all_text)
slide={"type":"guide","title":all_text,"guide":f"{guide:02d}"} #引导页
guide +=1
ppt.append(slide)
slide={"type":"text","title":all_text,"subtitle":None,"content":[]} #单页标题
ppt.append(slide)
elif element.tag=="h1": #封面
if slide["content"]: #引言有内容
ppt.append(slide)
slide={"type":"main","title":all_text,"subtitle":None,"content":[]} #封面标题
ppt.insert(0,slide) #封面第一
elif element.tag in ["h3","h4"]: #副标题
if slide["subtitle"]:#如果已经有副标题则生成新的单页
old_slide = slide
slide={"type":"text","title":old_slide["title"],"subtitle":all_text,"content":[]} #副标题
ppt.append(slide)
else:
slide["subtitle"] = all_text
elif element.tag=="h5": #一级
slide["content"].append({"text":all_text, "size": 22, "space_after": 4,"level":1,"bold":True})
elif element.tag=="li": #二级
if element.getparent().tag=="ol":
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":2,"number":True})
else:
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":2,"bullet":True})
elif element.tag=="p": #三级
if all_text.startswith("[chart]"):
slide["type"]="chart"
slide["chart_type"]=all_text
elif all_text.startswith("[catalogs]"):
catalog_auto = False
slide={"type":"catalog","data":[],"content":[]} #目录页
ppt.insert(1,slide)
else:
all_text = element.xpath("string()")
if all_text!="":
slide["content"].append({"text":all_text, "size": 18, "space_after": 12,"level":3})
elif element.tag=="img":
slide["type"] = "image"
if "images" not in slide:
slide["images"]=[]
src = element.get("src")
path = src.replace("/api/",f'{gcfg["fs"]["path"]}/')
slide["images"].append({"path":path,"caption":element.get("alt")})
elif element.tag=="table":
t_header=[]
t_body=[]
slide["data"] = t_body
slide["header"] = []
t_header =[]
slide["type"] = "table" #有表
elif element.tag=="th":
t_header.append(all_text)
slide["header"] = t_header
elif element.tag=="tr":
t_tr =[]
if not slide["header"]: #为空
slide["header"] = t_tr
t_body.append(t_tr)
elif element.tag=="td":
all_text = element.xpath("string()")
t_tr.append(all_text)
elif element.tag=="catalog": #自定义目录页
catalog_auto = False
slide={"type":"catalog","data":all_text.split("\n")} #目录页
ppt.insert(1,slide)
elif element.tag=="guide": #自定义引导页
guide_auto = False
slide={"type":"guide","title":all_text,"guide":f"{guide:02d}"} #引导页
guide +=1
ppt.append(slide)
elif element.tag == "code":
try:
#自己定义的数据格式,用于图表显示
slide["data"] = json.loads(all_text)
except:
pass
if element.getparent().tag=='pre':
slide["content"].append({"text":all_text, "size": 12, "space_after": 12,"level":0,"code":True})
elif element.tag == "blockquote":#引用
slide["content"].append({"text":all_text, "size": 18, "space_after": 12,"level":0,"blockquote":True})
# if element.getparent().tag=="p" or element.getparent().getparent().tag=="p" :
# #父有直接的文本,需要将粗体内容给串进来
# if (element.getparent().text or element.getparent().getparent().text) and len(slide["content"]) >0:
# if element.text:
# slide["content"][-1]["text"] += element.text
# if element.tail:
# slide["content"][-1]["text"] += element.tail
# elif element.getparent().text==None and element.text and len(slide["content"]) >0:
# #提升等级,内容全提升
# slide["content"][-1]["level"] = 2
# else:
# if element.getparent().tag=="p" and element.text and len(ppt) >0 and len(slide["content"]) >0:
# slide["content"][-1]["text"] +=element.text
#最后一页看看
if slide not in ppt:
ppt.append(slide)
#目录页
if catalog_auto and catalogs:
slide={"type":"catalog","data":catalogs} #目录页
ppt.insert(1,slide)
return ppt
#海报的格式
def parse_html_to_poster(html_string):
"""
<h1> 海报的主标题
<h2> 内容列表的单块内容的主标题
<h3> 内容列表的单块内容的主副题标/海报的副标题
<h4> 海报的小结,内容的摘要
<h5> 海报末尾信息
"""
# 解析 XML 字符串
root = html.fromstring(html_string)
#海报的最后结构
poster={
"主标":"",
"副标":"",
"内容列表":[],
"报尾":[],
"报尾图片":[],
"结语":"",
"logo_path": f'{gcfg["fs"]["path"]}/img/{gcfg["fs"]["logo"]}'
}
ppt = poster["内容列表"]
#防止出错使用
slide={"主标":"","副标":"","content":[]}
#报头
is_header = True #碰到h2报头结束
#报尾
is_footer=False
# 使用 iter() 遍历所有节点
for element in root.iter():
print(f"标签: {element.tag}, 属性: {element.attrib}, 文本: {element.text.strip() if element.text else ''}")
all_text = element.xpath("string()")
if element.tag=="h2": #单页
is_header = False
slide={"主标":all_text,"副标":0,"content":[]}
ppt.append(slide)
elif element.tag=="h1": #封面
poster["主标"] = all_text
elif element.tag=="h3": #副标题
if len(ppt)==0: #海报副标题
poster["副标"] = all_text
else:
#内容支持多个副标题,副标题直接放置入内容中
slide["副标"] +=1
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":1})
elif element.tag=="h4":
poster["结语"]= all_text
elif element.tag=="h5":
if is_footer:
poster["报尾"].append(all_text)
else:
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":2,"bullet":True})
elif element.tag=="li":
if element.getparent().tag=="ol":
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":2,"number":True})
else:
slide["content"].append({"text":all_text, "size": 20, "space_after": 8,"level":2,"square":True})
elif element.tag=="p":
if is_header:
poster["结语"] += all_text
elif is_footer:
poster["报尾"].append(all_text)
else:
if element.text:
slide["content"].append({"text":element.text, "size": 18, "space_after": 12,"level":3})
else:
slide["content"].append({"text":"", "size": 18, "space_after": 12,"level":3})
elif element.tag=="code":
try:
slide["data"] = json.loads(all_text)
except:
slide["data"] = None
elif element.tag=="img":
if poster["报尾"]:
src = element.get("src")
path = src.replace("/api/",f'{gcfg["fs"]["path"]}/')
poster["报尾图片"].append({"path":path,"caption":element.get("alt")})
else:
src = element.get("src")
path = src.replace("/api/",f'{gcfg["fs"]["path"]}/')
slide["content"].append({"image":True,"path":path,"caption":element.get("alt")})
elif element.tag=="table":
t_body=[]
slide["content"].append({"table":True,"data":t_body})
elif element.tag=="th":
t_tr.append(all_text)
elif element.tag=="tr":
t_tr =[]
t_body.append(t_tr)
elif element.tag=="td":
t_tr.append(all_text)
elif element.tag=="hr":
is_footer = True
elif element.tag=="a":
if all_text and len(ppt) >0 and len(slide["content"]) >0:
slide["content"][-1]["text"] +=all_text
elif element.tag=="span":
if element.getparent().tag=="p" and element.text and len(ppt) >0 and len(slide["content"]) >0:
slide["content"][-1]["text"] +=element.text
elif element.tag=="strong":
if element.getparent().tag=="p" or element.getparent().getparent().tag=="p":
if element.text and len(ppt) >0 and len(slide["content"]) >0:
if slide["content"][-1]["text"]: #代表已经有内容strong不是开头的标记,不做升级处理
slide["content"][-1]["text"] += element.text +(element.tail if element.tail else "")
else:
slide["content"][-1]["level"] = 2
if element.tail and element.tail[0] in [":",""]:
slide["content"][-1]["text"] = element.text+":"
slide["content"].append({"text":element.tail[1:], "size": 18, "space_after": 12,"level":3})
elif element.tail:
slide["content"][-1]["text"] = element.text
slide["content"].append({"text":element.tail, "size": 18, "space_after": 12,"level":3})
else:
slide["content"][-1]["text"] = element.text
elif element.tag == "blockquote":
slide["content"].append({"text":all_text, "size": 18, "space_after": 12,"level":0,"blockquote":True})
elif element.tag == "code":
slide["content"].append({"text":all_text, "size": 18, "space_after": 12,"level":0,"code":True})
#最后一页看看
if slide not in ppt:
ppt.append(slide)
print(poster)
return poster
import re
def protect_code_blocks(html_str):
"""将 code 块替换为占位符,防止被解析"""
code_blocks = []
def replace(m):
code_blocks.append(m.group(0))
return f"__CODE_BLOCK_{len(code_blocks)-1}__"
protected = re.sub(r'<code[^>]*>.*?</code>', replace, html_str, flags=re.DOTALL | re.I)
return protected, code_blocks
#转换html成中间格式方便wangeditor编辑
def trans_html_to_format(html_string):
"""
WangEditor的缺陷:
<h5>的标记中不能有<ul>或<ol>
<li>中不能有<table>
<h4>升级到<h3>
"""
import io
#保护code标签数据的原样性
protected_html, codes = protect_code_blocks(html_string)
context = etree.iterparse(
io.BytesIO(html_string.encode('utf-8')), # 从字符串解析(实际文件用 open('file.xml')
html=True,
encoding='utf-8', # 指定编码
events=('start', 'end') # 捕获标签开始和结束事件
)
#print("原始信息",html_string)
content=[]
index=1
#嵌套层次
ol_ul_muli=False
li_level = 0 #li的级别1-<h5>,2-<li>,3级以上<p>
h5_close=False #提前闭合的H5
table_close = False #提前闭合的表格, </li></ul><table></table><ul><li>
table_close_ul = "ul" #默认是ul
#代码处理
code_index=0
has_h1=False #是否存在h1的标题
first_element=None #第一个元素
#hr的数量
hr_count = html_string.count("<hr>")
print(hr_count,"个hr, 超过2个就需要提升至h2")
h3_h5_to_h2 = False
h3_count = html_string.count("<h3>")
h4_to_h3 = True
#<blockquote> 包含表格则移除
blockquote_remove = False
for event,element in context:
if event == 'start': #开始解析
#第一个元素
if not first_element:
first_element = element.text
#节点元素分析
if element.tag =="hr": #提升hr为h2单独的一个页面
next_item = element.getnext()
if hr_count >=2: ##多调线,不是海报
if next_item is not None and next_item.tag in ["h3","h4","h5"]: #直接升h2
h3_h5_to_h2=True
continue
else:
b_element=f"<hr"
elif next_item is not None and next_item.tag=="h5": #一条线,海报的脚注不做处理
b_element=f"<hr"
else:
b_element=f"<h2>0x{index:02d}</h2"
index +=1
elif element.tag in ["h3","h4","h5"]:
all_text = element.xpath("string()")
if h3_h5_to_h2:#全面升级为h2
b_element=f"<h2>{all_text}</h2>"
#h3_h5_to_h2=False
content.append(b_element)
continue
elif h4_to_h3 and element.tag=="h4":
b_element=f"<h3>{all_text}</h3>"
content.append(b_element)
continue
elif element.getparent().tag=="li": #直接降级为p
b_element=f"<p>{all_text}</p>"
content.append(b_element)
continue
else:
b_element = f'<{element.tag}'
elif element.tag in ["ol","ul"]: #处理嵌套的ol和ul
if ol_ul_muli:
print(f"处理嵌套{li_level+1}")
li_level +=1
if li_level==2: #二级保留ul或ol
content.append("</h5>") #提前闭合一级
h5_close = True
b_element = f'<{element.tag}'
else:
#其它全部忽略
continue
else:
#首次判断,是否嵌套
ol_s = element.xpath(".//ol")
ul_s = element.xpath(".//ul")
if len(ol_s) >0 or len(ul_s) >0:
ol_ul_muli = True
li_level +=1
print(f"存在嵌套情况ol:{len(ol_s)}, ul:{len(ul_s)}开启1级嵌套处理,忽略ul或ol")
continue
else:# 不嵌套
b_element = f'<{element.tag}'
elif element.tag=="li": #升级处理
if ol_ul_muli and li_level==1:
b_element = f'<h5'
elif ol_ul_muli and li_level>=3:
b_element = f'<br><i'
else:
b_element = f'<{element.tag}'
elif element.tag=="code":
content.append(codes[code_index])
code_index +=1
continue
elif element.tag=="table":
if element.getparent().tag=="li":
#需要提前闭合
table_close_ul = element.getparent().getparent().tag
content.append(f"</li></{table_close_ul}>") #提前闭合一级
table_close = True
#正常处理表格
b_element = f'<{element.tag}'
elif element.tag=="blockquote":
elements_with_any_table = element.xpath('//*[.//table]')
if len(elements_with_any_table) >0:
blockquote_remove = True
continue
else:
b_element = f'<{element.tag}'
else:
if element.tag=="h1":
has_h1=True
b_element = f'<{element.tag}'
#保留属性
if element.attrib:
for k,v in element.attrib.items():
b_element += f' {k}="{v}"'
#文字内容
if element.text:
b_element += f'> {element.text}'
else:
b_element += '>'
content.append(b_element)
#结束解析
elif event == 'end': #结束解析
if element.tag !='hr':
if element.tag=="code" and element.tail: #code之后的内容
content.append(element.tail)
continue
elif element.tag in ["h3","h4","h5"]:
if h3_h5_to_h2:#全面升级为h2
h3_h5_to_h2=False
continue
elif element.getparent().tag=="li": #直接降级为p
continue
elif h4_to_h3 and element.tag=="h4":
continue
else:
tag = element.tag
elif ol_ul_muli and element.tag in ["ol","ul"]:
print(f"结束嵌套{li_level}")
if li_level==2: #保留
tag = element.tag
li_level -=1
else:
if li_level==1: #结束
ol_ul_muli = False
li_level -=1
continue
elif ol_ul_muli and element.tag =="li": #处理嵌套的ol和ul的结束标记
if li_level >=3:
tag = 'i'
elif li_level ==2:
tag = element.tag
elif li_level ==1 and h5_close: #提前闭合不予处理
h5_close = False
continue
elif li_level ==1:
tag = "h5"
elif element.tag=="table" and table_close:
content.append(f"</table><{table_close_ul}><li>") #提前闭合一级
table_close = False
continue
elif element.tag=="blockquote" and blockquote_remove:
#忽略
continue
else:
tag = element.tag
if element.tail:
f_element = f"</{tag}>{element.tail}"
else:
f_element =f"</{tag}>"
content.append(f_element)
#end for
if not has_h1:
content.insert(0,f'<h1>{first_element.split("")[0]}</h1>')
#print("".join(content))
return "".join(content)
#
if __name__=="__main__":
html_string ="""
<p>您的问题聚焦于 <strong>XML/HTML 解析中标签开始start和标签结束end的遍历方式</strong>,这在 Python 的 <code>lxml</code> 库中通常通过 <code><strong>iterparse</strong></code> 实现。以下是针对此场景的清晰解答:</p>
<hr/><h3>✅ 正确方式:使用 <code>lxml.etree.iterparse</code> 的 <code>events</code> 参数</h3><p><code>lxml</code> 提供了 <code>iterparse</code> 方法,支持在解析过程中捕获 <code><strong>标签开始start</strong></code> 和 <code><strong>标签结束end</strong></code> 的事件。这是处理大文件或流式解析的高效方式,避免一次性加载整个文档。</p><h4>📜 代码示例</h4><pre><code class="language-python">from lxml import etree
# 示例 XML 内容
xml_content =
&lt;root&gt;
&lt;item id="1"&gt;Text1&lt;/item&gt;
&lt;item id="2"&gt;Text2&lt;/item&gt;
&lt;/root&gt;
# 关键:设置 events=('start', 'end')
context = etree.iterparse(
io.BytesIO(xml_content.encode()), # 从字符串解析(实际文件用 open('file.xml')
events=('start', 'end') # 捕获标签开始和结束事件
)
for event, elem in context:
if event == 'start':
print(f"【开始标签】{elem.tag} (属性: {elem.attrib})")
elif event == 'end':
print(f"【结束标签】{elem.tag}")
elem.clear() # 清理内存,避免内存泄漏(重要!)
</code></pre><h4>🔍 输出结果</h4><pre><code >【开始标签】root (属性: {})
【开始标签】item (属性: {'id': '1'})
【结束标签】item
【开始标签】item (属性: {'id': '2'})
【结束标签】item
【结束标签】root
</code></pre><hr/><h3>⚠️ 关键说明</h3><ol><li>events 参数: ('start', 'end'):同时触发标签开始和结束事件。 仅需 ('start',):仅捕获开始标签(如需快速遍历结构)。 仅需 ('end',):仅捕获结束标签(如需处理内容后清理)。</li><li>为什么需要 elem.clear() iterparse 会保留已解析的元素在内存中,调用 elem.clear() 可立即释放内存,避免大文件解析时内存溢出。</li><li>与普通遍历的区别: 方法 适用场景 内存效率 标签事件支持 iterparse(events=('start','end')) 流式解析(大文件/实时处理) ✅ 高(只保留当前元素) ✅ 支持开始/结束 root.iterchildren() 遍历子元素(需先加载完整文档) ❌ 低(全量加载) ❌ 仅元素内容</li></ol><hr/><h3>❌ 常见误区</h3><ul><li><strong>错误方式</strong>:试图用 <code>Element</code> 对象的属性(如 <code>element.tag</code>)直接区分开始/结束标签<br>→ <code>Element</code> 本身没有“开始/结束”属性,它是一个<strong>已解析的节点</strong>,标签的开始/结束是<strong>解析过程中的事件</strong>。</li><li><strong>正确逻辑</strong>:标签的“开始”和“结束”是<strong>解析器触发的事件</strong>,而非 <code>Element</code> 对象的属性。</li></ul><hr/><h3>💡 附加建议</h3><ul><li><strong>处理文本内容</strong>:如需获取标签内的文本,可在 <code>end</code> 事件中操作 <code>elem.text</code></li><li><strong>官方文档</strong><a href="https://lxml.de/parsing.html#iterparse" target="">lxml iterparse 文档</a></li></ul><blockquote>本次问题与您历史询问的 python lxml 中 element 都有哪些属性 相关——iterparse 的 events 机制是理解标签解析流程的关键,而 Element 属性(如 tag, attrib, text是事件处理后的操作对象。</blockquote><p>如需进一步验证,可运行上述代码测试。如有其他解析场景,欢迎补充细节! 😊</p>
"""
html_string2 = """
<h1>Gartner对DSPM数据安全态势管理的演进过程</h1>
<p>根据提供的联网搜索结果我将为您梳理Gartner对数据安全态势管理(DSPM)的演进过程:</p>
<h2>1. 初步提出阶段2021年</h2>
<p>Gartner在《2021数据安全技术成熟度曲线》报告中首次提出"数据安全平台"(Data Security Platforms, DSP)的概念为DSPM奠定了基础。该报告定义DSP为"以数据安全为中心的产品和服务,旨在跨数据类型、存储孤岛和生态系统集成数据的独特保护需求"。</p>
<h2>2. 正式定义与推广阶段2022年</h2>
<p>在2022年发布的《2022年数据安全技术成熟度曲线》报告中Gartner正式提出"数据安全态势管理"(Data Security Posture Management, DSPM)这一新概念,并给出了明确的定义:</p>
<blockquote>
<p>"DSPM旨在为企业组织提供更加全面的数据安全可见性以便深入了解敏感数据的位置、访问权限、如何被利用以及如何存储等安全状况态势信息。"</p>
</blockquote>
<p>这一阶段DSPM被定位为解决企业数据安全可见性不足问题的关键方案标志着数据安全技术从传统的边界防护向数据资产视角的转变。</p>
<h2>3. 功能扩展与深化阶段2023-2025年</h2>
<p>随着数据安全挑战的演变Gartner在后续报告中进一步扩展了DSPM的内涵和功能</p>
<ul>
<li><p><strong>2023年</strong>Gartner关注到市场出现了提供不同安全能力及组合的数据安全平台或数据安全管理平台开始强调DSPM在跨数据类型、存储孤岛和生态系统中的集成能力。</p>
</li>
<li><p><strong>2024-2025年</strong>在Gartner发布的《2025年数据安全技术成熟度曲线》报告中DSPM被列为关键数据安全技术之一其功能进一步扩展</p>
<ul>
<li>DSPM解决方案已能"发现、评估和监控GenAI对数据的访问"</li>
<li>DSPM供应商正在增强其工具以支持"组织安全地使用第三方GenAI服务并通过自定义GenAI架构安全地访问数据"</li>
<li>DSPM被纳入Gartner数据安全技术成熟度曲线中的核心条目与数据分类、数据发现、元数据管理等技术并列</li>
</ul>
</li>
</ul>
<h2>演进趋势总结</h2>
<p>Gartner对DSPM的演进过程体现了数据安全理念从"以网络边界为中心""以数据资产为中心"的转变,具体表现为:</p>
<ol>
<li>从基础的数据发现和分类,到提供全面的数据安全可见性</li>
<li>从静态的数据保护,到动态的实时监控和态势感知</li>
<li>从传统IT环境到云原生、混合环境以及生成式AI等新兴场景的扩展</li>
</ol>
<p>DSPM的演进反映了数据安全技术从被动防御到主动管理、从局部控制到全局态势感知的发展趋势成为企业数据安全治理的关键组成部分。</p>
<p>目前DSPM已成为Gartner数据安全技术成熟度曲线中的核心组成部分随着数据安全挑战的不断演变DSPM的功能和应用场景也在持续扩展和深化。</p>
"""
html_string3="""
<p>您的问题是关于 XPath 的 <code>//</code> 语法,但问题描述不完整(可能想问“<code>//</code> 在 XPath 中的含义”或“如何在 lxml 中使用 <code>//</code>”)。结合历史对话中您提到的 <strong>“lxml python 查找子元素”</strong> 和 <strong>“lxml python 查找所有子元素”</strong>,我来清晰解释:</p>
<hr>
<h3>🔍 XPath 中 <code>//</code> 的含义</h3>
<p>在 XPath 中,<code>//</code> 是 <strong>“从根节点递归查找所有匹配节点”</strong> 的通配符。它的作用是:</p>
<ul>
<li><strong>忽略层级</strong>:从文档根节点开始,<strong>递归查找所有后代节点</strong>(包括子元素、孙元素等),而不仅限于直接子元素。</li>
<li><strong>等价于</strong><code>descendant::</code>(后代轴)。</li>
</ul>
<h4>✅ 示例说明</h4>
<table>
<thead>
<tr>
<th>XPath 表达式</th>
<th>含义</th>
<th>是否匹配嵌套元素</th>
</tr>
</thead>
<tbody><tr>
<td><code>//div</code></td>
<td>文档中<strong>所有</strong> <code>&lt;div&gt;</code> 元素(无论层级)</td>
<td>✅ 是</td>
</tr>
<tr>
<td><code>//div[@class='a']</code></td>
<td>所有 <code>class="a"</code> 的 <code>&lt;div&gt;</code></td>
<td>✅ 是</td>
</tr>
<tr>
<td><code>div</code></td>
<td><strong>当前节点的直接子元素</strong>(不递归)</td>
<td>❌ 否</td>
</tr>
<tr>
<td><code>./div</code></td>
<td>当前节点的<strong>直接子元素</strong>(与 <code>div</code> 等价)</td>
<td>❌ 否</td>
</tr>
<tr>
<td><code>.//div</code></td>
<td><strong>当前节点的后代元素</strong>(递归查找)</td>
<td>✅ 是</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 <strong>关键区分</strong></p>
<ul>
<li>您历史问题中的 <strong>“查找子元素”</strong> 通常指 <strong>直接子元素</strong>(用 <code>div</code> 或 <code>./div</code>)。</li>
<li>您历史问题中的 <strong>“查找所有子元素”</strong> 实际应为 <strong>“查找所有后代元素”</strong>(用 <code>//div</code>),因为 XPath 中没有“所有子元素”的概念(子元素仅指直接子级)。</li>
</ul>
</blockquote>
<hr>
<h3>💻 在 lxml Python 中的使用示例</h3>
<p>假设您用 <code>lxml</code> 解析 HTML/XML以下是常见用法</p>
<h4>1⃣ 查找 <strong>所有后代元素</strong>(用 <code>//</code></h4>
<pre><code class="language-python">from lxml import etree
html = '''
&lt;html&gt;
&lt;body&gt;
&lt;div class="outer"&gt;
&lt;div class="inner"&gt;Hello&lt;/div&gt; &lt;!-- 嵌套在 div.outer 内 --&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
'''
tree = etree.HTML(html)
# 查找所有 &lt;div&gt; 元素(包括嵌套的)
all_divs = tree.xpath('//div')
print([elem.get('class') for elem in all_divs])
# 输出: ['outer', 'inner'] # 包含了所有层级的 div
</code></pre>
<h4>2⃣ 查找 <strong>直接子元素</strong>(用 <code>div</code> 或 <code>./div</code></h4>
<pre><code class="language-python"># 查找 body 的直接子元素(仅 body 下的第一层)
direct_children = tree.xpath('/html/body/div')
print([elem.get('class') for elem in direct_children])
# 输出: ['outer'] # 仅匹配 body 下的直接 div不匹配 inner
# 或用相对路径(当前节点 body 的直接子 div
body = tree.xpath('/html/body')[0]
direct_children = body.xpath('div') # 注意:这里用 'div' 而非 '//div'
print([elem.get('class') for elem in direct_children])
# 输出: ['outer'] # 仅匹配 body 的直接子 div
</code></pre>
<hr>
<h3>❌ 常见误区</h3>
<ul>
<li><strong>错误</strong><code>//div</code> 会被误认为“查找所有子元素” → <strong>实际是查找所有后代元素</strong>(包括嵌套)。</li>
<li><strong>正确</strong>:要查“直接子元素”,必须用 <strong><code>div</code> 或 <code>./div</code></strong>(不带 <code>//</code>)。</li>
</ul>
<hr>
<h3>📌 总结</h3>
<table>
<thead>
<tr>
<th>您的历史问题</th>
<th>正确 XPath 方案</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>“查找子元素”(直接子级)</td>
<td><code>div</code> 或 <code>./div</code></td>
<td>仅当前节点的子元素,不递归</td>
</tr>
<tr>
<td>“查找所有子元素”(实际应为“所有后代”)</td>
<td><code>//div</code></td>
<td><strong>递归查找所有层级的 div</strong></td>
</tr>
</tbody></table>
<p>如果需要进一步验证或具体场景的代码示例,请补充您的 HTML 结构或明确需求,我会提供针对性解答! 😊</p>
"""
html_string4="""
<p>《中华人民共和国数据安全法》以下简称《数据安全法》于2021年9月1日起正式施行是我国首部在数据安全领域的基础性、综合性法律旨在应对日益复杂的数据安全风险维护国家安全、公共利益及个人与组织的合法权益。以下是该法的核心要点总结</p>
<hr>
<h3>一、<strong>立法目的与基本原则</strong></h3>
<ul>
<li><strong>核心目标</strong><br>“<strong>规范数据处理活动,保障数据安全,促进数据开发利用</strong>”——三者构成递进关系,即通过规范行为保障安全,进而实现数据的合法有序利用。</li>
<li><strong>立法依据</strong><br>体现<strong>总体国家安全观</strong>,聚焦数据安全领域的关键问题,确立国家数据主权和安全底线。</li>
</ul>
<hr>
<h3>二、<strong>数据分类分级管理制度</strong></h3>
<ul>
<li><strong>核心数据</strong><br>关系国家安全、国民经济命脉、重要民生、重大公共利益的数据,属于“国家核心数据”,实行<strong>更加严格的管理制度</strong>。</li>
<li><strong>重要数据</strong><br>由各行业主管部门根据实际制定识别标准,实行分类分级保护。</li>
<li><strong>分类分级是基础</strong><br>为后续的风险评估、安全审查、应急响应等制度提供依据。</li>
</ul>
<hr>
<h3>三、<strong>数据全生命周期安全管理</strong></h3>
<p>《数据安全法》要求对数据的<strong>收集、存储、使用、加工、传输、提供、公开、销毁</strong>等各个环节落实安全保护义务,强调:</p>
<ul>
<li>采取技术措施和其他必要措施,保障数据安全;</li>
<li>建立数据安全风险评估、监测预警、应急处置机制;</li>
<li>实施数据安全事件的报告与处置。</li>
</ul>
<hr>
<h3>四、<strong>数据安全监管职责分工</strong></h3>
<ul>
<li><strong>国家网信部门</strong><br>负责统筹协调网络数据安全和相关监管工作。</li>
<li><strong>行业主管部门</strong><br>工业、电信、交通、金融、自然资源、卫生健康、教育、科技等领域的主管部门,承担本行业、本领域的数据安全监管职责。</li>
<li><strong>公安机关、国家安全机关</strong><br>在各自职责范围内依法履行数据安全监管职责。</li>
</ul>
<hr>
<h3>五、<strong>数据跨境流动与域外管辖</strong></h3>
<ul>
<li><strong>域外适用原则</strong><br>在境外开展数据处理活动,若<strong>损害中华人民共和国国家安全、公共利益或公民、组织合法权益的</strong>,依法追究法律责任。</li>
<li><strong>强调数据主权与管辖权</strong><br>明确我国对数据安全具有<strong>域外法律效力</strong>,防止境外数据活动威胁我国安全。</li>
</ul>
<hr>
<h3>六、<strong>数据安全审查制度</strong></h3>
<ul>
<li>建立<strong>数据安全审查制度</strong>,对影响或可能影响国家安全的数据处理活动进行审查。</li>
<li>特别适用于涉及国家核心数据、重要数据的跨境传输、重大基础设施项目等。</li>
</ul>
<hr>
<h3>七、<strong>违法行为的法律责任</strong></h3>
<ul>
<li>对违反数据安全义务的行为,设定明确的行政处罚:<ul>
<li>警告、罚款(最高可达<strong>1000万元</strong>或上一年度营业额的<strong>5%</strong></li>
<li>暂停相关业务、停业整顿、吊销业务许可证;</li>
<li>对直接负责的主管人员和其他直接责任人员处以罚款;</li>
<li>构成犯罪的,依法追究刑事责任。</li>
</ul>
</li>
</ul>
<hr>
<h3>八、<strong>数据主体权利保障</strong></h3>
<ul>
<li>明确个人、组织在数据处理活动中享有的权利,包括:<ul>
<li><strong>知情权</strong>:了解数据处理目的、方式、范围;</li>
<li><strong>同意权</strong>:在合法基础上获得明确同意;</li>
<li><strong>查阅、复制、更正、删除</strong>等权利;</li>
<li><strong>数据可携权</strong>(虽未明文规定,但可通过解释实现)。</li>
</ul>
</li>
</ul>
<hr>
<h3>九、<strong>安全技术与制度保障</strong></h3>
<ul>
<li>推动建立<strong>网络安全等级保护制度</strong>作为基础;</li>
<li>鼓励采用隐私计算、数据脱敏、加密等技术手段实现“<strong>可用不可见</strong>”、“<strong>可控可计量</strong>”的数据利用模式;</li>
<li>支持数据安全技术创新和产业发展。</li>
</ul>
<hr>
<h3>十、<strong>配套制度建设</strong></h3>
<ul>
<li>《数据安全法》为后续出台《个人信息保护法》《数据出境安全评估办法》《关键信息基础设施安全保护条例》等配套法规奠定基础形成“1+X”数据安全法律体系。</li>
</ul>
<hr>
<h3>✅ 总结:《数据安全法》的核心价值</h3>
<table>
<thead>
<tr>
<th>维度</th>
<th>核心要点</th>
</tr>
</thead>
<tbody><tr>
<td><strong>定位</strong></td>
<td>国家数据安全的“基本法”、“纲领性法律”</td>
</tr>
<tr>
<td><strong>核心理念</strong></td>
<td>安全与发展并重,以安全保发展,以发展促安全</td>
</tr>
<tr>
<td><strong>关键制度</strong></td>
<td>分类分级、风险评估、监测预警、应急处置、安全审查、跨境监管</td>
</tr>
<tr>
<td><strong>执法重点</strong></td>
<td>国家核心数据、重要数据、跨境数据流动、重大数据安全事件</td>
</tr>
</tbody></table>
<hr>
<blockquote>
<p>📌 <strong>提示</strong>随着2025年数据要素市场化改革加速推进企业需高度重视数据合规体系建设特别是数据分类分级、数据出境评估、数据安全审计等方面避免因违规导致高额处罚。</p>
</blockquote>
<p>如需了解《数据安全法》全文、实施细则或企业合规建议,可进一步提问。</p>
"""
html_string5="""
<h3>百度智能可信数据空间方案文档质量检查报告</h3>
<p>作为产品文档质量检查改进QA专家我已对上传的《百度智能可信数据空间方案》2025.06版)进行系统化检查。检查依据为: </p>
<ol>
<li>文档需覆盖需求背景、客户画像、产品结构、产品功能、特色优势、竞品分析、使用场景、客户价值8个方面每少一个方面扣10分 </li>
<li>每个方面内容需语言简明、表达精准、减少口语化不满足扣3分 </li>
<li>每个方面内容需阐述清晰、有专业深度且用户易懂不满足扣2分 </li>
<li>总分100分扣完即止。</li>
</ol>
<p>经逐项核验,检查结果如下(扣分累计:<strong>28分</strong>,当前得分:<strong>72分</strong>)。以下按检查逻辑分步说明。</p>
<hr>
<h3>一、扣分明细及理由共28分</h3>
<h4>1文档缺少2个方面 → 扣20分规则1</h4>
<table>
<thead>
<tr>
<th>问题方面</th>
<th>扣分</th>
<th>理由说明</th>
</tr>
</thead>
<tbody><tr>
<td>客户画像</td>
<td>10分</td>
<td>文档未明确描述目标客户群体特征(如行业、规模、痛点等)。现有内容仅在“可信数据空间的五种应用场景”中隐含场景类型(如个人、企业、城市),但无系统化客户画像定义,无法体现“谁是我们的客户”及“客户画像”维度。</td>
</tr>
<tr>
<td>竞品分析</td>
<td>10分</td>
<td>文档未涉及竞品对比、竞品优劣势分析或市场定位。现有内容仅提政策趋势(如国家数据局计划),但无竞品(如阿里云可信数据空间、腾讯可信数据平台等)的横向分析。</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>扣分依据</strong>规则1要求8个方面均需存在缺失2个方面按“每少一个方面扣10分”计20分。</p>
</blockquote>
<h4>2语言简明性不满足 → 扣3分规则2</h4>
<table>
<thead>
<tr>
<th>问题方面</th>
<th>扣分</th>
<th>理由说明</th>
</tr>
</thead>
<tbody><tr>
<td>产品功能</td>
<td>3分</td>
<td>产品功能描述中存在口语化表达:“支持不同来源数据资源、产品和服务在可信数据空间的统一发布、高效查询、跨主体互认”(“支持”一词重复使用,且“高效查询”表述模糊,未量化标准)。</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>扣分依据</strong>规则2要求“语言简明、表达精准、减少口语化”此句未体现精准性“高效”缺乏技术定义扣3分。</p>
</blockquote>
<h4>3阐述清晰度与专业深度不足 → 扣2分规则3</h4>
<table>
<thead>
<tr>
<th>问题方面</th>
<th>扣分</th>
<th>理由说明</th>
</tr>
</thead>
<tbody><tr>
<td>需求背景</td>
<td>2分</td>
<td>政策趋势描述较笼统缺乏对“为什么需要可信数据空间”的深度剖析。例如仅引用数据占比42.8%)但未关联当前市场缺口(如数据孤岛导致的经济损失案例),用户难以理解需求紧迫性。</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>扣分依据</strong>规则3要求“阐述清晰、有专业深度又能让用户明白”此部分未结合实证数据说明需求痛点专业深度不足扣2分。</p>
</blockquote>
<blockquote>
<p><strong>注</strong>其他方面客户画像、竞品分析等缺失部分不适用规则2/3的扣分因内容不存在。</p>
</blockquote>
<hr>
<h3>二、改进建议</h3>
<p>针对上述问题,提出具体可落地的改进方案,确保文档质量提升:</p>
<h4>1. <strong>补全缺失的2个方面紧急优先级</strong></h4>
<ul>
<li><p><strong>客户画像</strong> </p>
<ul>
<li>建议新增独立章节约300字明确目标客户分层<br><em>例:</em> <blockquote>
<p><strong>客户画像</strong> </p>
<ul>
<li><strong>政府/公共部门</strong>省级数据局、城市政务平台核心需求为“跨域数据安全共享”如某省会城市案例中需打通50+委办局数据); </li>
<li><strong>行业龙头企业</strong>如电子信息、汽车制造企业痛点为“供应链数据协同低效”案例代工企业质量数据共享赋能90亿元产值 </li>
<li><strong>中小企业</strong>需通过数据授权获取信用增值如1650+企业融资40亿元。<br><em>目标</em>:清晰定义客户群体、典型特征及核心诉求,避免“场景”描述与“画像”混淆。</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>竞品分析</strong> </p>
<ul>
<li>建议新增对比表格200字内聚焦3-5个头部竞品<br><em>例:</em> <table>
<thead>
<tr>
<th>竞品</th>
<th>核心技术</th>
<th>适用场景</th>
<th>优势</th>
<th>劣势</th>
</tr>
</thead>
<tbody><tr>
<td>阿里云可信数据空间</td>
<td>隐私计算+区块链</td>
<td>金融风控</td>
<td>服务覆盖广</td>
<td>跨境场景支持弱</td>
</tr>
<tr>
<td>腾讯可信数据平台</td>
<td>联邦学习</td>
<td>医疗数据共享</td>
<td>高性能计算</td>
<td>政务合规性待提升</td>
</tr>
<tr>
<td>百度智能可信数据空间</td>
<td>大模型+数据胶囊</td>
<td>全场景(含跨境)</td>
<td>价值共创能力突出</td>
<td>企业落地案例较少</td>
</tr>
</tbody></table>
<blockquote>
<p><em>目标</em>:用数据对比凸显百度方案差异化,避免竞品分析流于口号。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4>2. <strong>优化语言简明性问题(中优先级)</strong></h4>
<ul>
<li>修订产品功能描述:<br><em>原句</em> “支持不同来源数据资源、产品和服务在可信数据空间的统一发布、高效查询、跨主体互认”<br><em>优化后</em> “实现多源数据资源的标准化发布、毫秒级精准查询及跨主体互认支持100+数据源无缝接入)” <blockquote>
<p><em>理由</em>删除口语化词“支持”用“毫秒级”“100+”量化“高效”,提升精准度。</p>
</blockquote>
</li>
</ul>
<h4>3. <strong>增强需求背景的专业深度(高优先级)</strong></h4>
<ul>
<li>在需求背景章节补充实证数据:<br><em>新增段落</em> <blockquote>
<p>“据《中国数字经济发展研究报告2024数据孤岛导致企业数据利用率不足35%年损失超2000亿元。百度方案直击此痛点——通过可信数据空间可将数据流通效率提升90%(如某电子信息企业案例),推动数据要素价值释放从‘不敢用’转向‘愿用、能用’。”<br><em>目标</em>:用具体损失数据链接政策趋势与用户价值,避免空泛表述。</p>
</blockquote>
</li>
</ul>
<hr>
<h3>三、总结</h3>
<p>本次检查发现文档<strong>核心问题为缺失客户画像和竞品分析两个关键维度</strong>扣20分其次在产品功能语言简明性及需求背景深度上存在不足扣5分。<strong>当前总分72分</strong>,需优先补全客户画像与竞品分析章节,同时精炼语言、深化需求论证。<br><strong>改进后预期</strong>通过新增结构化客户画像、竞品对比表格及量化案例文档可达到90+分满足8方面全覆盖、语言精准、专业深度与用户友好。建议在2周内完成修订确保方案在客户沟通与投标中更具竞争力。</p>
<blockquote>
<p><strong>最终评分</strong>72分扣28分<br><strong>下一步行动</strong>:请团队重点落实“客户画像”和“竞品分析”章节的补充工作。</p>
</blockquote>
"""
print(html_string5)
html_string = trans_html_to_format(html_string5)
print("============================================")
print(html_string)
"""
ppt = parse_html_to_ppt(html_string)
for s in ppt:
print("====")
print(s)
"""