如何解析含缺失DTD与实体引用的audio.cdindex文件?
解决audio.cdindex XML解析失败的问题
核心问题有两个:一是原XML引用的MusicBrainz DTD地址已失效,二是曲目名称里的ü实体无对应定义,导致xml.etree.ElementTree解析报错。你之前替换外部DTD没生效,是因为xml.etree.ElementTree默认不会自动加载外部DTD文件,而直接在DOCTYPE中定义内部实体的方式更可靠。
方法一:使用内部实体定义(最直接)
直接把DOCTYPE替换成包含ü实体定义的内部子集,无需依赖外部文件,解析器可直接识别实体。修改代码里的repl变量:
repl = r'<!DOCTYPE CDInfo [<!ENTITY uuml "ü">]>'
替换后,解析器会自动将ü解析为对应的Unicode字符(ü)。
方法二:修复外部DTD加载问题(可选)
如果坚持用外部DTD文件,需确保两点:
my_private.dtd文件路径正确,和脚本在同一目录或使用绝对路径- 配置ElementTree解析器,允许加载外部DTD
修改readAudioindex函数,添加解析器配置:
def readAudioindex(dest): Tracks = [] try: with open(dest) as fd: txt = fd.read() # 配置解析器允许加载外部DTD parser = ET.XMLParser() parser.dtd_validation = True # 开启DTD验证(可选) root = ET.fromstring(clean(txt), parser=parser) # 后续代码不变...
同时,my_private.dtd里需包含实体定义:
<!ENTITY uuml "ü"> <!-- 补充其他需要的实体及XML结构定义 --> <!ELEMENT CDInfo (Title, NumTracks, IdInfo, SingleArtistCD)>
这种方式需维护外部DTD文件,且ElementTree对外部DTD支持有限,不如内部实体直接。
方法三:预处理XML文本替换实体(简单粗暴)
若不想处理DTD,可直接在读取XML后替换实体:
def clean(xtext): dt = dtype.search(xtext) if dt: xtext = xtext[:dt.start()] + repl + xtext[dt.end():] # 直接替换实体 xtext = xtext.replace('ü', 'ü') return xtext
适合仅遇到少数未定义实体的场景。
完整修改后的代码(方法一整合版)
import xml.etree.ElementTree as ET import re import logging dtype = re.compile(r"<!DOCTYPE.*musicbrainz.*>") # 使用内部实体定义替代外部DTD repl = r'<!DOCTYPE CDInfo [<!ENTITY uuml "ü">]>' def clean(xtext): dt = dtype.search(xtext) if dt: return xtext[:dt.start()] + repl + xtext[dt.end():] else: return xtext def readAudioindex(dest): Tracks = [] try: with open(dest) as fd: txt = fd.read() root = ET.fromstring(clean(txt)) album = root.find('Title').text artist = root.find('.//Artist').text for track in root.iter('Track'): tt = track.find('Name').text tp = tt.find('/') if tp >= 0: ta = tt[:tp] ta = ta.strip() tt = tt[tp+1:] tt = tt.strip() else: ta = root.find('.//Artist').text num = int(track.get('Num')) Tracks.append({'num':num, 'tt':tt, 'ta':ta}) return (artist, album, Tracks) except Exception as e: logging.getLogger().error('Exception reading audioindex "{}"'.format(str(e))) # shutil.copy(dest, 'faulty_audio.cdindex') return None if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("index") parser.add_argument("-d", "--dry", action='store_true') args = parser.parse_args() txt = "" if args.dry: with open(args.index) as fd: txt = fd.read() print(clean(txt)) else: cont = readAudioindex(args.index) if cont == None: print("not valid") else: print(f"artist: {cont[0]}, album: {cont[1]}") for tr in cont[2]: print(f'track {tr["num"]} "{tr["tt"]}"')
说明
内部实体定义的方式最可靠,无需依赖外部文件,若后续遇到其他类似实体(如ä、ö),可直接在DOCTYPE内部子集里添加对应定义。
内容的提问来源于stack exchange,提问作者tommiport5
相关产品推荐
相关产品推荐

