使用SAX将圣经文本转为OSIS XML:如何优雅生成头部模板文本?
优雅生成OSIS XML头部的SAX方案
我太懂这种感觉了——用标准库SAX处理XML时,流式处理的爽感拉满,但碰到静态的大段头部模板,硬写字符串或者凑SAX方法总觉得别扭,完全破坏了代码的整洁度。刚好我之前处理类似的宗教文本XML生成时,摸索出几个优雅的方案,完全适配你的需求:
方案1:将静态头部封装在自定义ContentHandler中
既然用SAX,那就把头部的静态逻辑和业务处理逻辑封装在一起,继承xml.sax.saxutils.XMLGenerator,重写startDocument和endDocument方法,直接向输出流写入预定义的头部/尾部内容,代替SAX默认的生成逻辑。这样代码内聚,不用在业务逻辑里穿插模板字符串:
from xml.sax import saxutils class OSISContentHandler(saxutils.XMLGenerator): def __init__(self, out, encoding='utf-8', osis_work_id='KJV'): super().__init__(out, encoding) self.out = out self.osis_work_id = osis_work_id def startDocument(self): # 写入自定义XML声明和OSIS根元素头部,支持动态参数 header = f'''<?xml version="1.0" encoding="UTF-8"?> <osis xmlns="http://www.bibletechnologies.net/2003/OSIS/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bibletechnologies.net/2003/OSIS/namespace http://www.bibletechnologies.net/2003/OSIS/osisCore.2.1.xsd"> <osisText osisIDWork="{self.osis_work_id}" osisRefWork="{self.osis_work_id}"> ''' self.out.write(header) def endDocument(self): # 写入尾部闭合标签 footer = '''</osisText> </osis> ''' self.out.write(footer) # 使用示例 with open('bible.osis.xml', 'w', encoding='utf-8') as output_file: handler = OSISContentHandler(output_file, osis_work_id='NIV') # 这里开始处理圣经文本的流式生成 handler.startElement('chapter', {'osisID': 'Gen.1'}) handler.startElement('verse', {'osisID': 'Gen.1.1'}) handler.characters('In the beginning God created the heaven and the earth.') handler.endElement('verse') handler.endElement('chapter') # 触发尾部生成 handler.endDocument()
这个方案的好处是:所有XML生成逻辑都在Handler里,业务代码只需要专注于处理圣经文本的节点生成,头部的静态结构和动态参数(比如不同版本的osisIDWork)都能优雅处理。
方案2:分离模板文件,代码与配置解耦
如果头部模板比较复杂,或者需要频繁修改(比如调整schema版本、添加新的命名空间),把头部/尾部做成单独的模板文件会更方便,完全和Python代码分离,维护起来更轻松:
先创建两个模板文件:
osis_header.tpl:<?xml version="1.0" encoding="UTF-8"?> <osis xmlns="http://www.bibletechnologies.net/2003/OSIS/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bibletechnologies.net/2003/OSIS/namespace http://www.bibletechnologies.net/2003/OSIS/osisCore.2.1.xsd"> <osisText osisIDWork="{osis_work_id}" osisRefWork="{osis_work_id}">osis_footer.tpl:</osisText> </osis>
然后在脚本中读取模板并写入输出流,再启动SAX的内容生成:
from xml.sax import saxutils def generate_osis_bible(input_text, output_path, osis_work_id='KJV'): with open(output_path, 'w', encoding='utf-8') as output_file: # 写入动态格式化后的头部模板 with open('osis_header.tpl', 'r', encoding='utf-8') as header_file: header_content = header_file.read().format(osis_work_id=osis_work_id) output_file.write(header_content) # 初始化SAX生成器,专注处理内容节点 handler = saxutils.XMLGenerator(output_file, encoding='utf-8') # 这里省略处理input_text生成章节、经文节点的逻辑,示例如下: handler.startElement('chapter', {'osisID': 'Gen.1'}) handler.startElement('verse', {'osisID': 'Gen.1.1'}) handler.characters('In the beginning God created the heaven and the earth.') handler.endElement('verse') handler.endElement('chapter') # 写入尾部模板 with open('osis_footer.tpl', 'r', encoding='utf-8') as footer_file: output_file.write(footer_file.read()) # 调用示例 generate_osis_bible('your_bible_text_source', 'output.osis.xml', osis_work_id='ESV')
这个方案的优势是:模板修改不需要动代码,非开发人员也能调整XML结构;如果需要支持多种OSIS配置,只需要准备不同的模板文件即可。
为什么这两个方案比硬写字符串优雅?
- 完全贴合SAX的流式处理特性:直接向输出流写入静态内容,不会占用额外内存,和SAX的设计理念一致;
- 代码职责清晰:业务逻辑专注于文本转节点,模板逻辑要么封装在Handler要么独立成文件,不会混在一起;
- 支持动态参数:不管是封装在Handler还是用模板文件,都能轻松替换
osisIDWork这类动态属性,不用拼接复杂字符串。
内容的提问来源于stack exchange,提问作者mcepl




