Linq to XML(XDocument)でエンティティ宣言されたものを使う

.NET Coreにおける問題のIssueが解決し、.NET Core 1.2がリリースされたら記事を書き直す。ソースコードの予定場所

(現在の中身は2014年6月4日当時のコードを使ったもの)

(例外の画像)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 3.0//EN" "http://www.w3.org/Math/DTD/mathml3/mathml3.dtd">
<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mn>2</mn>
    <mo>&times;</mo>
    <mn>5</mn>
    <mo>=</mo>
    <mn>10</mn>
</math>

上のようなxmlを読み込ませると「宣言されていないエンティティ’times’への参照です。」というエラーが出る場合の対処法など。

1:XmlReaderを使って、Dtdの評価をオンにして乗り切る

一番簡単に思えるのは、XmlReaderとその設定を用いた評価です。

DtdProcessingにParseを指定することで、Dtdを考慮して読み込んでくれます。

速いわけではないので、必要ない人は1を飛ばしてください。

プログラム

			DateTime start = DateTime.Now;
			var doc = XDocument.Load(XmlReader.Create("MathMLFile.xml", new XmlReaderSettings
			{
				DtdProcessing = DtdProcessing.Parse
			}));
			Console.WriteLine(DateTime.Now - start);
			Console.WriteLine(doc.ToString());

出力

00:00:08.3144756

<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 3.0//EN" "http://www.w3.org/Math/DTD/mathml3/mathml3.dtd"[]>
<math xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">2</mn>
    <mo xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">×</mo>
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">5</mn>
    <mo xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">=</mo>
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">10</mn>
</math>

結果

出力の最初を見てください。個人のネットワーク環境に左右されるとはいえ、8秒の処理は長すぎます。原因はxmlの宣言で外部(インターネット上)の定義ファイルを指定しており、評価時にダウンロードしてくる処理が(毎回)必要になるからです。

これを解決するためには、あらかじめローカルにdtdファイルを保存しておくとよいです。(xmlがモジュール化されている場合は、そのモジュールすべてが必要)

2:XmlReaderを使って、自作の派生クラスでローカルファイルの定義を使わせる

少々面倒な処理が必要になりますが、xmlに使うフォーマットが分かっている場合は、こちらの方が速くて便利です。また、クラス化することで処理が分かれるので見やすくなる?

XmlReader.Createの設定で、XmlResolverに自作の派生クラスのインスタンスを指定します。

プログラム

DateTime start = DateTime.Now;
var doc = XDocument.Load(XmlReader.Create(
	new FileStream("MathMLFile.xml", FileMode.Open, FileAccess.Read, FileShare.Read),
	new XmlReaderSettings
	{
		DtdProcessing = DtdProcessing.Parse,
		XmlResolver = new MathMLResolver()
	}));

Console.WriteLine(DateTime.Now - start);
Console.WriteLine(doc.ToString());
class MathMLResolver : System.Xml.Resolvers.XmlPreloadedResolver
{
	public MathMLResolver()
		: base()
	{
		this.Add(
			new Uri("http://www.w3.org/Math/DTD/mathml3/mathml3.dtd"),
			new FileStream("mathml3.dtd", FileMode.Open, FileAccess.Read, FileShare.Read));
			
		this.Add(
			new Uri("http://www.w3.org/Math/DTD/mathml3/mathml3-qname.mod"),
			new FileStream("mathml3-qname.mod", FileMode.Open, FileAccess.Read, FileShare.Read));
			
		foreach (var file in Directory.EnumerateFiles(Environment.CurrentDirectory, "*.ent")
										.Select(fullPath => Path.GetFileName(fullPath)))
		{
			this.Add(
				new Uri("http://www.w3.org/Math/DTD/mathml3/" + file),
				new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read));
		}
	}
}

出力

00:00:00.1200068

<!DOCTYPE math PUBLIC "-//W3C//DTD MathML 3.0//EN" "http://www.w3.org/Math/DTD/mathml3/mathml3.dtd"[]>
<math xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">2</mn>
    <mo xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">×</mo>
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">5</mn>
    <mo xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">=</mo>
    <mn xmlns="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink">10</mn>
</math>

結果

ローカルから読むことで速くなりました。

自作のクラスは、XmlPreloadedResolverクラスから派生しています。XmlPreloadedResolverクラスは、xml関係ファイルのURIと、それに対応するファイルの関係を決めることができ、今回はそれにローカルファイルを指定しています。

今回はmathmlのものを用いました。関係するすべてのファイルを実行フォルダに保存し、読み込ませています。

(ここのサイトも追加で参考になりそう。.NET Framework 4.5.2 でXmlReaderのDTD読み込みの挙動が変わっていた – ぽにょろん

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です