;;;; xml-compat.lisp -- XMLS-compatible data structures
;;;;
;;;; This file is part of the CXML parser, released under Lisp-LGPL.
;;;; See file COPYING for details.
;;;;
;;;; Developed 2004 for headcraft - http://headcraft.de/
;;;; Copyright: David Lichteblau

(defpackage cxml-xmls
  (:use :cl :runes)
  (:export #:make-node #:node-name #:node-ns #:node-attrs #:node-children
           #:make-xmls-builder #:map-node #:make-xpath-navigator))

(in-package :cxml-xmls)


;;;; Knoten

(defun make-node (&key name ns attrs children)
  `(,(if ns (cons name ns) name)
       ,attrs
       ,@children))

(defun node-name (node)
  (let ((car (car node)))
    (if (consp car)
        (car car)
        car)))

(defun (setf node-name) (newval node)
  (let ((car (car node)))
    (if (consp car)
        (setf (car car) newval)
        (setf (car node) newval))))

(defun node-ns (node)
  (let ((car (car node)))
    (if (consp car)
        (cdr car)
        nil)))

(defun (setf node-ns) (newval node)
  (let ((car (car node)))
    (if (consp car)
        (setf (cdr car) newval)
        (setf (car node) (cons car newval)))
    newval))

(defun node-attrs (node)
  (cadr node))

(defun (setf node-attrs) (newval node)
  (setf (cadr node) newval))

(defun node-children (node)
  (cddr node))

(defun (setf node-children) (newval node)
  (setf (cddr node) newval))


;;;; SAX-Handler (Parser)

(defclass xmls-builder (sax:default-handler)
    ((element-stack :initform nil :accessor element-stack)
     (root :initform nil :accessor root)
     (include-default-values :initform t
                             :initarg :include-default-values
                             :accessor include-default-values)
     (include-namespace-uri :initform t
			    :initarg :include-namespace-uri
			    :accessor include-namespace-uri)))

(defun make-xmls-builder (&key (include-default-values t)
		               (include-namespace-uri t))
  "Make a XMLS style builder.  When 'include-namespace-uri is true a modified
  XMLS tree is generated that includes the element namespace URI rather than
  the qualified name prefix and also includes the namespace URI for attributes."
  (make-instance 'xmls-builder
		 :include-default-values include-default-values
		 :include-namespace-uri include-namespace-uri))

(defmethod sax:end-document ((handler xmls-builder))
  (root handler))

(defmethod sax:start-element
    ((handler xmls-builder) namespace-uri local-name qname attributes)
  (let* ((include-default-values (include-default-values handler))
	 (include-namespace-uri (include-namespace-uri handler))
	 (attributes
          (loop
              for attr in attributes
	      for attr-namespace-uri = (sax:attribute-namespace-uri attr)
	      for attr-local-name = (sax:attribute-local-name attr)
              when (and (or (sax:attribute-specified-p attr)
			    include-default-values)
			#+(or)
			(or (not include-namespace-uri)
			    (not attr-namespace-uri)
			    attr-local-name))
              collect
                (list (cond (include-namespace-uri
			     (cond (attr-namespace-uri
				    (cons attr-local-name attr-namespace-uri))
				   (t
				    (sax:attribute-qname attr))))
                            (t
                             (sax:attribute-qname attr)))
                      (sax:attribute-value attr))))
	 (namespace (when include-namespace-uri namespace-uri))
         (node (make-node :name local-name
                          :ns namespace
                          :attrs attributes))
         (parent (car (element-stack handler))))
    (if parent
        (push node (node-children parent))
        (setf (root handler) node))
    (push node (element-stack handler))))

(defmethod sax:end-element
    ((handler xmls-builder) namespace-uri local-name qname)
  (declare (ignore namespace-uri local-name qname))
  (let ((node (pop (element-stack handler))))
    (setf (node-children node) (reverse (node-children node)))))

(defmethod sax:characters ((handler xmls-builder) data)
  (let* ((parent (car (element-stack handler)))
         (prev (car (node-children parent))))
    ;; Be careful to accept both rods and strings here, so that xmls can be
    ;; used with strings even if cxml is configured to use octet string rods.
    (if (typep prev '(or rod string))
        ;; um entities herum wird SAX:CHARACTERS mehrfach aufgerufen fuer
        ;; den gleichen Textknoten.  Hier muessen wir den bestehenden Knoten
        ;; erweitern, sonst ist das Dokument nicht normalisiert.
        ;; (XXX Oder sollte man besser den Parser entsprechend aendern?)
        (setf (car (node-children parent))
              (concatenate `(vector ,(array-element-type prev))
                           prev
                           data))
        (push data (node-children parent)))))


