你好,
我修改了上面的 Python 脚本,使其具有通用性:它会提示输入词表,如果存在词典文件也会提示输入,并输出一个 SMF 文件以导入到 FLEx 中。
#!/usr/bin/python3
"""
wordlist_to_sfm.py
将 Paratext 词表导出为 SFM(标准格式标记)格式,
以便导入到 FLEx(FieldWorks Language Explorer)中。
用法:
python wordlist_to_sfm.py
脚本将提示输入:
- Paratext 词表 XML 文件(例如从 Paratext 的词表工具导出)
- 可选的 Paratext 词典 XML 文件,用于提供英文释义
- 生成的 .sfm 文件的输出路径
默认情况下,只导出词表中标记为“正确”(Correct)的单词。
可以通过编辑下面的 INCLUDE_* 设置来更改此行为。
输出格式为 MDF(多词典格式化器),这是 FLEx 用于词典导入的标准 SFM 方言。
"""
import codecs
import os
import xml.etree.ElementTree as etree
# --- 设置 ----------------------------------------------------------------
# 控制哪些拼写类别包含在导出中。
# 设置为 True 以包含该类别中的单词,设置为 False 以排除它们。
INCLUDE_CORRECT = True # 翻译者已批准的单词
INCLUDE_UNKNOWN = False # 尚未审查的单词
INCLUDE_INCORRECT = False # 被标记为拼写错误的单词
# 设置为 True 以将单词的语料库频率作为注释字段(\co_count)写入
INCLUDE_COUNT = True
# -----------------------------------------------------------------------------
def prompt_path(prompt_text, default):
"""显示带有可选默认值的提示;返回用户输入或默认值。"""
display = f" [{default}]" if default else ""
value = input(f"{prompt_text}{display}: ").strip()
return value if value else default
def get_user_inputs():
"""询问用户运行导出所需的三个文件路径。"""
print("Paratext 词表到 SFM 导出器")
print("-----------------------------------\n")
# 词表是必需的 - 持续询问直到找到文件
wordlist_path = prompt_path("词表文件 (XML)", "Notsi-WL.xml")
while not os.path.exists(wordlist_path):
print(f" 找不到 '{wordlist_path}'。请检查路径并重试。")
wordlist_path = prompt_path("词表文件 (XML)", "Notsi-WL.xml")
# 词典是可选的 - 如果未找到则静默跳过
lexicon_path = prompt_path("用于释义的词典文件(按 Enter 跳过)", "")
if lexicon_path and not os.path.exists(lexicon_path):
print(f" 找不到 '{lexicon_path}'。将继续,但不包含释义。")
lexicon_path = ""
output_path = prompt_path("输出文件名", "wordlist_for_flex.sfm")
return wordlist_path, lexicon_path, output_path
def load_lexicon_glosses(lexicon_path):
"""
从 Paratext 词典 XML 文件中读取英文释义。
返回一个以词素形式为键的字典,其中每个值包含词素类型(Word, Prefix, Suffix)和英文释义字符串列表。
如果未提供词典路径,则返回空字典。
"""
glosses = {}
if not lexicon_path:
print("未提供词典 - 条目将不包含释义进行导出。")
return glosses
print(f"正在从以下位置读取词典:{lexicon_path}")
with open(lexicon_path, "rb") as f:
data = f.read()
# 去除任何 BOM 变体(标准 UTF-8 BOM,或双重编码的 BOM)
for bom in (b"\xef\xbb\xbf", b"\xc3\xaf\xc2\xbb\xc2\xbf"):
if data.startswith(bom):
data = data[len(bom):]
break
xmldoc = etree.fromstring(data)
for item in xmldoc.findall("Entries/item"):
lexeme = next(item.iter("Lexeme"), None)
if lexeme is None:
continue
form = lexeme.get("Form", "")
lex_type = lexeme.get("Type", "Word") # Word, Prefix 或 Suffix
# 收集此条目的所有英文释义(可能有一个以上的义项)
entry_glosses = [
gloss.text
for gloss in item.iter("Gloss")
if gloss.get("Language") == "en" and gloss.text
]
if form and entry_glosses:
glosses[form] = {"type": lex_type, "glosses": entry_glosses}
print(f" 找到 {len(glosses)} 个带有英文释义的条目。")
return glosses
def build_approved_list(wordlist_path):
"""
读取 Paratext 词表 XML 并返回通过 INCLUDE_* 过滤器的单词。
返回一个已批准单词形式的列表,以及一个包含每个单词的语料库计数、连字符和形态分解的元数据字典。
"""
wordlist = etree.parse(wordlist_path)
all_items = wordlist.getroot().findall("item")
print(f"正在从以下位置读取词表:{wordlist_path}")
print(f" 在列表中找到 {len(all_items)} 个单词。")
approved = []
metadata = {}
for item in all_items:
spelling = item.attrib.get("spelling", "Unknown")
word = item.attrib.get("word", "")
include = (
(spelling == "Correct" and INCLUDE_CORRECT) or
(spelling == "Unknown" and INCLUDE_UNKNOWN) or
(spelling == "Incorrect" and INCLUDE_INCORRECT)
)
if include and word:
approved.append(word)
metadata[word] = {
"count": item.attrib.get("count", "0"),
"hyphenation": item.attrib.get("hyphenation", ""),
"morphology": item.attrib.get("morphology", ""),
"morph_approved": item.attrib.get("morphologyApproved", "False"),
"specificcase": item.attrib.get("specificcase", ""),
}
print(f" {len(approved)} 个单词被标记为正确并准备导出。")
return approved, metadata
def write_sfm_entry(outfile, word, lex_type, glosses, meta, include_count):
"""
将单个 SFM 词典条目写入输出文件。
遵循的 MDF 约定:
\\lx - 词素(标题词);词缀在结合侧添加连字符
\\sn - 义项编号(每个释义写一次)
\\ge - 该义项的英文释义
\\mr - 形态字符串(当被翻译者批准时)
\\co_* - 用于 FLEx 没有标准标记的元数据的注释字段
"""
# 写入标题词,添加连字符以显示词缀结合点
outfile.write("\n\\lx ")
if lex_type == "Suffix":
outfile.write("-")
outfile.write(word)
if lex_type == "Prefix":
outfile.write("-")
outfile.write("\n")
# 将词缀类型记录为注释,以便在 FLEx 导入后保留
if lex_type != "Word":
outfile.write(f"\\co_type {lex_type}\n")
# 语料库频率 - 对于在词典工作中优先处理条目很有用
if include_count and meta:
outfile.write(f"\\co_count {meta['count']}\n")
# 形态:如果分析已获批准,使用标准的 \\mr 标记,
# 否则将其存储为注释,以避免导入未经验证的数据
if meta and meta["morphology"]:
marker = "\\mr" if meta["morph_approved"] == "True" else "\\co_morph"
outfile.write(f"{marker} {meta['morphology']}\n")
# 为每个释义写入一个义项块
for sense_num, gloss_text in enumerate(glosses, start=1):
outfile.write(f"\\sn {sense_num}\n")
outfile.write(f"\\ge {gloss_text}\n")
def main():
"""
主导出例程。
两遍方法:
第一遍 - 写入具有词典释义的条目(先写入数据更丰富的内容)
第二遍 - 写入剩余的没有词典条目的已批准单词
这确保了带释义的条目不会被重复。
"""
wordlist_path, lexicon_path, output_path = get_user_inputs()
print()
approved_words, metadata = build_approved_list(wordlist_path)
lexicon_glosses = load_lexicon_glosses(lexicon_path)
# 以 UTF-8 打开输出文件并写入 MDF 头
outfile = codecs.open(output_path, mode="w", encoding="utf-8")
outfile.write("\\_sh v3.0 400 MDF\n\\_DateStampHasFourDigitYear\n\n")
remaining = list(approved_words) # 第一遍之后仍需写入的单词
with_glosses = 0
# 第一遍:同时出现在词典和已批准词表中的条目
for form, lex_data in lexicon_glosses.items():
if form not in approved_words:
print(f" 跳过 '{form}'(在词典中,但未在词表中标记为正确)")
continue
if form in remaining:
remaining.remove(form)
write_sfm_entry(outfile, word=form, lex_type=lex_data["type"],
glosses=lex_data["glosses"], meta=metadata.get(form),
include_count=INCLUDE_COUNT)
with_glosses += 1
# 第二遍:没有词典条目的已批准单词 - 不含释义导出
for word in remaining:
write_sfm_entry(outfile, word=word, lex_type="Word", glosses=[],
meta=metadata.get(word), include_count=INCLUDE_COUNT)
total = with_glosses + len(remaining)
outfile.close()
print(f"\nExport 完成。")
print(f" 写入的总条目数 : {total}")
print(f" 带释义的 : {with_glosses}")
print(f" 不带释义的 : {len(remaining)}")
print(f" 输出文件 : {output_path}")
if __name__ == "__main__":
main()