Pythonのライブラリーであるlxmlは(lxmlも1)、デフォルトでは外部実体(外部エンティティ、external entity)を読みに行きません。
解決方法が公式に書いてあって2、XML Catalogsによる方法も紹介されてるのですが、URI Resolversを書く方法を使ってみました。この方法で、取得した外部実体をファイルとして保存しておけば、問題があって調べるときなどいろいろ便利かな…ということで。
以下は、JATS XMLをバリデートしてみたとき1のスクリプトです。
lxmlに対して外部実体を解決しながら、あわせて次を行います: entity
というフォルダが既にある前提で
- 取ってきた外部実体を
entity
フォルダの下に保存しておきます - 標準出力(
print()
)に外部ファイルのURL、公開識別子、保存したファイル名(URLのbasename
)をカンマ区切りで出力します
from lxml import etree
import argparse
import os
import sys
import requests
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("file", help="XML file to validate.")
args = arg_parser.parse_args()
print(f'start parsing {args.file}')
count = 0
entity_folder = 'entity'
print("url,id,basename")
class DTDResolver(etree.Resolver):
def resolve(self, url, id, context):
if url.startswith('http'):
global count
count += 1
res = requests.get(url)
# 読み込んだリモートのファイルを保存しておく
file_name = os.path.basename(url)
with open(f"{entity_folder}/{file_name}", mode='w') as f:
f.write(res.text)
print("'%s','%s,'%s'" % (url, id, file_name))
return self.resolve_string(res.text, context)
else:
f = open(url, 'r')
return self.resolve_file(f, context)
parser = etree.XMLParser(dtd_validation=True, no_network=False)
parser.resolvers.add( DTDResolver() )
try:
tree = etree.parse(args.file, parser=parser)
except etree.XMLSyntaxError as e:
print(f'etree.XMLSyntaxError: {e}')
finally:
print(f'Loaded {count} remote entities and stored under the "{entity_folder}" folder.')
-
外部実体の解決 - 無料のVisual Studio CodeとXML拡張機能でJATS XMLを編集する https://qiita.com/yamahige/items/c25f10f2177cc4d9df90#%E5%A4%96%E9%83%A8%E5%AE%9F%E4%BD%93%E3%81%AE%E8%A7%A3%E6%B1%BA ↩ ↩2
-
Document loading and URL resolving - lxml https://lxml.de/resolvers.html ↩