普贤教程 › 生态包
解析与提取
没有正则的时候怎么解析?这五个包给的是「组合子」「递归下降」两条路:parser 是通用组合子,pxml 是配置语言,htmlparse 是容错 HTML,mailparse 是 RFC 5322 邮件,readstat 是可读性统计。它们都不依赖外部服务,全部纯函数。
parser:组合子
一个 parser 就是一个函数 (input, pos) → Ok([新位置, 值]) | Err(消息)。先用内建原语拼出原子的,再用 p_seq / p_alt / p_many 组合。
px
import parser print(str(p_many1(p_digit)("123abc", 0))) # Ok([3, [1, 2, 3]]) print(str(p_ident("name_1 x", 0))) # Ok([6, name_1]) print(str(p_alt([p_int, p_ident])("x1", 0)))# Ok([2, x1]) int 失败自动换 ident print(str(p_many(p_digit)("abc", 0))) # Ok([0, []]) 零次也算成功 print(str(p_opt(p_int)("abc", 0))) # Ok([0, null])
text
Ok([3, [1, 2, 3]]) Ok([6, name_1]) Ok([2, x1]) Ok([0, []]) Ok([0, null])
原语不吃空白
p_int(" -42,", 0) 直接 Err(位置 0: 期望整数) —— 组合子不做隐式跳过。要跳空白必须显式串上 p_ws:px
var p = p_seq([p_ws, p_int]) print(str(p(" -42,", 0))) # Ok([5, [null, -42]])
text
Ok([5, [null, -42]])
注意 p_seq 的返回值是各子 parser 结果组成的 list,位置在第一个元素;p_ws 不产出值所以是 null。
常用原语一览:p_char(c) p_str(s) p_any p_digit p_letter p_alnum p_space p_ws p_ident p_int p_parse_int;组合子 p_seq p_alt p_many p_many1 p_opt p_map p_between p_sep_by p_sep_by1 p_take_while p_end。
没有匿名函数
PuXian 不支持
PuXian 不支持
def (x): ... 字面量。要给 p_map 传函数、给 td_run 传断言,都必须先 def name(x): 声明成具名函数再传名字。pxml:配置语言的解析器
px
import std.pxml def main(): var doc = pxml_parse("server {\n port = 8080\n tls = true\n host = \"cx.dzty.club\"\n}") print(type(doc)) # result print(str(doc.is_ok())) print(str(doc.unwrap()))
text
result true {server: {host: cx.dzty.club, port: 8080, tls: true}}
返的是 Result,不是字典
成功
成功
Ok(配置树)、失败 Err("line N: …")。判断用 doc.is_ok(),取值用 doc.unwrap(),函数里也可以写 let cfg = pxml_parse(text)?。不要索引:doc["ok"] 会报 R1002: 此类型不支持索引: result。htmlparse:容错解析
px
import htmlparse var html = "<div class=\"x\"><a href=\"/a\">一</a><a href=\"/b\">二</a><p>文本 & 实体</p></div>" var el = hp_parse(html).ok() print(str(hp_extract_links(el))) # [{href: /a, text: 一}, {href: /b, text: 二}] print(hp_text(el)) # 一二文本 & 实体 print(hp_decode_entities(hp_text(el))) # 一二文本 & 实体 print(hp_attr(hp_find_first(el, "a"), "href")) # /a
text
[{href: /a, text: 一}, {href: /b, text: 二}]
一二文本 & 实体
一二文本 & 实体
/a两个「看着像 bug 其实是契约」的点
①
①
hp_text 不解码实体,& 原样返回,要自己再过 hp_decode_entities;
② 多个子节点的文本直接拼接、不加分隔符(一二文本)—— 抓正文时如果想保留词边界,得自己遍历子节点加空格。常用:hp_find / hp_find_rec(按标签找)、hp_find_by_attr(按属性找)、hp_inner_html / hp_outer_html、hp_extract_images、hp_table_parse、hp_strip_tags(一次性去标签,返回 string)。
mailparse:邮件头解码
px
import mailparse var raw = "From: =?UTF-8?B?5pmo5pum?= <a@b.com>\nSubject: =?UTF-8?B?5rWL6K+V?=\nContent-Type: text/plain; charset=utf-8\n\nhello body" var m = mail_parse(raw) print(str(m)) print(mail_decode_rfc2047("=?UTF-8?B?5pmo5pum?=")) # 晨曦
text
Ok({body: hello body, date: null, from: 晨曦 <a@b.com>, headers: {Content-Type: text/plain; charset=utf-8, From: =?UTF-8?B?5pmo5pum?= <a@b.com>, Subject: =?UTF-8?B?5rWL6K+V?=}, subject: 测试, to: null})
晨曦from / subject 已解码,headers 保留原始(未解码)字符串——排查乱码时两处对照最有用。date 与 to 缺席时是 null,不是空串。支持 B(base64)与 Q(quoted-printable)两种编码词。
readstat:可读性统计
px
import readstat var s = "The quick brown fox jumps over the lazy dog. PuXian is a static language." print(rs_sentences(s)) # 2 print(str(rs_counts(s))) # {sentences: 2, syllables: 20, words: 14} print(rs_flesch(s)) # Ok(78.87285714285717) print(rs_grade(s)) # Ok(小学)
text
2 {sentences: 2, syllables: 20, words: 14} Ok(78.87285714285717) Ok(小学)
它是给英文写的
rs_words 按空白切词、音节按元音组估算,中文整句会被当成一个「词」——中文文本的可读性分数没有意义,别拿它做中文质量指标。