C#でローカルのDTDファイルを使うXMLリゾルバを作る

以前、C#でXmlDocumentを作る時にリゾルバでタイムアウトすると書いたが、ようやくローカルのDTDファイルを使ったXMLリゾルバが作れたので、コードをまるっと公開。尚、.NET Framework 4では殆ど同じことを行うXmlPreloadedResolverクラスが追加されているので、使えるならそっちを使うのが良い。悲しいかな、うちは.NET 3.5なのさ……

using System;
using System.Collections.Generic;
using System.Xml;
using System.IO;
 
namespace ProductionKusoGA
{
    class LocalXmlResolver : XmlResolver
    {
        public LocalXmlResolver()
        {
        }
 
        public override System.Net.ICredentials Credentials
        {
            set {  }
        }
 
        public override Uri ResolveUri(Uri baseUri, string relativeUri)
        {
            Uri uri = DocTypeManager.Instance.GetDTDURI(relativeUri);
            return uri != null ? uri : base.ResolveUri(baseUri, relativeUri);
        }
 
        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            object entity = DocTypeManager.Instance.GetDTDStream(absoluteUri.AbsoluteUri);
            if (entity == null)
            {
                XmlUrlResolver resolver = new XmlUrlResolver();
                entity = resolver.GetEntity(absoluteUri, role, ofObjectToReturn);
            }
            return entity;
        }
 
        class DocTypeManager
        {
            public static readonly DocTypeManager Instance = new DocTypeManager();
 
            public Uri GetDTDURI(string inFPI)
            {
                Uri dtdURI = null;
                URIForFPI.TryGetValue(inFPI, out dtdURI);
                return dtdURI;
            }
 
            public FileStream GetDTDStream(string inURI)
            {
                FileStream stream = null;
                if (DTDStreamForURI.TryGetValue(inURI, out stream) == false)
                {
                    string dtdFile = null;
                    if (DTDFileForURI.TryGetValue(inURI, out dtdFile))
                    {
                        string RESOURCE_DIR = "...";
                        stream = new FileStream(Path.Combine(RESOURCE_DIR, dtdFile), FileMode.Open);
                        DTDStreamForURI.Add(inURI, stream);
                    }
                }
                return stream;
            }
 
            DocTypeManager()
            {
                URIForFPI = new Dictionary<string,Uri>();
                DTDFileForURI = new Dictionary<string,string>();
                DTDStreamForURI = new Dictionary<string, FileStream>();
 
                AddDTD("-//W3C//DTD XHTML 1.0 Strict//EN",      @"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",       @"path/to/xhtml1-strict.dtd");
                AddDTD("-//W3C//DTD XHTML 1.0 Trasitional//EN", @"http://www.w3.org/TR/xhtml1/DTD/xhtml1-trasitional.dtd",  @"path/to/xhtml1-trasitional.dtd");
                AddDTD("-//W3C//DTD XHTML 1.0 Frameset//EN",    @"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd",     @"path/to/xhtml1-frameset.dtd");
                AddDTD("xhtml-lat1.ent", @"http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent", @"path/to/xhtml-lat1.ent");
                AddDTD("xhtml-symbol.ent", @"http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent", @"path/to/xhtml-symbol.ent");
                AddDTD("xhtml-special.ent", @"http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent", @"path/to/xhtml-special.ent");
            }
 
            ~DocTypeManager()
            {
                foreach (var pair in DTDStreamForURI)
                {
                    if (pair.Value != null)
                    {
                        pair.Value.Dispose();
                    }
                } 
            }
 
            void AddDTD(string inFPI, string inURI, string inFilepath)
            {
                URIForFPI.Add(inFPI, new Uri(inURI));
                DTDFileForURI.Add(inURI, inFilepath);
            }
 
            Dictionary<string, Uri> URIForFPI { get; set; }
            Dictionary<string, string> DTDFileForURI { get; set; }
            Dictionary<string, FileStream> DTDStreamForURI{ get; set; }
        }
    }
}

DTDファイルのパスを適宜変更し、xmlDocument.Resolver = new LocalXmlResolver();ってな感じで設定してやればおk。DTDを増やしたいときはAddDTDを増やす。DTDが内部で参照しているファイル(上記コードで言えばxhtml-lat1.entとか)も漏れなく追加する必要がある。

一応、コードのライセンスはパブリックドメインってことで。煮るなり焼くなりお好きにどうぞ。