XmlResolver reading from resources

by Matt 3. July 2007 06:02

Right. Putting my money where my mouth is.

After a rather lengthy discourse on what on earth the XmlResolver is all about, I outlined how I would implement XmlResolver to resolve external files from resources (based on, but quite different to a very useful example from Scott Willeke).

I actually implemented it pretty much as I described it, and it works like a charm, and is, I think, rather more obvious in what it's doing. Here goes:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;

namespace SticklebackPlastic.Xml {

    internal sealed class XmlResourceResolver : XmlUrlResolver {

        private readonly Dictionary<string, Resource> _publicIdResources = new Dictionary<string, Resource>();
        private readonly Dictionary<string, Assembly> _assemblyMapping = new Dictionary<string, Assembly>();

        private class Resource {
            private readonly Assembly ResourceAssembly;
            private readonly string _resourceNameAsPath;

            public Resource(Assembly resourceAssembly, string resourceNameAsPath) {
                ResourceAssembly = resourceAssembly;
                _resourceNameAsPath = resourceNameAsPath;
            }

            public Uri AbsoluteUri {
                get {
                    UriBuilder builder = new UriBuilder();
                    builder.Scheme = ResourceScheme;
                    builder.Host = ResourceAssembly.GetName().Name.ToLower();
                    builder.Path = _resourceNameAsPath;

                    return builder.Uri;
                }
            }
        }

        internal void AddPublicIdMapping(string publicId, Assembly resourceAssembly, string resourceNamespace, string resourceName) {

            // Treat the resource namespace as a path - replace the "." separators with "/", and append the resource name
            string resourceNameAsPath = resourceNamespace.Replace(".", "/") + "/" + resourceName;

            Resource resource = new Resource(resourceAssembly, resourceNameAsPath);
            _publicIdResources.Add(publicId, resource);
            string assemblyName = resourceAssembly.GetName().Name.ToLower();
            if (!_assemblyMapping.ContainsKey(assemblyName)) {
                _assemblyMapping.Add(assemblyName, resourceAssembly);
            }
        }

        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) {

            if (absoluteUri.Scheme == ResourceScheme) {
                Assembly resourceAssembly = _assemblyMapping[absoluteUri.Host];
                string resourceName = absoluteUri.AbsolutePath.Substring(1).Replace("/", ".");
                Stream stream = resourceAssembly.GetManifestResourceStream(resourceName);
                return stream;
            }

            return base.GetEntity(absoluteUri, role, ofObjectToReturn);
        }

        public override Uri ResolveUri(Uri baseUri, string relativeUri) {

            // Is this a DocType Public Id?
            if (baseUri == null) {
                Resource resource;
                if (_publicIdResources.TryGetValue(relativeUri, out resource)) {
                    return resource.AbsoluteUri;
                }
            }

            return base.ResolveUri(baseUri, relativeUri);
        }

        private const string ResourceScheme = "resource";
    }
}

The first significant change is to derive from XmlUrlResolver, instead of the abstract XmlResolver. This allows us to defer to an instance of XmlResolver.GetEntity that can download an xml file from a given URL (file or http). Now we can just concentrate on handling our own URI scheme.

Other than that, it's essentially as I described in my last post - I only recognise DocType Public Id's, and convert them to my own resource:// URI scheme during ResolveUri, with the host being the assembly containing the resource, and the path being the namespace of the resource. Any external references will get resolved against this URI, so must live in the same resource namespace, with the correct name. GetEntity simply pulls the resource out as a stream, or passes the request to the base class.

The limitation of this class is that it only recognises DocType Public Ids. If I wanted to simply replace a http based external reference, I'd need to tweak the class to capture that information and intercept it in ResolveUri. It wouldn't be difficult, and is left as an exercise to the reader.

Finally, I've got two little factory classes to create the XmlResolver and the XmlParserContext:

using System.Xml;

namespace SticklebackPlastic.Xml {

    internal static class XhtmlParserContextFactory {

        internal static XmlParserContext CreateStrict() {
            XmlNameTable nameTable = new NameTable();
            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
            XmlParserContext context = new XmlParserContext(nameTable, namespaceManager, null, XmlSpace.None);

            context.DocTypeName = "html";
            context.PublicId = "-//W3C//DTD XHTML 1.0 Strict//EN";
            context.SystemId = "xhtml1-strict.dtd";
            return context;
        }
    }
}

and:

using System.Reflection;
using System.Xml;

namespace SticklebackPlastic.Xml {

    internal static class XhtmlResolverFactory {

        internal static XmlResolver Create() {
            XmlResourceResolver resolver = new XmlResourceResolver();

            Assembly assembly = typeof (XhtmlResolverFactory).Assembly;
            string resourceNamespace = typeof (XhtmlResolverFactory).Namespace;

            resolver.AddPublicIdMapping("-//W3C//DTD XHTML 1.0 Strict//EN", assembly, resourceNamespace, XhtmlStrictDtd);
            resolver.AddPublicIdMapping("-//W3C//DTD XHTML 1.0 Transitional//EN", assembly, resourceNamespace, XhtmlTransitionalDtd);
            resolver.AddPublicIdMapping("-//W3C//DTD XHTML 1.0 Frameset//EN", assembly, resourceNamespace, XhtmlFramesetDtd);

            return resolver;
        }

        private const string XhtmlStrictDtd = "xhtml1-strict.dtd";
        private const string XhtmlTransitionalDtd = "xhtml1-transitional.dtd";
        private const string XhtmlFramesetDtd = "xhtml1-frameset.dtd";
    }
}

Forgive the lack of xml docs, this post is long enough without it...

Tags:

Comments (4) -

casinos en lignes
casinos en lignes France
7/19/2011 10:37:57 AM #

An powerful share, I just settled this onto a mate who was doing a little assay on this. And he in event bought me breakfast  because I institute it for him.. smile. So obstruction me express differently that: Thnx in return the treat! But yeah Thnkx during spending the moment to deliberate over this, I  feel strongly to it and love reading more on this topic. If practicable, as you suit expertise, would you mind updating your blog with  more details? It is highly caring recompense me. Big thumb up an eye to this blog post!

Reply

best suv 2011
best suv 2011
7/20/2011 10:34:28 PM #

I've recently began a weblog, the data you provide on this site has helped me tremendously. Thanks for all your time &amp; work.

Reply

Stephane
Stephane
10/22/2011 9:43:47 AM #

Hi

nice post here may come back soon
keep update

Reply

Guide Marrakech
Guide Marrakech France
11/12/2011 4:16:58 PM #

Rattling nice word can be base on blog .

Reply

Pingbacks and trackbacks (1)+

Add comment

biuquote
  • Comment
  • Preview
Loading

Month List

RecentComments

Comment RSS