#
# $Id: defaultresolver.icn,v 1.3 2004/10/03 15:13:39 rparlett Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package xml

import http, net


#
# The default resolver resolves from a URL or the local file system.
#
# If the system id begins with "http://", it is treated as a web URL, and
# loaded over the network.  
#
# If it begins with "file://", then it is treated as a
# local file (for an absolute path on Unix like systems, that would be
# three forward slashes at the start : "file:///tmp/file.dtd").
#
# Otherwise, the system id is treated as a filename.
#
class DefaultResolver : Resolver(public_mapping, uri_cache, cache_uris_flag)
   #
   # Set a mapping from a public id to an alternative URI.  The alternative URI
   # will then be loaded when the public id is encountered, rather than the
   # given system id.  The alternative may be a simple filename if desired.
   #
   # @example 
   # @  set_public_mapping("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", 
   # @                     "/tmp/web-app_2_3.dtd")
   # @param pub_id the public id
   # @param uri the alternative uri
   #
   method set_public_mapping(pub_id, uri)
      insert(public_mapping, pub_id, uri)
   end

   #
   # Configure the resolver so that it remembers URIs internally to save getting them
   # more than once.  This is on by default.
   #
   method set_cache_uris()
      self.cache_uris_flag := 1
   end

   #
   # Turn off the caching feature.
   #
   method clear_cache_uris()
      self.cache_uris_flag := &null
   end

   #
   # Cache an individual system id (URI) to the given value.
   #
   method cache_uri(sys_id, val)
      insert(uri_cache, sys_id, val)
   end

   method resolve(external_id)
      local sys_id, res

      #
      # Try to map from the public id (if any) to an alternative URI
      #
      if member(public_mapping, external_id.get_public_id()) then
         sys_id := public_mapping[external_id.get_public_id()]
      else
         sys_id := external_id.get_system_id()

      #
      # Try the cache.
      #
      if \cache_uris_flag & member(uri_cache, sys_id) then
         return uri_cache[sys_id]

      res := resolve_impl(sys_id) | fail

      #
      # Cache if appropriate
      #
      if \cache_uris_flag then
         cache_uri(sys_id, res)

      return res
   end

   method resolve_impl(sys_id)
      if match("http://", sys_id) then
         return get_http(sys_id)

      if match("file://", sys_id) then
         return get_local(sys_id[8:0])

      return get_local(sys_id)
   end

   method get_local(sys_id)
      local res, f
      f := open(sys_id, "r") | fail
      res := ""
      while res ||:= reads(f, 1000)
      close(f)
      return res
   end

   method get_http(sys_id)
      local hc, url, hp, hr

      hc := HttpClient()
      url := URL()
      url.parse(sys_id) | return error(url)

      hr := HttpRequest()
      hr.set_url(url)

      hp := hc.retrieve(hr) | return error(hc)

      return hp.get_data()
   end

   initially
      public_mapping := table()
      uri_cache := table()
      cache_uris_flag := 1
end