r5323: *** empty log message ***
authorKevin M. Rosenberg <kevin@rosenberg.net>
Fri, 18 Jul 2003 20:34:37 +0000 (20:34 +0000)
committerKevin M. Rosenberg <kevin@rosenberg.net>
Fri, 18 Jul 2003 20:34:37 +0000 (20:34 +0000)
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/postinst [new file with mode: 0644]
debian/prerm [new file with mode: 0644]
debian/rules [new file with mode: 0755]
puri.asd [new file with mode: 0644]
src.lisp [new file with mode: 0644]
tests.lisp [new file with mode: 0644]

diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..dd4257e
--- /dev/null
@@ -0,0 +1,21 @@
+cl-uri (1.2-1) unstable; urgency=low
+
+  * More porting fixes
+
+ -- Kevin M. Rosenberg <kmr@debian.org>  Fri, 18 Jul 2003 13:59:51 -0600
+
+cl-uri (1.1-1) unstable; urgency=low
+
+  * Fix some porting issues
+
+ -- Kevin M. Rosenberg <kmr@debian.org>  Fri, 18 Jul 2003 08:59:04 -0600
+
+cl-uri (1.0) unstable; urgency=low
+
+  * Initial upload
+  * Changes compared to upstream: 
+     - Added .asd file for use with ASDF
+     - Include if* source in the uri-src.lisp file
+     - Ported from AllegroCL specific functions
+
+ -- Kevin M. Rosenberg <kmr@debian.org>  Thu, 17 Jul 2003 21:39:34 -0600
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..11f83bf
--- /dev/null
@@ -0,0 +1,16 @@
+Source: cl-puri
+Section: devel
+Priority: optional
+Maintainer: Kevin M. Rosenberg <kmr@debian.org>
+Build-Depends-Indep: debhelper (>= 4.0.0)
+Standards-Version: 3.6.0
+
+Package: cl-puri
+Architecture: all
+Depends: ${shlibs:Depends}, common-lisp-controller (>= 3.47)
+Recommends: cl-tester
+Description: Common Lisp Portable URI Library
+ This is portable Universal Resource Identifier (RFC 2396)
+ library for Common Lisp programs. It's is based on Franz,
+ Inc's opensource package with porting work perform to run on
+ other platforms. A regression test package is included.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..85917e7
--- /dev/null
@@ -0,0 +1,90 @@
+This package was debianized by Kevin M. Rosenberg <kmr@debian.org> in
+July 2003.
+
+It was downloaded from http://opensource.franz.com/uri/
+Upstream Authors: Franz Inc. with modifications by Kevin Rosenberg
+
+Copyright:
+
+copyright (c) 1986-2000 Franz Inc, Berkeley, CA
+copyright (c) 2003 Kevin Rosenberg
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the version 2.1 of the GNU Lesser General Public
+License as published by the Free Software Foundation, as clarified by
+the Franz preamble to the LGPL found in
+http://opensource.franz.com/preamble.html. The preambled is copied
+below.
+
+This code is distributed in the hope that it will be useful,
+but without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.  See the GNU
+Lesser General Public License for more details.
+
+The GNU Lessor General Public License can be found in your Debian file
+system in /usr/share/common-licenses/LGPL.
+
+Preamble to the Gnu Lesser General Public License
+-------------------------------------------------
+Copyright (c) 2000 Franz Incorporated, Berkeley, CA 94704
+
+The concept of the GNU Lesser General Public License version 2.1
+("LGPL") has been adopted to govern the use and distribution of
+above-mentioned application. However, the LGPL uses terminology that
+is more appropriate for a program written in C than one written in
+Lisp. Nevertheless, the LGPL can still be applied to a Lisp program if
+certain clarifications are made. This document details those
+clarifications. Accordingly, the license for the open-source Lisp
+applications consists of this document plus the LGPL. Wherever there
+is a conflict between this document and the LGPL, this document takes
+precedence over the LGPL.
+
+A "Library" in Lisp is a collection of Lisp functions, data and
+foreign modules. The form of the Library can be Lisp source code (for
+processing by an interpreter) or object code (usually the result of
+compilation of source code or built with some other
+mechanisms). Foreign modules are object code in a form that can be
+linked into a Lisp executable. When we speak of functions we do so in
+the most general way to include, in addition, methods and unnamed
+functions. Lisp "data" is also a general term that includes the data
+structures resulting from defining Lisp classes. A Lisp application
+may include the same set of Lisp objects as does a Library, but this
+does not mean that the application is necessarily a "work based on the
+Library" it contains.
+
+The Library consists of everything in the distribution file set before
+any modifications are made to the files. If any of the functions or
+classes in the Library are redefined in other files, then those
+redefinitions ARE considered a work based on the Library. If
+additional methods are added to generic functions in the Library,
+those additional methods are NOT considered a work based on the
+Library. If Library classes are subclassed, these subclasses are NOT
+considered a work based on the Library. If the Library is modified to
+explicitly call other functions that are neither part of Lisp itself
+nor an available add-on module to Lisp, then the functions called by
+the modified Library ARE considered a work based on the Library. The
+goal is to ensure that the Library will compile and run without
+getting undefined function errors.
+
+It is permitted to add proprietary source code to the Library, but it
+must be done in a way such that the Library will still run without
+that proprietary code present. Section 5 of the LGPL distinguishes
+between the case of a library being dynamically linked at runtime and
+one being statically linked at build time. Section 5 of the LGPL
+states that the former results in an executable that is a "work that
+uses the Library." Section 5 of the LGPL states that the latter
+results in one that is a "derivative of the Library", which is
+therefore covered by the LGPL. Since Lisp only offers one choice,
+which is to link the Library into an executable at build time, we
+declare that, for the purpose applying the LGPL to the Library, an
+executable that results from linking a "work that uses the Library"
+with the Library is considered a "work that uses the Library" and is
+therefore NOT covered by the LGPL.
+
+Because of this declaration, section 6 of LGPL is not applicable to
+the Library. However, in connection with each distribution of this
+executable, you must also deliver, in accordance with the terms and
+conditions of the LGPL, the source code of Library (or your derivative
+thereof) that is incorporated into this executable.
+
+
diff --git a/debian/postinst b/debian/postinst
new file mode 100644 (file)
index 0000000..c53065e
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+set -e
+
+LISP_PKG=puri
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+#
+# quoting from the policy:
+#     Any necessary prompting should almost always be confined to the
+#     post-installation script, and should be protected with a conditional
+#     so that unnecessary prompting doesn't happen if a package's
+#     installation fails and the `postinst' is called with `abort-upgrade',
+#     `abort-remove' or `abort-deconfigure'.
+
+case "$1" in
+    configure)
+       /usr/sbin/register-common-lisp-source ${LISP_PKG}
+       ;;
+    abort-upgrade|abort-remove|abort-deconfigure)
+       ;;
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 1
+       ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/prerm b/debian/prerm
new file mode 100644 (file)
index 0000000..3e18302
--- /dev/null
@@ -0,0 +1,37 @@
+#! /bin/sh
+set -e
+
+LISP_PKG=puri
+
+# summary of how this script can be called:
+#        * <prerm> `remove'
+#        * <old-prerm> `upgrade' <new-version>
+#        * <new-prerm> `failed-upgrade' <old-version>
+#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+#        * <deconfigured's-prerm> `deconfigure' `in-favour'
+#          <package-being-installed> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+    remove|upgrade|deconfigure)
+       /usr/sbin/unregister-common-lisp-source ${LISP_PKG}
+        ;;
+    failed-upgrade)
+        ;;
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 1
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..4c439c8
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/make -f
+
+pkg    := puri
+debpkg  := cl-puri
+
+
+clc-source     := usr/share/common-lisp/source
+clc-systems    := usr/share/common-lisp/systems
+clc-puri       := $(clc-source)/$(pkg)
+
+doc-dir                := usr/share/doc/$(debpkg)
+
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       # Add here commands to configure the package.
+
+       touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp 
+       dh_testdir
+       # Add here commands to compile the package.
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+       # Add here commands to clean up after the build process.
+       rm -f debian/cl-puri.postinst.* debian/cl-puri.prerm.*
+       dh_clean
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       # Add here commands to install the package into debian/puri.
+       dh_installdirs $(clc-systems) $(clc-puri)
+       dh_install *.asd $(shell echo *.lisp) $(clc-puri)
+       dh_link $(clc-puri)/puri.asd $(clc-systems)/puri.asd
+
+# Build architecture-independent files here.
+binary-indep: build install
+
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+       dh_installdocs
+       dh_installchangelogs
+       dh_strip
+       dh_compress
+       dh_fixperms
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
+
diff --git a/puri.asd b/puri.asd
new file mode 100644 (file)
index 0000000..1e09b66
--- /dev/null
+++ b/puri.asd
@@ -0,0 +1,29 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; Programmer: Kevin Rosenberg
+
+
+(in-package #:cl-user)
+(defpackage #:puri-system (:use #:cl #:asdf))
+(in-package #:puri-system)
+
+
+(defsystem puri
+  :name "cl-puri"
+  :maintainer "Kevin M. Rosenberg <kmr@debian.org>"
+  :licence "GNU Lesser General Public License"
+  :description "Portable Universal Resource Indentifier Library"
+  :components
+  ((:file "src")))
+
+(defmethod perform ((o test-op) (c (eql (find-system 'puri))))
+  (oos 'load-op 'puri-tests)
+  (oos 'test-op 'puri-tests))
+
+(defsystem puri-tests
+    :depends-on (:rt :tester) 
+    :components
+    ((:file "tests")))
+
+(defmethod perform ((o test-op) (c (eql (find-system 'puri-tests))))
+  (or (funcall (intern (symbol-name '#:do-tests) (find-package :rt)))
+      (error "test-op failed")))
diff --git a/src.lisp b/src.lisp
new file mode 100644 (file)
index 0000000..8d98c19
--- /dev/null
+++ b/src.lisp
@@ -0,0 +1,1374 @@
+;; -*- mode: common-lisp; package: net.uri -*-
+;; Support for URIs in Allegro.
+;; For general URI information see RFC2396.
+;;
+;; copyright (c) 1999-2001 Franz Inc, Berkeley, CA - All rights reserved.
+;; copyright (c) 2003 Kevin Rosenberg (porting changes)
+;;
+;; The software, data and information contained herein are proprietary
+;; to, and comprise valuable trade secrets of, Franz, Inc.  They are
+;; given in confidence by Franz, Inc. pursuant to a written license
+;; agreement, and may be stored and used only in accordance with the terms
+;; of such license.
+;;
+;; Restricted Rights Legend
+;; ------------------------
+;; Use, duplication, and disclosure of the software, data and information
+;; contained herein by any agency, department or entity of the U.S.
+;; Government are subject to restrictions of Restricted Rights for
+;; Commercial Software developed at private expense as specified in
+;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable.
+;;
+;; Original version from ACL 6.1:
+;; uri.cl,v 2.3.6.4.2.1 2001/08/09 17:42:39 layer
+;;
+;; $Id: src.lisp,v 1.1 2003/07/18 20:34:23 kevin Exp $
+
+(defpackage #:puri
+  (:use #:cl)
+  (:export
+   #:uri                               ; the type and a function
+   #:uri-p
+   #:copy-uri
+
+   #:uri-scheme                                ; and slots
+   #:uri-host #:uri-port
+   #:uri-path
+   #:uri-query
+   #:uri-fragment
+   #:uri-plist
+   #:uri-authority                     ; pseudo-slot accessor
+
+   #:urn                               ; class
+   #:urn-nid                           ; pseudo-slot accessor
+   #:urn-nss                           ; pseudo-slot accessor
+   
+   #:*strict-parse*
+   #:parse-uri
+   #:merge-uris
+   #:enough-uri
+   #:uri-parsed-path
+   #:render-uri
+
+   #:make-uri-space                    ; interning...
+   #:uri-space
+   #:uri=
+   #:intern-uri
+   #:unintern-uri
+   #:do-all-uris))
+
+(in-package :net.uri)
+
+(eval-when (compile) (declaim (optimize (speed 3))))
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+  (defvar if*-keyword-list '("then" "thenret" "else" "elseif")))
+
+#-allegro
+(define-condition parse-error (error)
+  ()
+  )
+
+
+(defun .parse-error (fmt &rest args)
+  #+allegro (apply #'excl::.parse-error fmt args)
+  #-allegro (error 
+            (make-condition 'parse-error :format-control fmt
+                            :format-arguments args)))
+
+(defun internal-reader-error (stream fmt &rest args)
+  #+allegro
+  (apply #'excl::internal-reader-error stream fmt args)
+  #-allegro
+  (apply #'format stream
+        "#u takes a string or list argument: ~s" args))
+
+#-allegro (defvar *current-case-mode* :case-insensitive-upper)
+
+;; From Larry Hunter with modifications
+(defun position-char (char string start max)
+  (declare (optimize (speed 3) (safety 0) (space 0))
+          (fixnum start max) (simple-string string))
+  (do* ((i start (1+ i)))
+       ((= i max) nil)
+    (declare (fixnum i))
+    (when (char= char (schar string i)) (return i))))
+
+#+allegro 
+(defun delimited-string-to-list (string &optional (separator #\space)) 
+  (excl:delimited-string-to-list string))
+
+(defun delimited-string-to-list (string &optional (separator #\space) 
+                                skip-terminal)
+  (declare (optimize (speed 3) (safety 0) (space 0)
+                    (compilation-speed 0))
+          (type string string)
+          (type character separator))
+  (do* ((len (length string))
+       (output '())
+       (pos 0)
+       (end (position-char separator string pos len)
+            (position-char separator string pos len)))
+       ((null end)
+       (if (< pos len)
+           (push (subseq string pos) output)
+           (when (or (not skip-terminal) (zerop len))
+             (push "" output)))
+       (nreverse output))
+    (declare (type fixnum pos len)
+            (type (or null fixnum) end))
+    (push (subseq string pos end) output)
+    (setq pos (1+ end))))
+  
+(defmacro if* (&rest args)
+   (do ((xx (reverse args) (cdr xx))
+       (state :init)
+       (elseseen nil)
+       (totalcol nil)
+       (lookat nil nil)
+       (col nil))
+       ((null xx)
+       (cond ((eq state :compl)
+              `(cond ,@totalcol))
+             (t (error "if*: illegal form ~s" args))))
+       (cond ((and (symbolp (car xx))
+                  (member (symbol-name (car xx))
+                          if*-keyword-list
+                          :test #'string-equal))
+             (setq lookat (symbol-name (car xx)))))
+
+       (cond ((eq state :init)
+             (cond (lookat (cond ((string-equal lookat "thenret")
+                                  (setq col nil
+                                        state :then))
+                                 (t (error
+                                     "if*: bad keyword ~a" lookat))))
+                   (t (setq state :col
+                            col nil)
+                      (push (car xx) col))))
+            ((eq state :col)
+             (cond (lookat
+                    (cond ((string-equal lookat "else")
+                           (cond (elseseen
+                                  (error
+                                   "if*: multiples elses")))
+                           (setq elseseen t)
+                           (setq state :init)
+                           (push `(t ,@col) totalcol))
+                          ((string-equal lookat "then")
+                           (setq state :then))
+                          (t (error "if*: bad keyword ~s"
+                                             lookat))))
+                   (t (push (car xx) col))))
+            ((eq state :then)
+             (cond (lookat
+                    (error
+                     "if*: keyword ~s at the wrong place " (car xx)))
+                   (t (setq state :compl)
+                      (push `(,(car xx) ,@col) totalcol))))
+            ((eq state :compl)
+             (cond ((not (string-equal lookat "elseif"))
+                    (error "if*: missing elseif clause ")))
+             (setq state :init)))))
+
+
+(defclass uri ()
+  (
+;;;; external:
+   (scheme :initarg :scheme :initform nil :accessor uri-scheme)
+   (host :initarg :host :initform nil :accessor uri-host)
+   (port :initarg :port :initform nil :accessor uri-port)
+   (path :initarg :path :initform nil :accessor uri-path)
+   (query :initarg :query :initform nil :accessor uri-query)
+   (fragment :initarg :fragment :initform nil :accessor uri-fragment)
+   (plist :initarg :plist :initform nil :accessor uri-plist)
+
+;;;; internal:
+   (escaped
+    ;; used to prevent unnessary work, looking for chars to escape and
+    ;; unescape.
+    :initarg :escaped :initform nil :accessor uri-escaped)
+   (string
+    ;; the cached printable representation of the URI.  It *might* be
+    ;; different than the original string, though, because the user might
+    ;; have escaped non-reserved chars--they won't be escaped when the URI
+    ;; is printed.
+    :initarg :string :initform nil :accessor uri-string)
+   (parsed-path
+    ;; the cached parsed representation of the URI path.
+    :initarg :parsed-path
+    :initform nil
+    :accessor .uri-parsed-path)
+   (hashcode
+    ;; cached sxhash, so we don't have to compute it more than once.
+    :initarg :hashcode :initform nil :accessor uri-hashcode)))
+
+(defclass urn (uri)
+  ((nid :initarg :nid :initform nil :accessor urn-nid)
+   (nss :initarg :nss :initform nil :accessor urn-nss)))
+
+(eval-when (compile eval)
+  (defmacro clear-caching-on-slot-change (name)
+    `(defmethod (setf ,name) :around (new-value (self uri))
+       (declare (ignore new-value))
+       (prog1 (call-next-method)
+        (setf (uri-string self) nil)
+        ,@(when (eq name 'uri-path) `((setf (.uri-parsed-path self) nil)))
+        (setf (uri-hashcode self) nil))))
+  )
+
+(clear-caching-on-slot-change uri-scheme)
+(clear-caching-on-slot-change uri-host)
+(clear-caching-on-slot-change uri-port)
+(clear-caching-on-slot-change uri-path)
+(clear-caching-on-slot-change uri-query)
+(clear-caching-on-slot-change uri-fragment)
+
+
+(defmethod make-load-form ((self uri) &optional env)
+  (declare (ignore env))
+  `(make-instance ',(class-name (class-of self))
+     :scheme ,(uri-scheme self)
+     :host ,(uri-host self)
+     :port ,(uri-port self)
+     :path ',(uri-path self)
+     :query ,(uri-query self)
+     :fragment ,(uri-fragment self)
+     :plist ',(uri-plist self)
+     :string ,(uri-string self)
+     :parsed-path ',(.uri-parsed-path self)))
+
+(defmethod uri-p ((thing uri)) t)
+(defmethod uri-p ((thing t)) nil)
+
+(defun copy-uri (uri
+                &key place
+                     (scheme (when uri (uri-scheme uri)))
+                     (host (when uri (uri-host uri)))
+                     (port (when uri (uri-port uri)))
+                     (path (when uri (uri-path uri)))
+                     (parsed-path
+                      (when uri (copy-list (.uri-parsed-path uri))))
+                     (query (when uri (uri-query uri)))
+                     (fragment (when uri (uri-fragment uri)))
+                     (plist (when uri (copy-list (uri-plist uri))))
+                     (class (when uri (class-of uri)))
+                &aux (escaped (when uri (uri-escaped uri))))
+  (if* place
+     then (setf (uri-scheme place) scheme)
+         (setf (uri-host place) host)
+         (setf (uri-port place) port)
+         (setf (uri-path place) path)
+         (setf (.uri-parsed-path place) parsed-path)
+         (setf (uri-query place) query)
+         (setf (uri-fragment place) fragment)
+         (setf (uri-plist place) plist)
+         (setf (uri-escaped place) escaped)
+         (setf (uri-string place) nil)
+         (setf (uri-hashcode place) nil)
+         place
+   elseif (eq 'uri class)
+     then ;; allow the compiler to optimize the call to make-instance:
+         (make-instance 'uri
+           :scheme scheme :host host :port port :path path
+           :parsed-path parsed-path
+           :query query :fragment fragment :plist plist
+           :escaped escaped :string nil :hashcode nil)
+     else (make-instance class
+           :scheme scheme :host host :port port :path path
+           :parsed-path parsed-path
+           :query query :fragment fragment :plist plist
+           :escaped escaped :string nil :hashcode nil)))
+
+(defmethod uri-parsed-path ((uri uri))
+  (when (uri-path uri)
+    (when (null (.uri-parsed-path uri))
+      (setf (.uri-parsed-path uri)
+       (parse-path (uri-path uri) (uri-escaped uri))))
+    (.uri-parsed-path uri)))
+
+(defmethod (setf uri-parsed-path) (path-list (uri uri))
+  (assert (and (consp path-list)
+              (or (member (car path-list) '(:absolute :relative)
+                          :test #'eq))))
+  (setf (uri-path uri) (render-parsed-path path-list t))
+  (setf (.uri-parsed-path uri) path-list)
+  path-list)
+
+(defun uri-authority (uri)
+  (when (uri-host uri)
+    (let ((*print-pretty* nil))
+      (format nil "~a~@[:~a~]" (uri-host uri) (uri-port uri)))))
+
+(defun uri-nid (uri)
+  (if* (equalp "urn" (uri-scheme uri))
+     then (uri-host uri)
+     else (error "URI is not a URN: ~s." uri)))
+
+(defun uri-nss (uri)
+  (if* (equalp "urn" (uri-scheme uri))
+     then (uri-path uri)
+     else (error "URI is not a URN: ~s." uri)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Parsing
+
+(defparameter *excluded-characters*
+    '(;; `delims' (except #\%, because it's handled specially):
+      #\< #\> #\" #\space #\#
+      ;; `unwise':
+      #\{ #\} #\| #\\ #\^ #\[ #\] #\`))
+
+(defun reserved-char-vector (chars &key except)
+  (do* ((a (make-array 127 :element-type 'bit :initial-element 0))
+       (chars chars (cdr chars))
+       (c (car chars) (car chars)))
+      ((null chars) a)
+    (if* (and except (member c except :test #'char=))
+       thenret
+       else (setf (sbit a (char-int c)) 1))))
+
+(defparameter *reserved-characters*
+    (reserved-char-vector
+     (append *excluded-characters*
+            '(#\; #\/ #\? #\: #\@ #\& #\= #\+ #\$ #\, #\%))))
+(defparameter *reserved-authority-characters*
+    (reserved-char-vector
+     (append *excluded-characters* '(#\; #\/ #\? #\: #\@))))
+(defparameter *reserved-path-characters*
+    (reserved-char-vector
+     (append *excluded-characters*
+            '(#\;
+;;;;The rfc says this should be here, but it doesn't make sense.
+              ;; #\=
+              #\/ #\?))))
+(defparameter *reserved-path-characters2*
+    ;; These are the same characters that are in
+    ;; *reserved-path-characters*, minus #\/.  Why?  Because the parsed
+    ;; representation of the path can contain the %2f converted into a /.
+    ;; That's the whole point of having the parsed representation, so that
+    ;; lisp programs can deal with the path element data in the most
+    ;; convenient form.
+    (reserved-char-vector
+     (append *excluded-characters*
+            '(#\;
+;;;;The rfc says this should be here, but it doesn't make sense.
+              ;; #\=
+              #\?))))
+(defparameter *reserved-fragment-characters*
+    (reserved-char-vector (remove #\# *excluded-characters*)))
+
+(eval-when (compile eval)
+(defun gen-char-range-list (start end)
+  (do* ((res '())
+       (endcode (1+ (char-int end)))
+       (chcode (char-int start)
+               (1+ chcode))
+       (hyphen nil))
+      ((= chcode endcode)
+       ;; - has to be first, otherwise it signifies a range!
+       (if* hyphen
+         then (setq res (nreverse res))
+              (push #\- res)
+              res
+         else (nreverse res)))
+    (if* (= #.(char-int #\-) chcode)
+       then (setq hyphen t)
+       else (push (code-char chcode) res))))
+)
+
+(defparameter *valid-nid-characters*
+    (reserved-char-vector
+     '#.(nconc (gen-char-range-list #\a #\z)
+              (gen-char-range-list #\A #\Z)
+              (gen-char-range-list #\0 #\9)
+              '(#\- #\. #\+))))
+(defparameter *reserved-nss-characters*
+    (reserved-char-vector
+     (append *excluded-characters* '(#\& #\~ #\/ #\?))))
+
+(defparameter *illegal-characters*
+    (reserved-char-vector (remove #\# *excluded-characters*)))
+(defparameter *strict-illegal-query-characters*
+    (reserved-char-vector (append '(#\?) (remove #\# *excluded-characters*))))
+(defparameter *illegal-query-characters*
+    (reserved-char-vector
+     *excluded-characters* :except '(#\^ #\| #\#)))
+
+
+(defun parse-uri (thing &key (class 'uri) &aux escape)
+  (when (uri-p thing) (return-from parse-uri thing))
+  
+  (setq escape (escape-p thing))
+  (multiple-value-bind (scheme host port path query fragment)
+      (parse-uri-string thing)
+    (when scheme
+      (setq scheme
+       (intern (funcall
+                (case *current-case-mode*
+                  ((:case-insensitive-upper :case-sensitive-upper)
+                   #'string-upcase)
+                  ((:case-insensitive-lower :case-sensitive-lower)
+                   #'string-downcase))
+                (decode-escaped-encoding scheme escape))
+               (find-package :keyword))))
+    
+    (when (and scheme (eq :urn scheme))
+      (return-from parse-uri
+       (make-instance 'urn :scheme scheme :nid host :nss path)))
+    
+    (when host (setq host (decode-escaped-encoding host escape)))
+    (when port
+      (setq port (read-from-string port))
+      (when (not (numberp port)) (error "port is not a number: ~s." port))
+      (when (not (plusp port))
+       (error "port is not a positive integer: ~d." port))
+      (when (eql port (case scheme
+                     (:http 80)
+                     (:https 443)
+                     (:ftp 21)
+                     (:telnet 23)))
+       (setq port nil)))
+    (when (or (string= "" path)
+             (and ;; we canonicalize away a reference to just /:
+              scheme
+              (member scheme '(:http :https :ftp) :test #'eq)
+              (string= "/" path)))
+      (setq path nil))
+    (when path
+      (setq path
+       (decode-escaped-encoding path escape *reserved-path-characters*)))
+    (when query (setq query (decode-escaped-encoding query escape)))
+    (when fragment
+      (setq fragment
+       (decode-escaped-encoding fragment escape
+                                *reserved-fragment-characters*)))
+    (if* (eq 'uri class)
+       then ;; allow the compiler to optimize the make-instance call:
+           (make-instance 'uri
+             :scheme scheme
+             :host host
+             :port port
+             :path path
+             :query query
+             :fragment fragment
+             :escaped escape)
+       else ;; do it the slow way:
+           (make-instance class
+             :scheme scheme
+             :host host
+             :port port
+             :path path
+             :query query
+             :fragment fragment
+             :escaped escape))))
+
+(defmethod uri ((thing uri))
+  thing)
+
+(defmethod uri ((thing string))
+  (parse-uri thing))
+
+(defmethod uri ((thing t))
+  (error "Cannot coerce ~s to a uri." thing))
+
+(defvar *strict-parse* t)
+
+(defun parse-uri-string (string &aux (illegal-chars *illegal-characters*))
+  (declare (optimize (speed 3)))
+  ;; Speed is important, so use a specialized state machine instead of
+  ;; regular expressions for parsing the URI string. The regexp we are
+  ;; simulating:
+  ;;  ^(([^:/?#]+):)?
+  ;;   (//([^/?#]*))?
+  ;;   ([^?#]*)
+  ;;   (\?([^#]*))?
+  ;;   (#(.*))?
+  (let* ((state 0)
+        (start 0)
+        (end (length string))
+        (tokval nil)
+        (scheme nil)
+        (host nil)
+        (port nil)
+        (path-components '())
+        (query nil)
+        (fragment nil)
+        ;; namespace identifier, for urn parsing only:
+        (nid nil))
+    (declare (fixnum state start end))
+    (flet ((read-token (kind &optional legal-chars)
+            (setq tokval nil)
+            (if* (>= start end)
+               then :end
+               else (let ((sindex start)
+                          (res nil)
+                          c)
+                      (declare (fixnum sindex))
+                      (setq res
+                        (loop
+                          (when (>= start end) (return nil))
+                          (setq c (schar string start))
+                          (let ((ci (char-int c)))
+                            (if* legal-chars
+                               then (if* (and (eq :colon kind) (eq c #\:))
+                                       then (return :colon)
+                                     elseif (= 0 (sbit legal-chars ci))
+                                       then (.parse-error
+                                             "~
+URI ~s contains illegal character ~s at position ~d."
+                                             string c start))
+                             elseif (and (< ci 128)
+                                         *strict-parse*
+                                         (= 1 (sbit illegal-chars ci)))
+                               then (.parse-error "~
+URI ~s contains illegal character ~s at position ~d."
+                                                        string c start)))
+                          (case kind
+                            (:path (case c
+                                     (#\? (return :question))
+                                     (#\# (return :hash))))
+                            (:query (case c (#\# (return :hash))))
+                            (:rest)
+                            (t (case c
+                                 (#\: (return :colon))
+                                 (#\? (return :question))
+                                 (#\# (return :hash))
+                                 (#\/ (return :slash)))))
+                          (incf start)))
+                      (if* (> start sindex)
+                         then ;; we found some chars
+                              ;; before we stopped the parse
+                              (setq tokval (subseq string sindex start))
+                              :string
+                         else ;; immediately stopped at a special char
+                              (incf start)
+                              res))))
+          (failure (&optional why)
+            (.parse-error "illegal URI: ~s [~d]~@[: ~a~]"
+                                string state why))
+          (impossible ()
+            (.parse-error "impossible state: ~d [~s]" state string)))
+      (loop
+       (case state
+         (0 ;; starting to parse
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:slash (setq state 3))
+            (:string (setq state 1))
+            (:end (setq state 9))))
+         (1 ;; seen <token><special char>
+          (let ((token tokval))
+            (ecase (read-token t)
+              (:colon (setq scheme token)
+                      (if* (equalp "urn" scheme)
+                         then (setq state 15)
+                         else (setq state 2)))
+              (:question (push token path-components)
+                         (setq state 7))
+              (:hash (push token path-components)
+                     (setq state 8))
+              (:slash (push token path-components)
+                      (push "/" path-components)
+                      (setq state 6))
+              (:string (failure))
+              (:end (push token path-components)
+                    (setq state 9)))))
+         (2 ;; seen <scheme>:
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:slash (setq state 3))
+            (:string (setq state 10))
+            (:end (setq state 9))))
+         (10 ;; seen <scheme>:<token>
+          (let ((token tokval))
+            (ecase (read-token t)
+              (:colon (failure))
+              (:question (push token path-components)
+                         (setq state 7))
+              (:hash (push token path-components)
+                     (setq state 8))
+              (:slash (push token path-components)
+                      (setq state 6))
+              (:string (failure))
+              (:end (push token path-components)
+                    (setq state 9)))))
+         (3 ;; seen / or <scheme>:/
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (push "/" path-components)
+                       (setq state 7))
+            (:hash (push "/" path-components)
+                   (setq state 8))
+            (:slash (setq state 4))
+            (:string (push "/" path-components)
+                     (push tokval path-components)
+                     (setq state 6))
+            (:end (push "/" path-components)
+                  (setq state 9))))
+         (4 ;; seen [<scheme>:]//
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (failure))
+            (:hash (failure))
+            (:slash (failure))
+            (:string (setq host tokval)
+                     (setq state 11))
+            (:end (failure))))
+         (11 ;; seen [<scheme>:]//<host>
+          (ecase (read-token t)
+            (:colon (setq state 5))
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:slash (push "/" path-components)
+                    (setq state 6))
+            (:string (impossible))
+            (:end (setq state 9))))
+         (5 ;; seen [<scheme>:]//<host>:
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (failure))
+            (:hash (failure))
+            (:slash (push "/" path-components)
+                    (setq state 6))
+            (:string (setq port tokval)
+                     (setq state 12))
+            (:end (failure))))
+         (12 ;; seen [<scheme>:]//<host>:[<port>]
+          (ecase (read-token t)
+            (:colon (failure))
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:slash (push "/" path-components)
+                    (setq state 6))
+            (:string (impossible))
+            (:end (setq state 9))))
+         (6 ;; seen /
+          (ecase (read-token :path)
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:string (push tokval path-components)
+                     (setq state 13))
+            (:end (setq state 9))))
+         (13 ;; seen path
+          (ecase (read-token :path)
+            (:question (setq state 7))
+            (:hash (setq state 8))
+            (:string (impossible))
+            (:end (setq state 9))))
+         (7 ;; seen ?
+          (setq illegal-chars
+            (if* *strict-parse*
+               then *strict-illegal-query-characters*
+               else *illegal-query-characters*))
+          (ecase (prog1 (read-token :query)
+                   (setq illegal-chars *illegal-characters*))
+            (:hash (setq state 8))
+            (:string (setq query tokval)
+                     (setq state 14))
+            (:end (setq state 9))))
+         (14 ;; query
+          (ecase (read-token :query)
+            (:hash (setq state 8))
+            (:string (impossible))
+            (:end (setq state 9))))
+         (8 ;; seen #
+          (ecase (read-token :rest)
+            (:string (setq fragment tokval)
+                     (setq state 9))
+            (:end (setq state 9))))
+         (9 ;; done
+          (return
+            (values
+             scheme host port
+             (apply #'concatenate 'simple-string (nreverse path-components))
+             query fragment)))
+         ;; URN parsing:
+         (15 ;; seen urn:, read nid now
+          (case (read-token :colon *valid-nid-characters*)
+            (:string (setq nid tokval)
+                     (setq state 16))
+            (t (failure "missing namespace identifier"))))
+         (16 ;; seen urn:<nid>
+          (case (read-token t)
+            (:colon (setq state 17))
+            (t (failure "missing namespace specific string"))))
+         (17 ;; seen urn:<nid>:, rest is nss
+          (return (values scheme
+                          nid
+                          nil
+                          (progn
+                            (setq illegal-chars *reserved-nss-characters*)
+                            (read-token :rest)
+                            tokval))))
+         (t (.parse-error
+             "internal error in parse engine, wrong state: ~s." state)))))))
+
+(defun escape-p (string)
+  (declare (optimize (speed 3)))
+  (do* ((i 0 (1+ i))
+       (max (the fixnum (length string))))
+      ((= i max) nil)
+    (declare (fixnum i max))
+    (when (char= #\% (schar string i))
+      (return t))))
+
+(defun parse-path (path-string escape)
+  (do* ((xpath-list (delimited-string-to-list path-string #\/))
+       (path-list
+        (progn
+          (if* (string= "" (car xpath-list))
+             then (setf (car xpath-list) :absolute)
+             else (push :relative xpath-list))
+          xpath-list))
+       (pl (cdr path-list) (cdr pl))
+       segments)
+      ((null pl) path-list)
+    (if* (cdr (setq segments (delimited-string-to-list (car pl) #\;)))
+       then ;; there is a param
+;;;        (setf (car pl) segments)
+           (setf (car pl)
+             (mapcar #'(lambda (s)
+                         (decode-escaped-encoding
+                          s escape *reserved-path-characters2*))
+              segments))
+       else ;; no param
+;;;        (setf (car pl) (car segments))
+           (setf (car pl)
+             (decode-escaped-encoding
+              (car segments) escape *reserved-path-characters2*)))))
+
+(defun decode-escaped-encoding (string escape
+                               &optional (reserved-chars
+                                          *reserved-characters*))
+  ;; Return a string with the real characters.
+  (when (null escape) (return-from decode-escaped-encoding string))
+  (do* ((i 0 (1+ i))
+       (max (length string))
+       (new-string (copy-seq string))
+       (new-i 0 (1+ new-i))
+       ch ch2 chc chc2)
+      ((= i max)
+       #+allegro
+       (excl::.primcall 'sys::shrink-svector new-string new-i)
+       #+sbcl
+       (sb-kernel:shrink-vector new-string new-i)
+       #-(or allegro sbcl)
+       (subseq new-string 0 new-i)
+       new-string)
+    (if* (char= #\% (setq ch (schar string i)))
+       then (when (> (+ i 3) max)
+             (.parse-error
+              "Unsyntactic escaped encoding in ~s." string))
+           (setq ch (schar string (incf i)))
+           (setq ch2 (schar string (incf i)))
+           (when (not (and (setq chc (digit-char-p ch 16))
+                           (setq chc2 (digit-char-p ch2 16))))
+             (.parse-error
+              "Non-hexidecimal digits after %: %c%c." ch ch2))
+           (let ((ci (+ (* 16 chc) chc2)))
+             (if* (or (null reserved-chars)
+                      (= 0 (sbit reserved-chars ci)))
+                then ;; ok as is
+                     (setf (schar new-string new-i)
+                       (code-char ci))
+                else (setf (schar new-string new-i) #\%)
+                     (setf (schar new-string (incf new-i)) ch)
+                     (setf (schar new-string (incf new-i)) ch2)))
+       else (setf (schar new-string new-i) ch))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;; Printing
+
+(defun render-uri (uri stream
+                  &aux (escape (uri-escaped uri))
+                       (*print-pretty* nil))
+  (when (null (uri-string uri))
+    (setf (uri-string uri)
+      (let ((scheme (uri-scheme uri))
+           (host (uri-host uri))
+           (port (uri-port uri))
+           (path (uri-path uri))
+           (query (uri-query uri))
+           (fragment (uri-fragment uri)))
+       (concatenate 'simple-string
+         (when scheme
+           (encode-escaped-encoding
+            (string-downcase ;; for upper case lisps
+             (symbol-name scheme))
+            *reserved-characters* escape))
+         (when scheme ":")
+         (when host "//")
+         (when host
+           (encode-escaped-encoding
+            host *reserved-authority-characters* escape))
+         (when port ":")
+         (when port
+;;;; too slow until ACL 6.0:
+;;;        (format nil "~d" port)
+;;;        (princ-to-string port)
+           #-allegro (princ-to-string port)
+           #+allegro
+           (with-output-to-string (s)
+             (excl::maybe-print-fast s port))
+           )
+         (when path
+           (encode-escaped-encoding path
+                                    nil
+                                    ;;*reserved-path-characters*
+                                    escape))
+         (when query "?")
+         (when query (encode-escaped-encoding query nil escape))
+         (when fragment "#")
+         (when fragment (encode-escaped-encoding fragment nil escape))))))
+  (if* stream
+     then (format stream "~a" (uri-string uri))
+     else (uri-string uri)))
+
+(defun render-parsed-path (path-list escape)
+  (do* ((res '())
+       (first (car path-list))
+       (pl (cdr path-list) (cdr pl))
+       (pe (car pl) (car pl)))
+      ((null pl)
+       (when res (apply #'concatenate 'simple-string (nreverse res))))
+    (when (or (null first)
+             (prog1 (eq :absolute first)
+               (setq first nil)))
+      (push "/" res))
+    (if* (atom pe)
+       then (push
+            (encode-escaped-encoding pe *reserved-path-characters* escape)
+            res)
+       else ;; contains params
+           (push (encode-escaped-encoding
+                  (car pe) *reserved-path-characters* escape)
+                 res)
+           (dolist (item (cdr pe))
+             (push ";" res)
+             (push (encode-escaped-encoding
+                    item *reserved-path-characters* escape)
+                   res)))))
+
+(defun render-urn (urn stream
+                  &aux (*print-pretty* nil))
+  (when (null (uri-string urn))
+    (setf (uri-string urn)
+      (let ((nid (urn-nid urn))
+           (nss (urn-nss urn)))
+       (concatenate 'simple-string "urn:" nid ":" nss))))
+  (if* stream
+     then (format stream "~a" (uri-string urn))
+     else (uri-string urn)))
+
+(defparameter *escaped-encoding*
+    (vector #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\a #\b #\c #\d #\e #\f))
+
+(defun encode-escaped-encoding (string reserved-chars escape)
+  (when (null escape) (return-from encode-escaped-encoding string))
+  ;; Make a string as big as it possibly needs to be (3 times the original
+  ;; size), and truncate it at the end.
+  (do* ((max (length string))
+       (new-max (* 3 max)) ;; worst case new size
+       (new-string (make-string new-max))
+       (i 0 (1+ i))
+       (new-i -1)
+       c ci)
+      ((= i max)
+       #+allegro
+       (excl::.primcall 'sys::shrink-svector new-string (incf new-i))
+       #+sbcl
+       (sb-kernel:shrink-vector new-string (incf new-i))
+       #-(or allegro sbcl)
+       (subseq new-string 0 (incf new-i))
+       new-string)
+    (setq ci (char-int (setq c (schar string i))))
+    (if* (or (null reserved-chars)
+            (> ci 127)
+            (= 0 (sbit reserved-chars ci)))
+       then ;; ok as is
+           (incf new-i)
+           (setf (schar new-string new-i) c)
+       else ;; need to escape it
+           (multiple-value-bind (q r) (truncate ci 16)
+             (setf (schar new-string (incf new-i)) #\%)
+             (setf (schar new-string (incf new-i)) (elt *escaped-encoding* q))
+             (setf (schar new-string (incf new-i))
+               (elt *escaped-encoding* r))))))
+
+(defmethod print-object ((uri uri) stream)
+  (if* *print-escape*
+     then (format stream "#<~a ~a>" 'uri (render-uri uri nil))
+     else (render-uri uri stream)))
+
+(defmethod print-object ((urn urn) stream)
+  (if* *print-escape*
+     then (format stream "#<~a ~a>" 'uri (render-urn urn nil))
+     else (render-urn urn stream)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; merging and unmerging
+
+(defmethod merge-uris ((uri string) (base string) &optional place)
+  (merge-uris (parse-uri uri) (parse-uri base) place))
+
+(defmethod merge-uris ((uri uri) (base string) &optional place)
+  (merge-uris uri (parse-uri base) place))
+
+(defmethod merge-uris ((uri string) (base uri) &optional place)
+  (merge-uris (parse-uri uri) base place))
+
+(defmethod merge-uris ((uri uri) (base uri) &optional place)
+  ;; The following is from
+  ;; http://info.internet.isi.edu/in-notes/rfc/files/rfc2396.txt
+  ;; and is algorithm we use to merge URIs.
+  ;;
+  ;; For more information, see section 5.2 of the RFC.
+  ;;
+  (tagbody
+;;;; step 2
+    (when (and (null (uri-parsed-path uri))
+              (null (uri-scheme uri))
+              (null (uri-host uri))
+              (null (uri-port uri))
+              (null (uri-query uri)))
+      (return-from merge-uris
+       (let ((new (copy-uri base :place place)))
+         (when (uri-query uri)
+           (setf (uri-query new) (uri-query uri)))
+         (when (uri-fragment uri)
+           (setf (uri-fragment new) (uri-fragment uri)))
+         new)))
+
+    (setq uri (copy-uri uri :place place))
+
+;;;; step 3
+    (when (uri-scheme uri)
+      (return-from merge-uris uri))
+    (setf (uri-scheme uri) (uri-scheme base))
+  
+;;;; step 4
+    (when (uri-host uri) (go :done))
+    (setf (uri-host uri) (uri-host base))
+    (setf (uri-port uri) (uri-port base))
+    
+;;;; step 5
+    (let ((p (uri-parsed-path uri)))
+      (when (and p (eq :absolute (car p)))
+       (when (equal '(:absolute "") p)
+         ;; Canonicalize the way parsing does:
+         (setf (uri-path uri) nil))
+       (go :done)))
+    
+;;;; step 6
+    (let* ((base-path
+           (or (uri-parsed-path base)
+               ;; needed because we canonicalize away a path of just `/':
+               '(:absolute "")))
+          (path (uri-parsed-path uri))
+          new-path-list)
+      (when (not (eq :absolute (car base-path)))
+       (error "Cannot merge ~a and ~a, since latter is not absolute."
+              uri base))
+
+      ;; steps 6a and 6b:
+      (setq new-path-list
+       (append (butlast base-path)
+               (if* path then (cdr path) else '(""))))
+
+      ;; steps 6c and 6d:
+      (let ((last (last new-path-list)))
+       (if* (atom (car last))
+          then (when (string= "." (car last))
+                 (setf (car last) ""))
+          else (when (string= "." (caar last))
+                 (setf (caar last) ""))))
+      (setq new-path-list
+       (delete "." new-path-list :test #'(lambda (a b)
+                                           (if* (atom b)
+                                              then (string= a b)
+                                              else nil))))
+
+      ;; steps 6e and 6f:
+      (let ((npl (cdr new-path-list))
+           index tmp fix-tail)
+       (setq fix-tail
+         (string= ".." (let ((l (car (last npl))))
+                         (if* (atom l)
+                            then l
+                            else (car l)))))
+       (loop
+         (setq index
+           (position ".." npl
+                     :test #'(lambda (a b)
+                               (string= a
+                                        (if* (atom b)
+                                           then b
+                                           else (car b))))))
+         (when (null index) (return))
+         (when (= 0 index)
+           ;; The RFC says, in 6g, "that the implementation may handle
+           ;; this error by retaining these components in the resolved
+           ;; path, by removing them from the resolved path, or by
+           ;; avoiding traversal of the reference."  The examples in C.2
+           ;; imply that we should do the first thing (retain them), so
+           ;; that's what we'll do.
+           (return))
+         (if* (= 1 index)
+            then (setq npl (cddr npl))
+            else (setq tmp npl)
+                 (dotimes (x (- index 2)) (setq tmp (cdr tmp)))
+                 (setf (cdr tmp) (cdddr tmp))))
+       (setf (cdr new-path-list) npl)
+       (when fix-tail (setq new-path-list (nconc new-path-list '("")))))
+
+      ;; step 6g:
+      ;; don't complain if new-path-list starts with `..'.  See comment
+      ;; above about this step.
+
+      ;; step 6h:
+      (when (or (equal '(:absolute "") new-path-list)
+               (equal '(:absolute) new-path-list))
+       (setq new-path-list nil))
+      (setf (uri-path uri)
+       (render-parsed-path new-path-list
+                           ;; don't know, so have to assume:
+                           t)))
+
+;;;; step 7
+   :done
+    (return-from merge-uris uri)))
+
+(defmethod enough-uri ((uri string) (base string) &optional place)
+  (enough-uri (parse-uri uri) (parse-uri base) place))
+
+(defmethod enough-uri ((uri uri) (base string) &optional place)
+  (enough-uri uri (parse-uri base) place))
+
+(defmethod enough-uri ((uri string) (base uri) &optional place)
+  (enough-uri (parse-uri uri) base place))
+
+(defmethod enough-uri ((uri uri) (base uri) &optional place)
+  (let ((new-scheme nil)
+       (new-host nil)
+       (new-port nil)
+       (new-parsed-path nil))
+
+    (when (or (and (uri-scheme uri)
+                  (not (equalp (uri-scheme uri) (uri-scheme base))))
+             (and (uri-host uri)
+                  (not (equalp (uri-host uri) (uri-host base))))
+             (not (equalp (uri-port uri) (uri-port base))))
+      (return-from enough-uri uri))
+
+    (when (null (uri-host uri))
+      (setq new-host (uri-host base)))
+    (when (null (uri-port uri))
+      (setq new-port (uri-port base)))
+    
+    (when (null (uri-scheme uri))
+      (setq new-scheme (uri-scheme base)))
+
+    ;; Now, for the hard one, path.
+    ;; We essentially do here what enough-namestring does.
+    (do* ((base-path (uri-parsed-path base))
+         (path (uri-parsed-path uri))
+         (bp base-path (cdr bp))
+         (p path (cdr p)))
+       ((or (null bp) (null p))
+        ;; If p is nil, that means we have something like
+        ;; (enough-uri "/foo/bar" "/foo/bar/baz.htm"), so
+        ;; new-parsed-path will be nil.
+        (when (null bp)
+          (setq new-parsed-path (copy-list p))
+          (when (not (symbolp (car new-parsed-path)))
+            (push :relative new-parsed-path))))
+      (if* (equal (car bp) (car p))
+        thenret ;; skip it
+        else (setq new-parsed-path (copy-list p))
+             (when (not (symbolp (car new-parsed-path)))
+               (push :relative new-parsed-path))
+             (return)))
+
+    (let ((new-path 
+          (when new-parsed-path
+            (render-parsed-path new-parsed-path
+                                ;; don't know, so have to assume:
+                                t)))
+         (new-query (uri-query uri))
+         (new-fragment (uri-fragment uri))
+         (new-plist (copy-list (uri-plist uri))))
+      (if* (and (null new-scheme)
+               (null new-host)
+               (null new-port)
+               (null new-path)
+               (null new-parsed-path)
+               (null new-query)
+               (null new-fragment))
+        then ;; can't have a completely empty uri!
+             (copy-uri nil
+                       :class (class-of uri)
+                       :place place
+                       :path "/"
+                       :plist new-plist)
+        else (copy-uri nil
+                       :class (class-of uri)
+                       :place place
+                       :scheme new-scheme
+                       :host new-host
+                       :port new-port
+                       :path new-path
+                       :parsed-path new-parsed-path
+                       :query new-query
+                       :fragment new-fragment
+                       :plist new-plist)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; support for interning URIs
+
+(defun make-uri-space (&rest keys &key (size 777) &allow-other-keys)
+  #+allegro
+  (apply #'make-hash-table :size size
+        :hash-function 'uri-hash
+        :test 'uri= :values nil keys)
+  #-allegro
+  (apply #'make-hash-table :size size keys))
+
+(defun gethash-uri (uri table)
+  #+allegro (gethash uri table)
+  #-allegro 
+  (let* ((hash (uri-hash uri))
+        (existing (gethash hash table)))
+    (dolist (u existing)
+      (when (uri= u uri)
+       (return-from gethash-uri (values u t))))
+    (values nil nil)))
+
+(defun puthash-uri (uri table)
+  #+allegro (excl:puthash-key uri table)
+  #-allegro 
+  (let ((existing (gethash (uri-hash uri) table)))
+    (dolist (u existing)
+      (when (uri= u uri)
+       (return-from puthash-uri u)))
+    (setf (gethash (uri-hash uri) table)
+      (cons uri existing))
+    uri))
+
+
+(defun uri-hash (uri)
+  (if* (uri-hashcode uri)
+     thenret
+     else (setf (uri-hashcode uri)
+               (sxhash
+                #+allegro
+                (render-uri uri nil)
+                #-allegro
+                (string-downcase 
+                 (render-uri uri nil))))))
+
+(defvar *uris* (make-uri-space))
+
+(defun uri-space () *uris*)
+
+(defun (setf uri-space) (new-val)
+  (setq *uris* new-val))
+
+;; bootstrapping (uri= changed from function to method):
+(when (fboundp 'uri=) (fmakunbound 'uri=))
+
+(defmethod uri= ((uri1 uri) (uri2 uri))
+  (when (not (eq (uri-scheme uri1) (uri-scheme uri2)))
+    (return-from uri= nil))
+  ;; RFC2396 says: a URL with an explicit ":port", where the port is
+  ;; the default for the scheme, is the equivalent to one where the
+  ;; port is elided.  Hmmmm.  This means that this function has to be
+  ;; scheme dependent.  Grrrr.
+  (let ((default-port (case (uri-scheme uri1)
+                       (:http 80)
+                       (:https 443)
+                       (:ftp 21)
+                       (:telnet 23))))
+    (and (equalp (uri-host uri1) (uri-host uri2))
+        (eql (or (uri-port uri1) default-port)
+             (or (uri-port uri2) default-port))
+        (string= (uri-path uri1) (uri-path uri2))
+        (string= (uri-query uri1) (uri-query uri2))
+        (string= (uri-fragment uri1) (uri-fragment uri2)))))
+
+(defmethod uri= ((urn1 urn) (urn2 urn))
+  (when (not (eq (uri-scheme urn1) (uri-scheme urn2)))
+    (return-from uri= nil))
+  (and (equalp (urn-nid urn1) (urn-nid urn2))
+       (urn-nss-equal (urn-nss urn1) (urn-nss urn2))))
+
+(defun urn-nss-equal (nss1 nss2 &aux len)
+  ;; Return t iff the nss values are the same.
+  ;; %2c and %2C are equivalent.
+  (when (or (null nss1) (null nss2)
+           (not (= (setq len (length nss1))
+                   (length nss2))))
+    (return-from urn-nss-equal nil))
+  (do* ((i 0 (1+ i))
+       (state :char)
+       c1 c2)
+      ((= i len) t)
+    (setq c1 (schar nss1 i))
+    (setq c2 (schar nss2 i))
+    (ecase state
+      (:char
+       (if* (and (char= #\% c1) (char= #\% c2))
+         then (setq state :percent+1)
+       elseif (char/= c1 c2)
+         then (return nil)))
+      (:percent+1
+       (when (char-not-equal c1 c2) (return nil))
+       (setq state :percent+2))
+      (:percent+2
+       (when (char-not-equal c1 c2) (return nil))
+       (setq state :char)))))
+
+(defmethod intern-uri ((xuri uri) &optional (uri-space *uris*))
+  (let ((uri (gethash-uri xuri uri-space)))
+    (if* uri
+       thenret
+       else (puthash-uri xuri uri-space))))
+
+(defmethod intern-uri ((uri string) &optional (uri-space *uris*))
+  (intern-uri (parse-uri uri) uri-space))
+
+(defun unintern-uri (uri &optional (uri-space *uris*))
+  (if* (eq t uri)
+     then (clrhash uri-space)
+   elseif (uri-p uri)
+     then (remhash uri uri-space)
+     else (error "bad uri: ~s." uri)))
+
+(defmacro do-all-uris ((var &optional uri-space result-form)
+                      &rest forms
+                      &environment env)
+  "do-all-uris (var [[uri-space] result-form])
+                   {declaration}* {tag | statement}*
+Executes the forms once for each uri with var bound to the current uri"
+  (let ((f (gensym))
+       (g-ignore (gensym))
+       (g-uri-space (gensym))
+       (body #+allegro (third (excl::parse-body forms env))
+             #-allegro forms))
+    `(let ((,g-uri-space (or ,uri-space *uris*)))
+       (prog nil
+        (flet ((,f (,var &optional ,g-ignore)
+                 (declare (ignore-if-unused ,var ,g-ignore))
+                 (tagbody ,@body)))
+          (maphash #',f ,g-uri-space))
+        (return ,result-form)))))
+
+(defun sharp-u (stream chr arg)
+  (declare (ignore chr arg))
+  (let ((arg (read stream nil nil t)))
+    (if *read-suppress*
+       nil
+      (if* (stringp arg)
+        then (parse-uri arg)
+        else
+
+        (internal-reader-error
+         stream
+         "#u takes a string or list argument: ~s" arg)))))
+
+#+allegro
+excl::
+#+allegro
+(locally (declare (special std-lisp-readtable))
+  (let ((*readtable* std-lisp-readtable))
+    (set-dispatch-macro-character #\# #\u #'net.uri::sharp-u)))
+#-allegro
+(set-dispatch-macro-character #\# #\u #'net.uri::sharp-u)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(provide :uri)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; timings
+;; (don't run under emacs with M-x fi:common-lisp)
+
+#+ignore
+(defun time-uri-module ()
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
+  (let ((uri "http://www.franz.com/a/b;x;y;z/c/foo?bar=baz&xxx#foo")
+       (uri2 "http://www.franz.com/a/b;x;y;z/c/%2ffoo?bar=baz&xxx#foo"))
+    (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
+    (format t "~&;;; starting timing testing 1...~%")
+    (time (dotimes (i 100000) (parse-uri uri)))
+    
+    (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
+    (format t "~&;;; starting timing testing 2...~%")
+    (let ((uri (parse-uri uri)))
+      (time (dotimes (i 100000)
+             ;; forces no caching of the printed representation:
+             (setf (uri-string uri) nil)
+             (format nil "~a" uri))))
+    
+    (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
+    (format t "~&;;; starting timing testing 3...~%")
+    (time
+     (progn
+       (dotimes (i 100000) (parse-uri uri2))
+       (let ((uri (parse-uri uri)))
+        (dotimes (i 100000)
+          ;; forces no caching of the printed representation:
+          (setf (uri-string uri) nil)
+          (format nil "~a" uri)))))))
+
+;;******** reference output (ultra, modified 5.0.1):
+;;; starting timing testing 1...
+; cpu time (non-gc) 13,710 msec user, 0 msec system
+; cpu time (gc)     600 msec user, 10 msec system
+; cpu time (total)  14,310 msec user, 10 msec system
+; real time  14,465 msec
+; space allocation:
+;  1,804,261 cons cells, 7 symbols, 41,628,832 other bytes, 0 static bytes
+;;; starting timing testing 2...
+; cpu time (non-gc) 27,500 msec user, 0 msec system
+; cpu time (gc)     280 msec user, 20 msec system
+; cpu time (total)  27,780 msec user, 20 msec system
+; real time  27,897 msec
+; space allocation:
+;  1,900,463 cons cells, 0 symbols, 17,693,712 other bytes, 0 static bytes
+;;; starting timing testing 3...
+; cpu time (non-gc) 52,290 msec user, 10 msec system
+; cpu time (gc)     1,290 msec user, 30 msec system
+; cpu time (total)  53,580 msec user, 40 msec system
+; real time  54,062 msec
+; space allocation:
+;  7,800,205 cons cells, 0 symbols, 81,697,496 other bytes, 0 static bytes
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; after improving decode-escaped-encoding/encode-escaped-encoding:
+
+;;; starting timing testing 1...
+; cpu time (non-gc) 14,520 msec user, 0 msec system
+; cpu time (gc)     400 msec user, 0 msec system
+; cpu time (total)  14,920 msec user, 0 msec system
+; real time  15,082 msec
+; space allocation:
+;  1,800,270 cons cells, 0 symbols, 41,600,160 other bytes, 0 static bytes
+;;; starting timing testing 2...
+; cpu time (non-gc) 27,490 msec user, 10 msec system
+; cpu time (gc)     300 msec user, 0 msec system
+; cpu time (total)  27,790 msec user, 10 msec system
+; real time  28,025 msec
+; space allocation:
+;  1,900,436 cons cells, 0 symbols, 17,693,712 other bytes, 0 static bytes
+;;; starting timing testing 3...
+; cpu time (non-gc) 47,900 msec user, 20 msec system
+; cpu time (gc)     920 msec user, 10 msec system
+; cpu time (total)  48,820 msec user, 30 msec system
+; real time  49,188 msec
+; space allocation:
+;  3,700,215 cons cells, 0 symbols, 81,707,144 other bytes, 0 static bytes
diff --git a/tests.lisp b/tests.lisp
new file mode 100644 (file)
index 0000000..cadb9c5
--- /dev/null
@@ -0,0 +1,413 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;; copyright (c) 1999-2001 Franz Inc, Berkeley, CA  - All rights reserved.
+;; copyright (c) 2003 Kevin Rosenberg (significant fixes for using
+;; tester package)
+;;
+;; The software, data and information contained herein are proprietary
+;; to, and comprise valuable trade secrets of, Franz, Inc.  They are
+;; given in confidence by Franz, Inc. pursuant to a written license
+;; agreement, and may be stored and used only in accordance with the terms
+;; of such license.
+;;
+;; Restricted Rights Legend
+;; ------------------------
+;; Use, duplication, and disclosure of the software, data and information
+;; contained herein by any agency, department or entity of the U.S.
+;; Government are subject to restrictions of Restricted Rights for
+;; Commercial Software developed at private expense as specified in
+;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable.
+;;
+;; Original version from ACL 6.1:
+;; t-uri.cl,v 1.3.6.3.2.1 2001/08/09 17:42:43 layer
+;;
+;; $Id: tests.lisp,v 1.1 2003/07/18 20:34:23 kevin Exp $
+
+
+(defpackage #:puri-tests (:use #:puri #:cl #:util.test))
+(in-package #:puri-tests)
+
+(unintern-uri t)
+
+(defparameter *tests*
+  (let ((res '())
+       (base-uri "http://a/b/c/d;p?q"))
+
+    (dolist (x `(;; (relative-uri result base-uri compare-function)
+;;;; RFC Appendix C.1 (normal examples)
+                ("g:h" "g:h" ,base-uri)
+                ("g" "http://a/b/c/g" ,base-uri)
+                ("./g" "http://a/b/c/g" ,base-uri)
+                ("g/" "http://a/b/c/g/" ,base-uri)
+                ("/g" "http://a/g" ,base-uri) 
+                ("//g" "http://g" ,base-uri) 
+                ("?y" "http://a/b/c/?y" ,base-uri) 
+                ("g?y" "http://a/b/c/g?y" ,base-uri)
+                ("#s" "http://a/b/c/d;p?q#s" ,base-uri) 
+                ("g#s" "http://a/b/c/g#s" ,base-uri) 
+                ("g?y#s" "http://a/b/c/g?y#s" ,base-uri)
+                (";x" "http://a/b/c/;x" ,base-uri) 
+                ("g;x" "http://a/b/c/g;x" ,base-uri) 
+                ("g;x?y#s" "http://a/b/c/g;x?y#s" ,base-uri)
+                ("." "http://a/b/c/" ,base-uri) 
+                ("./" "http://a/b/c/" ,base-uri) 
+                (".." "http://a/b/" ,base-uri) 
+                ("../" "http://a/b/" ,base-uri)
+                ("../g" "http://a/b/g" ,base-uri) 
+                ("../.." "http://a/" ,base-uri) 
+                ("../../" "http://a/" ,base-uri)
+                ("../../g" "http://a/g" ,base-uri)
+;;;; RFC Appendix C.2 (abnormal examples)
+                ("" "http://a/b/c/d;p?q" ,base-uri) 
+                ("../../../g" "http://a/../g" ,base-uri)
+                ("../../../../g" "http://a/../../g" ,base-uri) 
+                ("/./g" "http://a/./g" ,base-uri) 
+                ("/../g" "http://a/../g" ,base-uri)
+                ("g." "http://a/b/c/g." ,base-uri) 
+                (".g" "http://a/b/c/.g" ,base-uri) 
+                ("g.." "http://a/b/c/g.." ,base-uri)
+                ("..g" "http://a/b/c/..g" ,base-uri) 
+                ("./../g" "http://a/b/g" ,base-uri) 
+                ("./g/." "http://a/b/c/g/" ,base-uri)
+                ("g/./h" "http://a/b/c/g/h" ,base-uri) 
+                ("g/../h" "http://a/b/c/h" ,base-uri) 
+                ("g;x=1/./y" "http://a/b/c/g;x=1/y" ,base-uri)
+                ("g;x=1/../y" "http://a/b/c/y" ,base-uri) 
+                ("g?y/./x" "http://a/b/c/g?y/./x" ,base-uri)
+                ("g?y/../x" "http://a/b/c/g?y/../x" ,base-uri) 
+                ("g#s/./x" "http://a/b/c/g#s/./x" ,base-uri)
+                ("g#s/../x" "http://a/b/c/g#s/../x" ,base-uri) 
+                ("http:g" "http:g" ,base-uri)
+
+                ("foo/bar/baz.htm#foo"
+                 "http://a/b/foo/bar/baz.htm#foo"
+                 "http://a/b/c.htm")
+                ("foo/bar/baz.htm#foo"
+                 "http://a/b/foo/bar/baz.htm#foo"
+                 "http://a/b/")
+                ("foo/bar/baz.htm#foo"
+                 "http://a/foo/bar/baz.htm#foo"
+                 "http://a/b")
+                ("foo/bar;x;y/bam.htm"
+                 "http://a/b/c/foo/bar;x;y/bam.htm"
+                 "http://a/b/c/")))
+      (push `(util.test:test (intern-uri ,(second x))
+                            (intern-uri (merge-uris (intern-uri ,(first x))
+                                                    (intern-uri ,(third x))))
+                            :test 'uri=)
+           res))
+
+;;;; intern tests
+    (dolist (x '(;; default port and specifying the default port are
+                ;; supposed to compare the same:
+                ("http://www.franz.com:80" "http://www.franz.com")
+                ("http://www.franz.com:80" "http://www.franz.com" eq)
+                ;; make sure they're `eq':
+                ("http://www.franz.com:80" "http://www.franz.com" eq)
+                ("http://www.franz.com" "http://www.franz.com" eq)
+                ("http://www.franz.com/foo" "http://www.franz.com/foo" eq)
+                ("http://www.franz.com/foo?bar"
+                 "http://www.franz.com/foo?bar" eq)
+                ("http://www.franz.com/foo?bar#baz"
+                 "http://www.franz.com/foo?bar#baz" eq)
+                ("http://WWW.FRANZ.COM" "http://www.franz.com" eq)
+                ("http://www.FRANZ.com" "http://www.franz.com" eq)
+                ("http://www.franz.com" "http://www.franz.com/" eq)
+                (;; %72 is "r", %2f is "/", %3b is ";"
+                 "http://www.franz.com/ba%72%2f%3b;x;y;z/baz/"
+                 "http://www.franz.com/bar%2f%3b;x;y;z/baz/" eq)))
+      (push `(util.test:test (intern-uri ,(second x))
+                            (intern-uri ,(first x))
+             :test ',(if (third x)
+                         (third x)
+                         'uri=))
+           res))
+
+;;;; parsing and equivalence tests
+    (push `(util.test:test
+           (parse-uri "http://foo+bar?baz=b%26lob+bof")
+           (parse-uri (parse-uri "http://foo+bar?baz=b%26lob+bof"))
+           :test 'uri=)
+         res)
+    (push '(util.test:test
+           (parse-uri "http://www.foo.com")
+           (parse-uri (parse-uri "http://www.foo.com?")) ; allow ? at end
+           :test 'uri=)
+         res)
+    (push `(util.test:test
+           "baz=b%26lob+bof"
+           (uri-query (parse-uri "http://foo+bar?baz=b%26lob+bof"))
+           :test 'string=)
+         res)
+    (push `(util.test:test
+           "baz=b%26lob+bof%3d"
+           (uri-query (parse-uri "http://foo+bar?baz=b%26lob+bof%3d"))
+           :test 'string=)
+         res)
+    (push
+     `(util.test:test (parse-uri "xxx?%41") (parse-uri "xxx?A") :test 'uri=)
+     res)
+    (push
+     `(util.test:test "A" (uri-query (parse-uri "xxx?%41")) :test 'string=)
+     res)
+
+    (push `(util.test:test-error (parse-uri " ")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "foo ")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri " foo ")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "<foo")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "foo>")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "<foo>")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "%")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "foo%xyr")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "\"foo\"")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test "%20" (format nil "~a" (parse-uri "%20"))
+                          :test 'string=)
+         res)
+    (push `(util.test:test "&" (format nil "~a" (parse-uri "%26"))
+                          :test 'string=)
+         res)
+    (push
+     `(util.test:test "foo%23bar" (format nil "~a" (parse-uri "foo%23bar"))
+                     :test 'string=)
+     res)
+    (push
+     `(util.test:test "foo%23bar#foobar"
+                     (format nil "~a" (parse-uri "foo%23bar#foobar"))
+                     :test 'string=)
+     res)
+    (push
+     `(util.test:test "foo%23bar#foobar#baz"
+                     (format nil "~a" (parse-uri "foo%23bar#foobar#baz"))
+                     :test 'string=)
+     res)
+    (push
+     `(util.test:test "foo%23bar#foobar#baz"
+                     (format nil "~a" (parse-uri "foo%23bar#foobar%23baz"))
+                     :test 'string=)
+     res)
+    (push
+     `(util.test:test "foo%23bar#foobar/baz"
+                     (format nil "~a" (parse-uri "foo%23bar#foobar%2fbaz"))
+                     :test 'string=)
+     res)
+    (push `(util.test:test-error (parse-uri "foobar??")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "foobar?foo?")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test "foobar?%3f"
+                          (format nil "~a" (parse-uri "foobar?%3f"))
+                          :test 'string=)
+         res)
+    (push `(util.test:test
+           "http://foo/bAr;3/baz?baf=3"
+           (format nil "~a" (parse-uri "http://foo/b%41r;3/baz?baf=3"))
+           :test 'string=)
+         res)
+    (push `(util.test:test
+           '(:absolute ("/bAr" "3") "baz")
+           (uri-parsed-path (parse-uri "http://foo/%2fb%41r;3/baz?baf=3"))
+           :test 'equal)
+         res)
+    (push `(util.test:test
+           "/%2fbAr;3/baz"
+           (let ((u (parse-uri "http://foo/%2fb%41r;3/baz?baf=3")))
+             (setf (uri-parsed-path u) '(:absolute ("/bAr" "3") "baz"))
+             (uri-path u))
+           :test 'string=)
+         res)
+    (push `(util.test:test
+           "http://www.verada.com:8010/kapow?name=foo%3Dbar%25"
+           (format nil "~a"
+                   (parse-uri
+                    "http://www.verada.com:8010/kapow?name=foo%3Dbar%25"))
+           :test 'string=)
+         res)
+    (push `(util.test:test
+           "ftp://parcftp.xerox.com/pub/pcl/mop/"
+           (format nil "~a"
+                   (parse-uri "ftp://parcftp.xerox.com:/pub/pcl/mop/"))
+           :test 'string=)
+         res)
+
+;;;; enough-uri tests
+    (dolist (x `(("http://www.franz.com/foo/bar/baz.htm"
+                 "http://www.franz.com/foo/bar/"
+                 "baz.htm")
+                ("http://www.franz.com/foo/bar/baz.htm"
+                 "http://www.franz.com/foo/bar"
+                 "baz.htm")
+                ("http://www.franz.com:80/foo/bar/baz.htm"
+                 "http://www.franz.com:80/foo/bar"
+                 "baz.htm")
+                ("http:/foo/bar/baz.htm" "http:/foo/bar"  "baz.htm")
+                ("http:/foo/bar/baz.htm" "http:/foo/bar/" "baz.htm")
+                ("/foo/bar/baz.htm" "/foo/bar"  "baz.htm")
+                ("/foo/bar/baz.htm" "/foo/bar/" "baz.htm")
+                ("/foo/bar/baz.htm#foo" "/foo/bar/" "baz.htm#foo")
+                ("/foo/bar/baz.htm?bar#foo" "/foo/bar/" "baz.htm?bar#foo")
+                
+                ("http://www.dnai.com/~layer/foo.htm"
+                 "http://www.known.net"
+                 "http://www.dnai.com/~layer/foo.htm")
+                ("http://www.dnai.com/~layer/foo.htm"
+                 "http://www.dnai.com:8000/~layer/"
+                 "http://www.dnai.com/~layer/foo.htm")
+                ("http://www.dnai.com:8000/~layer/foo.htm"
+                 "http://www.dnai.com/~layer/"
+                 "http://www.dnai.com:8000/~layer/foo.htm")
+                ("http://www.franz.com"
+                 "http://www.franz.com"
+                 "/")))
+      (push `(util.test:test (parse-uri ,(third x))
+                            (enough-uri (parse-uri ,(first x))
+                                        (parse-uri ,(second x)))
+                            :test 'uri=)
+           res))
+    
+;;;; urn tests, ideas of which are from rfc2141
+    (let ((urn "urn:com:foo-the-bar"))
+      (push `(util.test:test "com" (urn-nid (parse-uri ,urn))
+                            :test #'string=)
+           res)
+      (push `(util.test:test "foo-the-bar" (urn-nss (parse-uri ,urn))
+                            :test #'string=)
+           res))
+    (push `(util.test:test-error (parse-uri "urn:")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "urn:foo")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "urn:foo$")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "urn:foo_")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test-error (parse-uri "urn:foo:foo&bar")
+                                :condition-type 'parse-error)
+         res)
+    (push `(util.test:test (parse-uri "URN:foo:a123,456")
+                          (parse-uri "urn:foo:a123,456")
+                          :test #'uri=)
+         res)
+    (push `(util.test:test (parse-uri "URN:foo:a123,456")
+                          (parse-uri "urn:FOO:a123,456")
+                          :test #'uri=)
+         res)
+    (push `(util.test:test (parse-uri "urn:foo:a123,456")
+                          (parse-uri "urn:FOO:a123,456")
+                          :test #'uri=)
+         res)
+    (push `(util.test:test (parse-uri "URN:FOO:a123%2c456")
+                          (parse-uri "urn:foo:a123%2C456")
+                          :test #'uri=)
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:A123,456")
+                 (parse-uri "urn:FOO:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:A123,456")
+                 (parse-uri "urn:foo:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:A123,456")
+                 (parse-uri "URN:foo:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:a123%2C456")
+                 (parse-uri "urn:FOO:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:a123%2C456")
+                 (parse-uri "urn:foo:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "URN:FOO:a123%2c456")
+                 (parse-uri "urn:foo:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:FOO:a123%2c456")
+                 (parse-uri "urn:foo:a123,456")))
+         res)
+    (push `(util.test:test
+           nil
+           (uri= (parse-uri "urn:foo:a123%2c456")
+                 (parse-uri "urn:foo:a123,456")))
+         res)
+    
+    (push `(util.test:test t
+                          (uri= (parse-uri "foo") (parse-uri "foo#")))
+         res)
+    
+    (push
+     '(let ((net.uri::*strict-parse* nil))
+       (util.test:test-no-error
+       (net.uri:parse-uri
+        "http://foo.com/bar?a=zip|zop")))
+     res)
+    (push
+     '(util.test:test-error
+       (net.uri:parse-uri "http://foo.com/bar?a=zip|zop")
+       :condition-type 'parse-error)
+     res)
+    
+    (push
+     '(let ((net.uri::*strict-parse* nil))
+       (util.test:test-no-error
+       (net.uri:parse-uri
+        "http://arc3.msn.com/ADSAdClient31.dll?GetAd?PG=NBCSBU?SC=D2?AN=1.0586041")))
+     res)
+    (push
+     '(util.test:test-error
+       (net.uri:parse-uri
+       "http://arc3.msn.com/ADSAdClient31.dll?GetAd?PG=NBCSBU?SC=D2?AN=1.0586041")
+       :condition-type 'parse-error)
+     res)
+    
+    (push
+     '(let ((net.uri::*strict-parse* nil))
+       (util.test:test-no-error
+       (net.uri:parse-uri
+        "http://scbc.booksonline.com/cgi-bin/ndCGI.exe/Develop/pagClubHome.hrfTIOLI_onWebEvent(hrfTIOLI)?selGetClubOffer.TB_OFFER_ID_OFFER=344879%2e0&selGetClubOffer.TB_OFFER_ID_ITEM=34487%2e0&selGetClubOffer.TB_OFFER_ID_OFFER=344879%2e0&^CSpCommand.currRowNumber=5&hrfTIOLI=The+Visual+Basic+6+Programmer%27s+Toolkit&SPIDERSESSION=%3f%3f%3f%3f%3f%5f%3f%3f%3f%40%5b%3f%3f%3f%3fBOs%5cH%3f%3f%3f%3f%3f%3f%3f%3f%3fMMpXO%5f%40JG%7d%40%5c%5f%3f%3f%3fECK%5dt%3fLDT%3fTBD%3fDDTxPEToBS%40%5f%5dBDgXVoKBSDs%7cDT%3fK%3fd%3fTIb%7ceHbkeMfh%60LRpO%5cact%5eUC%7bMu%5fyWUGzLUhP%5ebpdWRka%5dFO%3f%5dBopW%3f%40HMrxbMRd%60LOpuMVga%3fv%3fTS%3fpODT%40O&%5euniqueValue=977933764843")))
+     res)
+    (push
+     '(util.test:test-error
+       (net.uri:parse-uri
+       "http://scbc.booksonline.com/cgi-bin/ndCGI.exe/Develop/pagClubHome.hrfTIOLI_onWebEvent(hrfTIOLI)?selGetClubOffer.TB_OFFER_ID_OFFER=344879%2e0&selGetClubOffer.TB_OFFER_ID_ITEM=34487%2e0&selGetClubOffer.TB_OFFER_ID_OFFER=344879%2e0&^CSpCommand.currRowNumber=5&hrfTIOLI=The+Visual+Basic+6+Programmer%27s+Toolkit&SPIDERSESSION=%3f%3f%3f%3f%3f%5f%3f%3f%3f%40%5b%3f%3f%3f%3fBOs%5cH%3f%3f%3f%3f%3f%3f%3f%3f%3fMMpXO%5f%40JG%7d%40%5c%5f%3f%3f%3fECK%5dt%3fLDT%3fTBD%3fDDTxPEToBS%40%5f%5dBDgXVoKBSDs%7cDT%3fK%3fd%3fTIb%7ceHbkeMfh%60LRpO%5cact%5eUC%7bMu%5fyWUGzLUhP%5ebpdWRka%5dFO%3f%5dBopW%3f%40HMrxbMRd%60LOpuMVga%3fv%3fTS%3fpODT%40O&%5euniqueValue=977933764843")
+       :condition-type 'parse-error)
+     res)
+    
+    `(progn ,@(nreverse res)))
+  )
+
+(eval
+ `(with-tests (:name "puri")
+   ,@*tests*))