;;;; xmlns-normalizer.lisp -- DOM 3-style namespace normalization
;;;;
;;;; This file is part of the CXML parser, released under Lisp-LGPL.
;;;; See file COPYING for details.
;;;;
;;;; Copyright (c) 2005 David Lichteblau

;;;; Hier eine Variante des reichlich furchtbaren Algorithmus zur
;;;; Namespace-Normalisierung aus DOM 3 Core.[1]
;;;;
;;;; Gebraucht wir die Sache, weil Element- und Attributknoten in DOM
;;;; zwar ein Prefix-Attribut speichern, massgeblich fuer ihren Namespace
;;;; aber nur die URI sein soll.  Und eine Anpassung der zugehoerigen
;;;; xmlns-Attribute findet bei Veraenderungen im DOM-Baum nicht statt,
;;;; bzw. wird dem Nutzer ueberlassen.
;;;;
;;;; Daher muss letztlich spaetestens beim Serialisieren eine
;;;; Namespace-Deklaration fuer die angegebene URI nachgetragen und das
;;;; Praefix ggf. umbenannt werden, damit am Ende doch etwas
;;;; Namespace-konformes heraus kommt.
;;;;
;;;; Und das nennen sie dann Namespace-Support.
;;;;
;;;; [1] http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#normalizeDocumentAlgo

(in-package :cxml)

(defclass namespace-normalizer (sax-proxy)
    ((xmlns-stack :initarg :xmlns-stack :accessor xmlns-stack)))

(defvar *xmlns-namespace* #"http://www.w3.org/2000/xmlns/")

(defun make-namespace-normalizer (chained-handler)
  "@arg[chained-handler]{A @class{SAX handler}.}
   @return{A @class{SAX handler}.}

   Return a SAX handler that performs @a[http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#normalizeDocumentAlgo]{DOM
     3-style namespace normalization} on attribute lists in
   @fun{sax:start-element} events before passing them on the next handler."
  (make-instance 'namespace-normalizer
    :xmlns-stack (list (mapcar (lambda (cons)
				 (make-xmlns-attribute (car cons) (cdr cons)))
			       *initial-namespace-bindings*))
    :chained-handler chained-handler))

(defun normalizer-find-prefix (handler prefix)
  (when (zerop (length prefix))
    (setf prefix #"xmlns"))
  (block t
    (dolist (bindings (xmlns-stack handler))
      (dolist (attribute bindings)
	(when (rod= (sax:attribute-local-name attribute) prefix)
	  (return-from t attribute))))))

(defun normalizer-find-uri (handler uri)
  (block t
    (dolist (bindings (xmlns-stack handler))
      (dolist (attribute bindings)
	(when (and (rod= (sax:attribute-value attribute) uri)
		   ;; default-namespace interessiert uns nicht
		   (not (rod= (sax:attribute-qname attribute) #"xmlns")))
	  (return-from t attribute))))))

(defun make-xmlns-attribute (prefix uri)
  (if (and (plusp (length prefix)) (not (equal prefix #"xmlns")))
      (sax:make-attribute
       :qname (concatenate 'rod #"xmlns:" prefix)
       :namespace-uri *xmlns-namespace*
       :local-name prefix
       :value uri)
      (sax:make-attribute
       :qname #"xmlns"
       :namespace-uri *xmlns-namespace*
       :local-name #"xmlns"
       :value uri)))

(defun rename-attribute (a new-prefix)
  (setf (sax:attribute-qname a)
	(concatenate 'rod new-prefix #":" (sax:attribute-local-name a))))

(defmethod sax:start-element
    ((handler namespace-normalizer) uri lname qname attrs)
  (when (null uri)
    (setf uri #""))
  (let ((normal-attrs '()))
    (push nil (xmlns-stack handler))
    (dolist (a attrs)
      (if (rod= *xmlns-namespace* (sax:attribute-namespace-uri a))
	  (push a (car (xmlns-stack handler)))
	  (push a normal-attrs)))
    (flet ((push-namespace (prefix uri)
	     (let ((new (make-xmlns-attribute prefix uri)))
	       (unless (find (sax:attribute-qname new)
			     attrs
			     :test #'rod=
			     :key #'sax:attribute-qname)
		 (push new (car (xmlns-stack handler)))
		 (push new attrs)))))
      (multiple-value-bind (prefix local-name) (split-qname qname)
	(setf lname local-name)
	(let ((binding (normalizer-find-prefix handler prefix)))
	  (cond
	    ((null binding)
	      (unless (and (null prefix) (zerop (length uri)))
		(push-namespace prefix uri)))
	    ((rod= (sax:attribute-value binding) uri))
	    ((member binding (car (xmlns-stack handler)))
	      (setf (sax:attribute-value binding) uri))
	    (t
	      (push-namespace prefix uri)))))
      (dolist (a normal-attrs)
	(let ((u (sax:attribute-namespace-uri a)))
	  (when u
	    (let* ((prefix (split-qname (sax:attribute-qname a)))
		   (prefix-binding
		    (when prefix
		      (normalizer-find-prefix handler prefix))))
	      (when (or (null prefix-binding)
			(not (rod= (sax:attribute-value prefix-binding) u)))
		(let ((uri-binding (normalizer-find-uri handler u)))
		  (cond
		    (uri-binding
		      (rename-attribute
		       a
		       (sax:attribute-local-name uri-binding)))
		    ((and prefix (null prefix-binding))
		      (push-namespace prefix u))
		    (t
		      (loop
			  for i from 1
			  for prefix = (rod (format nil "NS~D" i))
			  unless (normalizer-find-prefix handler prefix)
			  do
			    (push-namespace prefix u)
			    (rename-attribute a prefix)
			    (return))))))))))))
  (sax:start-element (proxy-chained-handler handler) uri lname qname attrs))

(defmethod sax:end-element ((handler namespace-normalizer) uri lname qname)
  (pop (xmlns-stack handler))
  (sax:end-element (proxy-chained-handler handler) (or uri #"") lname qname))
