You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解析含缺失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   "&#252;">]>'

替换后,解析器会自动将&uuml;解析为对应的Unicode字符(ü)。

方法二:修复外部DTD加载问题(可选)

如果坚持用外部DTD文件,需确保两点:

  1. my_private.dtd文件路径正确,和脚本在同一目录或使用绝对路径
  2. 配置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 "&#252;">
<!-- 补充其他需要的实体及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('&uuml;', '&#252;')
    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   "&#252;">]>'

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"]}"')

说明

内部实体定义的方式最可靠,无需依赖外部文件,若后续遇到其他类似实体(如&auml;、&ouml;),可直接在DOCTYPE内部子集里添加对应定义。

内容的提问来源于stack exchange,提问作者tommiport5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 22:17:27