;;;; SAX-Treiber (fuer Serialisierung)

(defun map-node
    (handler node
     &key (include-xmlns-attributes sax:*include-xmlns-attributes*)
          (include-namespace-uri t))
  (if include-namespace-uri
      (map-node/lnames (cxml:make-namespace-normalizer handler)
		       node
		       include-xmlns-attributes)
      (map-node/qnames handler node include-xmlns-attributes)))

(defun map-node/lnames (handler node include-xmlns-attributes)
  (sax:start-document handler)
  (labels ((walk (node)
	     (unless (node-ns node)
	       (error "serializing with :INCLUDE-NAMESPACE-URI, but node ~
                       was created without namespace URI"))
	     (let* ((attlist
		     (compute-attributes/lnames node include-xmlns-attributes))
		    (uri (node-ns node))
		    (lname (node-name node))
		    (qname lname)	;let the normalizer fix it
		    )
	       (sax:start-element handler uri lname qname attlist)
	       (dolist (child (node-children node))
		 (typecase child
		   (list (walk child))
		   ((or string rod)
		    (sax:characters handler (string-rod child)))))
	       (sax:end-element handler uri lname qname))))
    (walk node))
  (sax:end-document handler))

(defun map-node/qnames (handler node include-xmlns-attributes)
  (sax:start-document handler)
  (labels ((walk (node)
	     (when (node-ns node)
	       (error "serializing without :INCLUDE-NAMESPACE-URI, but node ~
                       was created with a namespace URI"))
             (let* ((attlist
		     (compute-attributes/qnames node include-xmlns-attributes))
		    (qname (string-rod (node-name node)))
                    (lname (nth-value 1 (cxml::split-qname qname))))
               (sax:start-element handler nil lname qname attlist)
               (dolist (child (node-children node))
                 (typecase child
                   (list (walk child))
                   ((or string rod)
		    (sax:characters handler (string-rod child)))))
               (sax:end-element handler nil lname qname))))
    (walk node))
  (sax:end-document handler))

(defun compute-attributes/lnames (node xmlnsp)
  (remove nil
          (mapcar (lambda (a)
                    (destructuring-bind (name value) a
		      (unless (listp name)
			(setf name (cons name nil)))
                      (destructuring-bind (lname &rest uri) name
			(cond
			  ((not (equal uri "http://www.w3.org/2000/xmlns/"))
			   (sax:make-attribute
			    ;; let the normalizer fix the qname
			    :qname (if uri
				       (string-rod (concatenate 'string
							 "dummy:"
							 lname))
				       (string-rod lname))
			    :local-name (string-rod lname)
			    :namespace-uri uri
			    :value (string-rod value)
			    :specified-p t))
			  (xmlnsp
			   (sax:make-attribute
			    :qname (string-rod
				    (if lname
					(concatenate 'string "xmlns:" lname)
					"xmlns"))
			    :local-name (string-rod lname)
			    :namespace-uri uri
			    :value (string-rod value)
			    :specified-p t))))))
                  (node-attrs node))))

(defun compute-attributes/qnames (node xmlnsp)
  (remove nil
          (mapcar (lambda (a)
                    (destructuring-bind (name value) a
		      (when (listp name)
			(error "serializing without :INCLUDE-NAMESPACE-URI, ~
                                but attribute was created with a namespace ~
                                URI"))
                      (if (or xmlnsp
			      (not (cxml::xmlns-attr-p (string-rod name))))
                          (sax:make-attribute :qname (string-rod name)
                                              :value (string-rod value)
                                              :specified-p t)
                          nil)))
                  (node-attrs node))))

;;;; XPath

(defun make-xpath-navigator ()
  (make-instance 'xpath-navigator))

(defclass xpath-navigator ()
  ((parents :initform (make-hash-table))
   (prefixes :initform (make-hash-table))
   (children :initform (make-hash-table))
   (attributes :initform (make-hash-table))
   (namespaces :initform (make-hash-table))))

(defmethod initialize-instance :after ((instance xpath-navigator) &key)
  (with-slots (prefixes) instance
    (setf (gethash "http://www.w3.org/XML/1998/namespace" prefixes)
	  "xml")))
