ddo-axes.rkt
#lang mzscheme

(require (lib "string.ss" "srfi/13"))
(require "ssax/ssax.rkt")
(require "sxml-tools.ss")
(require "xpath-context_xlink.ss")

(require (only racket filter))

;; The implementation of SXPath axes with support for distinct document order
;
; This software is in Public Domain.
; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
;
; Please send bug reports and comments to:
;   lizorkin@hotbox.ru    Dmitry Lizorkin
;
; The implementation of axes is based on the idea of context

;=========================================================================
; Miscellaneous helpers

; Returns the last member of the lst
; lst is expected to be non-empty
(define (ddo:list-last lst)
  (if (null? (cdr lst))
      (car lst)
      (ddo:list-last (cdr lst))))

; Selects all attribute and child nodes of a given 'node'
; Node is not supposed to be a context
(define (ddo:attr-child node)
  (cond
    ((or (not (pair? node))  ; Leaf node
         (null? (cdr node))  ; empty element
         (memq (car node) '(*PI* *COMMENT* *ENTITY*))   ; PI, Comment or Entity
         )  ; => no children
     '())
    ((and (pair? (cadr node))
          (eq? '@ (caadr node)))
     ; attribute node presented
     (append (cdadr node)  ; attributes
             (filter sxml:node? (cddr node))))
    (else  ; no attributes
     (filter sxml:node? (cdr node)))))

; For a given node, returns its attribute nodes and attribute value nodes in
; document order
; Node is not supposed to be a context
(define (ddo:attrs-and-values node)
  (apply append
         (map  ; attribute and its content
          (lambda (a) (cons a (cdr a)))
          (sxml:attr-list node))))

; Removes those members of the input 'nodeset' that are attributes or
; attribute values of a given 'node'. Nodeset is supposed to be in distinct
; document order. The order of attribute nodes in the 'nodeset' is supposed
; to be the same as in the original SXML document
; Works for ordinary nodes are well as for contexts
(define (ddo:discard-attributes node nodeset)
  (let loop ((attrs (ddo:attrs-and-values (sxml:context->node node)))
             (nset nodeset))
    (if (or (null? attrs) (null? nset))
        nset
        (loop (cdr attrs)
              (if (eq? (car attrs) (sxml:context->node (car nset)))
                  (cdr nset) nset)))))
  

;=========================================================================
; XPath axes for location steps not involving position-based predicates
; In this section, all axes expect the argument node-set in distinct document
; order, and return the result in distinct document order

; Ancestor axis
; In general, every two nodes have have some common ancestors (at least the
; root of the document). When we obtain ancestors of the context node, only
; those of them must be added to the result which are different from the
; ancestors of the previous node in the input node-set
(define (ddo:ancestor test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (prev-ancestors '())
                 (res '()))
        (if
         (null? src)  ; everyone processed
         (reverse res)
         (let ((curr (car src)))
           (if
            (sxml:context? curr)
            (let rpt ((curr-ancs (reverse
                                  (sxml:context->ancestors-u curr)))
                      (dupl '()))
              (cond
                ((null? curr-ancs)  ; no new ancestors
                 (loop (cdr src) prev-ancestors res))
                ((memq (car curr-ancs) prev-ancestors)
                 ; member of the ancestorial chain
                 (rpt (cdr curr-ancs)
                      (cons (car curr-ancs) dupl)))
                (else  ; the first different ancestor in a chain found
                 (let creat ((new-ancestors dupl)
                             (curr-ancs curr-ancs)
                             (res res))
                   (cond
                     ((null? curr-ancs)  ; everyone processed
                      (loop (cdr src)
                            new-ancestors
                            res))
                     ((test-pred? (car curr-ancs))
                      ; add to the result
                      (creat (cons (car curr-ancs) new-ancestors)
                             (cdr curr-ancs)
                             (cons
                              (draft:smart-make-context
                               (car curr-ancs)
                               new-ancestors
                               num-anc)
                              res)))
                     (else  ; this node doesn't satisfy the node test
                      (creat (cons (car curr-ancs) new-ancestors)
                             (cdr curr-ancs)
                             res)))))))
            ; no ancestors for this node
            (loop (cdr src) prev-ancestors res))))))))
            
; Ancestor-or-self axis
; See the comment for ddo:ancestor
(define (ddo:ancestor-or-self test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (prev-ancestors '())
                 (res '()))
        (if
         (null? src)  ; everyone processed
         (reverse res)
         (let rpt ((curr-ancs (reverse
                               (sxml:context->content (car src))))
                   (dupl '()))
           (cond
             ((null? curr-ancs)  ; no new ancestors
              (loop (cdr src) prev-ancestors res))
             ((memq (car curr-ancs) prev-ancestors)
              ; member of the ancestorial chain
              (rpt (cdr curr-ancs)
                   (cons (car curr-ancs) dupl)))
             (else  ; the first different ancestor in a chain found
              (let creat ((new-ancestors dupl)
                          (curr-ancs curr-ancs)
                          (res res))
                (cond
                  ((null? curr-ancs)  ; everyone processed
                   (loop (cdr src)
                         new-ancestors
                         res))
                  ((test-pred? (car curr-ancs))
                   ; add to the result
                   (creat (cons (car curr-ancs) new-ancestors)
                          (cdr curr-ancs)
                          (cons
                           (draft:smart-make-context
                            (car curr-ancs)
                            new-ancestors
                            num-anc)
                           res)))
                  (else  ; this node doesn't satisfy the node test
                   (creat (cons (car curr-ancs) new-ancestors)
                          (cdr curr-ancs)
                          res))))))))))))

; Attribute axis
; The alias for drart:attribute, since no reordering or duplicate elimination
; is required
(define ddo:attribute draft:attribute)

; Child axis
; If the input node is such that node of the nodes is the descendant of
; another, draft:child will produce the result in distinct document
; order
; In the general case, the implementation for child axis is more
; complicated, since it must provide the correct ordering
(define (ddo:child test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?)))
    (letrec
        ((child4this
          ; Arguments
          ;  curr-node - current node (or context) to process
          ;  src - the remaining nodes of the input node-set
          ; Returns: (values res src)
          (lambda (curr-node src)
            (let iter-attrs ((src src)
                             (res '()))
              (cond
                ((null? src)  ; the zoo is completely over
                 (values
                  (append res
                          ((draft:child test-pred? num-anc) curr-node))
                  src  ; null
                  ))
                ((memq (sxml:context->node (car src))
                       (sxml:attr-list (sxml:context->node curr-node)))
                 ; next in src is the attribute of the curr-node
                 (iter-attrs
                  (cdr src)
                  (append res ((draft:child test-pred? num-anc) (car src)))))
                (else   ; normal behaviour
                 (let ((res-ancestors
                        (sxml:context->content curr-node)))
                   (let iter-cands ((res-candidates
                                     (child (sxml:context->node curr-node)))
                                    (src src)
                                    (res res))
                     (cond
                       ((null? src)  ; this zoo is over
                        (values
                         (append
                          res
                          (draft:siblings->context-set
                           ((sxml:filter test-pred?) res-candidates)
                           (draft:list-head res-ancestors num-anc)))
                         src  ; always null
                         ))
                       ((null? res-candidates)
                        (values res src))
                       (else  ; processing the current res-candidate
                        (let rpt ((more (list (car res-candidates)))
                                  (next (sxml:context->node (car src)))
                                  (src src)
                                  (res
                                   (if
                                    (test-pred? (car res-candidates))
                                    (append
                                     res
                                     (list
                                      (draft:smart-make-context
                                       (car res-candidates)
                                       res-ancestors num-anc)))
                                    res)))
                          (cond
                            ((null? more)
                             ; no more src members
                             (iter-cands (cdr res-candidates) src res))
                            ((eq? (car more) next)
                             ; next node is a descendant-or-self candidate
                             ; or the attribute of its descendants
                             (call-with-values
                              (lambda () (child4this (car src) (cdr src)))
                              (lambda (add-res new-src)
                                (if
                                 (null? new-src)
                                 (iter-cands   ; will exit there
                                  (cdr res-candidates)
                                  new-src
                                  (append res add-res))
                                 (rpt
                                  (cdr more)  ; kids processed by recursive
                                  (sxml:context->node (car new-src))
                                  new-src
                                  (append res add-res))))))
                            (else
                             (rpt
                              (append (ddo:attr-child (car more))
                                      (cdr more))
                              next src res))))))))))))))
      (lambda (node)   ; node or nodeset
        (if
         (nodeset? node)
         (let iter ((nset node)
                    (res '()))
           (if
            (null? nset)
            res
            (call-with-values
             (lambda () (child4this (car nset) (cdr nset)))
             (lambda (add-res new-nset)
               (iter new-nset (append res add-res))))))
         ((draft:child test-pred? num-anc) node))))))
        
; Descendant axis
; We should take into account that one node in the input node set may be the
; descendant of the other node in the input node-set. Evaluation of descendant
; axis should not take the former node into account then, since its descendants
; won't add any new nodes to the result with respects to descendants of the
; latter node.
; Note that if the input node is such that node of the nodes is the descendant
; of another, draft:descendant will produce the result in distinct document
; order
(define (ddo:descendant test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (child (sxml:child sxml:node?))
         (desc (draft:descendant test-pred? num-anc)))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (next-node #f)
                 (content-to-scan '())
                 (res '()))
        (if
         (null? content-to-scan)
         (cond
           ((null? src)  ; everyone processed
            (reverse res))
           ((null? (cdr src))  ; of length 1 => never produces duplicates
            (append (reverse res)
                    (desc (car src))))
           (else
            (loop (cdr src)
                  (sxml:context->node (cadr src))
                  (let ((cntnt (sxml:context->content (car src))))
                    (map
                     (lambda (c) (cons c cntnt))
                     (child (sxml:context->node (car src)))))
                  res)))
         (let ((curr-cntnt (car content-to-scan)))
           (call-with-values
            (lambda ()
              (if
               ; next input node should be removed from consideration
               (eq? (car curr-cntnt) next-node)
               (values
                (cdr src)
                (if (null? (cdr src))  ; no next node
                    #f 
                    (sxml:context->node (cadr src))))
               (values src next-node)))
            (lambda (new-src new-next)
              (loop new-src
                    new-next
                    (append
                     (map
                      (lambda (c) (cons c curr-cntnt))
                      (child (car curr-cntnt)))
                     (cdr content-to-scan))
                    (if
                     (test-pred? (car curr-cntnt))  ; satisfies the node test
                     (cons
                      (draft:smart-make-context
                       (car curr-cntnt) (cdr curr-cntnt) num-anc)
                      res)
                     res))))))))))

; Descendant-or-self axis
; See the comment for ddo:descendant
; Note that if the input node is such that node of the nodes is the descendant
; of another, draft:descendant-or-self will produce the result in distinct
; document order
(define (ddo:descendant-or-self test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (child (sxml:child sxml:node?))
         (desc-os (draft:descendant-or-self test-pred? num-anc)))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (next-node #f)
                 (content-to-scan '())
                 (res '()))
        (if
         (null? content-to-scan)
         (cond
           ((null? src)  ; everyone processed
            (reverse res))
           ((null? (cdr src))  ; of length 1 => never produces duplicates
            (append (reverse res)
                    (desc-os (car src))))
           (else
            (loop (cdr src)
                  (sxml:context->node (cadr src))
                  (list (sxml:context->content (car src)))
                  res)))
         (let ((curr-cntnt (car content-to-scan)))
           (call-with-values
            (lambda ()
              (if
               ; next input node should be removed from consideration
               (eq? (car curr-cntnt) next-node)
               (values
                (cdr src)
                (if (null? (cdr src))  ; no next node
                    #f 
                    (sxml:context->node (cadr src))))
               (values src next-node)))
            (lambda (new-src new-next)
              (loop new-src
                    new-next
                    (append
                     (map
                      (lambda (c) (cons c curr-cntnt))
                      (child (car curr-cntnt)))
                     (cdr content-to-scan))
                    (if
                     (test-pred? (car curr-cntnt))  ; satisfies the node test
                     (cons
                      (draft:smart-make-context
                       (car curr-cntnt) (cdr curr-cntnt) num-anc)
                      res)
                     res))))))))))

; Following axis
; The implementation exploits the idea expressed in
; http://pi3.informatik.uni-mannheim.de/publications/TR-02-011.pdf,
; that is, it is sufficient to calculate following for the first_dmax
; member of the input nodeset
(define (ddo:following test-pred? . num-ancestors)
  (let ((child (sxml:child sxml:node?))
        (foll (apply draft:following (cons test-pred? num-ancestors))))
    (lambda (node)   ; node or nodeset
      (cond
        ((null? node)  ; empty nodeset - nothing to do
         '())
        ((and (pair? node) (not (symbol? (car node))))  ; non-empty nodeset
         (if
          (null? (cdr node))  ; a singleton nodeset
          (foll (car node))
          (let loop ((candidate (car node))
                     (next (sxml:context->node (cadr node)))
                     (more (cdr node))
                     (descendants (list (sxml:context->node (car node)))))
            (cond
              ((null? descendants)
               ; none of the node-set members are descendants of the candidate
               ; => apply following axis
               (foll candidate))
              ((eq? (car descendants) next)
               ; the next node is the new candidate
               (if (null? (cdr more))  ; the next node is the final candidate
                   (foll (car more))
                   (loop (car more)
                         (sxml:context->node (cadr more))
                         (cdr more)
                         (list next))))
              ((memq next (ddo:attrs-and-values (car descendants)))
               ; next node in src is an attribute node or attribute value node
               (foll (car more)))
              (else  ; proceed deeper in a tree
               (loop candidate next more
                     (append (child (car descendants)) (cdr descendants))))))))
        (else  ; a single node
         (foll node))))))

; Following-sibling axis
(define (ddo:following-sibling test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?))
        (all-following-siblings
         (lambda (node)  ; not a nodeset
           (if
            (and (sxml:context? node)                  
                 (not (null? (sxml:context->ancestors-u node))))
            (cond
              ((memq (sxml:context->node-u node)
                     (cdr  ; parent is an element => cdr gives its children
                      (car (sxml:context->ancestors-u node))))
               => (lambda (x) x))
              (else  ; no following siblings
               '()))
            '()  ; no parent => no siblings
            ))))
    (letrec
        ((reordering
          ; Arguments
          ;  res-candidates = (listof node) - candidates for the result, not
          ; yet filtered with a node test
          ;  res-ancestors - ancestors of res-candidates
          ;  src - the remaining nodes of the input node-set
          ; Returns: (values res src)
          (lambda (res-candidates res-ancestors src)
            (let loop ((res-candidates res-candidates)
                       (src src)
                       (res '())
                       (nonself? #f))
              (cond
                ((null? res-candidates)
                 (values res src))
                ((null? src)  ; this zoo is over
                 (values
                  (append
                   res
                   (draft:siblings->context-set
                    ((sxml:filter test-pred?)
                     (if nonself?
                         res-candidates
                         (cdr res-candidates)))
                    (draft:list-head res-ancestors num-anc)))
                  src  ; always null
                  ))
                ((eq? (car res-candidates) (sxml:context->node (car src)))
                 (loop res-candidates (cdr src) res nonself?))
                (else  ; processing the current res-candidate
                 (let ((res-candidate (car res-candidates)))
                   (let rpt ((more (list res-candidate))
                             (next (sxml:context->node (car src)))
                             (src src)
                             (res (if
                                   (and nonself? (test-pred? res-candidate))
                                   (append
                                    res
                                    (list
                                      (draft:smart-make-context
                                       res-candidate res-ancestors num-anc)))
                                   res)))
                     (cond
                       ((null? more)
                        ; no more src members among res-candidate descendants
                        (loop (cdr res-candidates) src res #t))
                       ((eq? (car more) next)
                        ; next node is a descendant-or-self of res-candidate
                        (call-with-values
                         (lambda ()
                           (reordering
                            (all-following-siblings (car src))
                            (sxml:context->ancestors (car src))
                            (cdr src)))
                         (lambda (add-res new-src)
                           (if
                            (null? new-src)
                            (loop (cdr res-candidates)
                                  new-src
                                  (append res add-res)
                                  #t)
                            (rpt (cdr more)  ; kids processed by recursive
                                 (sxml:context->node (car new-src))
                                 new-src
                                 (append res add-res))))))
                       ((memq next (ddo:attrs-and-values (car more)))
                        ; the next node is the attribute node or
                        ; attribute value node => it has no siblings
                        (if
                          (null? (cdr src))
                          (loop (cdr res-candidates)
                                (cdr src)  ; null
                                res
                                #t)
                          (rpt more  ; check for the other attributes
                               (sxml:context->node (car src))
                               (cdr src)
                               res)))
                       (else
                        (rpt (append (child (car more)) (cdr more))
                             next src res)))))))))))
      (lambda (node)   ; node or nodeset
        (if
         (nodeset? node)        
         (let iter ((nset node)
                    (res '()))
           (if
            (null? nset)
            res
            (call-with-values
             (lambda ()
               (reordering (all-following-siblings (car nset))
                           (sxml:context->ancestors (car nset))
                           (cdr nset)))
             (lambda (add-res new-nset)
               (iter new-nset (append res add-res))))))
         ((draft:following-sibling test-pred? num-anc) node))))))
    
; Namespace axis
; The alias for drart:namespace, since no reordering or duplicate elimination
; is required
(define ddo:namespace draft:namespace)

; Parent axis
; When locating a parent, the thing we should care about is that several nodes
; in the input node-set may have the same parent node
(define (ddo:parent test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (prev-parents '())
                 (res '()))
        (if
         (null? src)
         (reverse res)
         (let ((curr (car src)))
           (if
            (and (sxml:context? curr)
                 (not (null? (sxml:context->ancestors-u curr))))
            (let ((curr-parent (car (sxml:context->ancestors-u curr))))
              (if
               (memq curr-parent prev-parents)  ; a duplicate node              
               ; this node is already in the result
               (loop (cdr src) prev-parents res)
               (loop (cdr src)
                     (cons curr-parent prev-parents)
                     (if
                      (test-pred? curr-parent)
                      (cons
                        (draft:smart-make-context
                         curr-parent
                         (cdr (sxml:context->ancestors-u curr))
                         num-anc)
                        res)
                      res))))
            ; no parent
            (loop (cdr src) prev-parents res))))))))

; Preceding axis
; The implementation exploits the idea expressed in
; http://pi3.informatik.uni-mannheim.de/publications/TR-02-011.pdf,
; that is, it is sufficient to calculate preceding for the last_dmin member
; of the input nodeset
(define (ddo:preceding test-pred? . num-ancestors)
  (let ((prec (apply draft:preceding (cons test-pred? num-ancestors))))
    (lambda (node)   ; node or nodeset
      (cond
        ((null? node)  ; empty nodeset - nothing to do
         '())        
        ((and (pair? node) (not (symbol? (car node))))  ; non-empty nodeset
         (if
          (null? (cdr node))  ; a singleton nodeset
          (prec (car node))
          (let ((node (reverse node)))
            (let loop ((candidate (car node))
                       (next (sxml:context->node (cadr node)))
                       (more (cdr node))
                       (descendants
                        (reverse
                         (ddo:attr-child (sxml:context->node (car node))))))
              (cond
                ((null? descendants)
                 ; none of the node-set members are descendants of the candidate
                 ; => apply following axis
                 (reverse (prec candidate)))
                ((eq? (car descendants) next)
                 ; the next node is the new candidate
                 (if (null? (cdr more))  ; the next node is the final candidate
                     (reverse (prec (car more)))
                     (loop (car more)
                           (sxml:context->node (cadr more))
                           (cdr more)
                           (reverse (ddo:attr-child next)))))
                (else  ; proceed deeper in a tree
                 (loop candidate next more
                       (append (reverse (ddo:attr-child (car descendants)))
                               (cdr descendants)))))))))
         (else  ; a single node
          (reverse (prec node)))))))

; Preceding-sibling axis
(define (ddo:preceding-sibling test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?))
        (all-preceding-siblings
         ; Selects preceding siblings of the node (should be context)
         (lambda (node)  ; not a nodeset
            (if
             (and (sxml:context? node)                  
                  (not (null? (sxml:context->ancestors-u node))))
             (cond
               ((memq (sxml:context->node-u node)
                      (reverse
                       (cdr  ; parent is an element => cdr gives its children
                        (car (sxml:context->ancestors-u node)))))
                => cdr)
               (else  ; no preceding siblings
                '()))
             '()  ; no parent => no siblings
             ))))
    (letrec
        ((reordering
          ; Arguments
          ;  res-candidates = (listof node) - candidates for the result, not
          ; yet filtered with a node test
          ;  res-ancestors - ancestors of res-candidates
          ;  src - the remaining nodes of the input node-set
          ; Returns: (values res src)
          (lambda (res-candidates res-ancestors src)
            (let loop ((res-candidates res-candidates)
                       (src src)
                       (res '()))              
              (cond
                ((null? res-candidates)
                 (values res src))
                ((null? src)  ; this zoo is over
                 (values
                  (append
                   res
                   (draft:siblings->context-set
                    ((sxml:filter test-pred?) res-candidates)
                    (draft:list-head res-ancestors num-anc)))
                  src  ; always null
                  ))
                ((eq? (car res-candidates) (sxml:context->node (car src)))
                 (loop res-candidates (cdr src) res))
                (else  ; processing the current res-candidate
                 (let ((res-candidate (car res-candidates)))
                   (let rpt ((more (reverse (child res-candidate)))
                             (next (sxml:context->node (car src)))
                             (src src)
                             (res res))
                     (cond
                       ((null? more)
                        ; no more src members among res-candidate descendants
                        (loop
                         (cdr res-candidates)
                         src
                         (if (test-pred? res-candidate)
                             (append res
                                     (list
                                      (draft:smart-make-context
                                       res-candidate res-ancestors num-anc)))
                             res)))
                       ((eq? (car more) next)
                        ; next node is a descendant-or-self of res-candidate
                        (call-with-values
                         (lambda ()
                           (reordering
                            (all-preceding-siblings (car src))
                            (sxml:context->ancestors (car src))
                            (cdr src)))
                         (lambda (add-res new-src)
                           (let ((new-src
                                  (cond
                                    ((null? new-src) new-src)
                                    ((eq? res-candidate
                                          (sxml:context->node (car new-src)))
                                     (cdr new-src))
                                    (else new-src))))
                             (if
                              (null? new-src)
                              (loop (cdr res-candidates)
                                    new-src
                                    (if
                                     (test-pred? res-candidate)
                                     (append
                                      res
                                      add-res                                 
                                      (list
                                       (draft:smart-make-context
                                        res-candidate res-ancestors num-anc)))
                                     (append res add-res)))
                              (rpt (cdr more)  ; kids processed by recursive
                                   (sxml:context->node (car new-src))
                                   new-src
                                   (append res add-res)))))))
                       (else
                        (rpt (append (reverse (child (car more))) (cdr more))
                             next src res)))))))))))
      (lambda (node)   ; node or nodeset
        (if
         (nodeset? node)
         (let iter ((nset (reverse node))
                    (res '()))
           (if
            (null? nset)
            (reverse res)
            (call-with-values
             (lambda ()
               (reordering (all-preceding-siblings (car nset))
                           (sxml:context->ancestors (car nset))
                           (cdr nset)))
             (lambda (add-res new-nset)
               (iter new-nset (append res add-res))))))
         ((draft:following-sibling test-pred? num-anc) node))))))
  
; Self axis
; The alias for drart:self, since no reordering or duplicate elimination
; is required
; num-ancestors is not used here
(define ddo:self draft:self)

;-------------------------------------------------
; Particular case: all nodes in the input node-set are on the same level of
; hierarchy within a document
; In this case, some axes can be implemented more effectively

; Following axis for special case
; According to
; http://pi3.informatik.uni-mannheim.de/publications/TR-02-011.pdf,
; it is sufficient to calculate following for the first member of the input
; nodeset
(define (ddo:following-single-level test-pred? . num-ancestors)
  (let ((foll (apply draft:following
                     (cons test-pred? num-ancestors))))
    (lambda (node)   ; node or nodeset
      (cond
        ((null? node)  ; empty nodeset - nothing to do
         '())
        ((and (pair? node) (not (symbol? (car node))))  ; non-empty nodeset
         (foll (car node)))
        (else  ; a single node
         (foll node))))))

; Following-sibling axis for the special case
; We need only to care of duplicates removal, and ordering would be
; achieved automatically
(define (ddo:following-sibling-single-level test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))    
                 (res '()))
        (if
         (null? src)  ; everyone processed
         (reverse res)
         (let ((curr (car src)))           
           (if
            (and (sxml:context? curr)
                 (not (null? (sxml:context->ancestors-u curr))))
            (cond
              ((memq (sxml:context->node-u curr)
                     (cdr  ; parent is an element => cdr gives its children
                      (car (sxml:context->ancestors-u curr))))
               => (lambda (foll-siblings)
                    (let rpt ((foll-siblings (cdr foll-siblings))
                              (src (cdr src))
                              (res res))
                      (cond
                        ((null? foll-siblings)
                         (loop src res))
                        ((null? src)  ; no more source nodes in document order
                         (append
                          (reverse res)
                          (draft:siblings->context-set
                           ((sxml:filter test-pred?) foll-siblings)
                           (draft:list-head
                            (sxml:context->ancestors-u curr) num-anc))))
                        (else                       
                         (rpt
                          (cdr foll-siblings)
                          (if (eq? (car foll-siblings)
                                   (sxml:context->node (car src)))
                              (cdr src)  ; remove the first source node
                              src)
                          (if (test-pred? (car foll-siblings))
                              (cons
                               (draft:smart-make-context
                                (car foll-siblings)
                                (sxml:context->ancestors-u curr)
                                num-anc)
                               res)
                              res)))))))
              (else  ; no following siblings
               (loop (cdr src) res)))
            (loop (cdr src) res)  ; no parent => no siblings
            )))))))

; Parent axis for the case when all nodes in the input node-set are located
; on the same level of hierarchy within a document
; In this case the parent axis can be computed with the O(n) complexity, n
; is the number of nodes in the document, compared to O(n^2) complexity for
; ddo:parent
(define (ddo:parent-single-level test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (as-nodeset node))
                 (prev-parent #f)
                 (res '()))
        (if
         (null? src)
         (reverse res)
         (let ((curr (car src)))
           (if
            (and (sxml:context? curr)
                 (not (null? (sxml:context->ancestors-u curr))))
            (let ((curr-parent (car (sxml:context->ancestors-u curr))))
              (if
               ; this condition would never evaluate to #t when prev-parent=#f
               (eq? curr-parent prev-parent)  ; a duplicate node
               ; this node is already in the result
               (loop (cdr src) prev-parent res)
               (loop (cdr src) curr-parent
                     (if
                      (test-pred? curr-parent)
                      (cons
                       (draft:smart-make-context
                        curr-parent
                        (cdr (sxml:context->ancestors-u curr))
                        num-anc)
                       res)
                      res))))
            ; no parent
            (loop (cdr src) prev-parent res))))))))

; Preceding axis for the special case
; The implementation exploits the idea expressed in
; http://pi3.informatik.uni-mannheim.de/publications/TR-02-011.pdf,
; that is, it is sufficient to calculate preceding for the last member of the
; input nodeset
(define (ddo:preceding-single-level test-pred? . num-ancestors)
  (let ((prec (apply draft:preceding
                     (cons test-pred? num-ancestors))))
    (lambda (node)   ; node or nodeset
      (cond
        ((null? node)  ; empty nodeset - nothing to do
         '())
        ((and (pair? node) (not (symbol? (car node))))  ; non-empty nodeset
         (reverse  ; restore document order
          (prec (ddo:list-last node))))
        (else  ; a single node
         (reverse (prec node)))))))

; Preceding-sibling axis for the special case
(define (ddo:preceding-sibling-single-level test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (lambda (node)   ; node or nodeset
      (let loop ((src (reverse (as-nodeset node)))
                 (res '()))
        (if
         (null? src)  ; everyone processed
         res
         (let ((curr (car src)))           
           (if
            (and (sxml:context? curr)
                 (not (null? (sxml:context->ancestors-u curr))))
            (cond
              ((memq (sxml:context->node-u curr)
                     (reverse
                      (cdr  ; parent is an element => cdr gives its children
                       (car (sxml:context->ancestors-u curr)))))
               => (lambda (prec-siblings)
                    (let rpt ((prec-siblings (cdr prec-siblings))
                              (src (cdr src))
                              (res res))
                      (cond
                        ((null? prec-siblings)
                         (loop src res))
                        ((null? src)  ; no more source nodes
                         (append
                          (reverse
                           (draft:siblings->context-set
                            ((sxml:filter test-pred?) prec-siblings)
                            (draft:list-head
                             (sxml:context->ancestors-u curr) num-anc)))
                          res))
                        (else                       
                         (rpt
                          (cdr prec-siblings)
                          (if (eq? (car prec-siblings)
                                   (sxml:context->node (car src)))
                              (cdr src)  ; remove the first source node
                              src)
                          (if (test-pred? (car prec-siblings))
                              (cons
                               (draft:smart-make-context
                                (car prec-siblings)
                                (sxml:context->ancestors-u curr)
                                num-anc)
                               res)
                              res)))))))
              (else  ; no preceding siblings
               (loop (cdr src) res)))
            (loop (cdr src) res)  ; no parent => no siblings
            )))))))


;=========================================================================
; XPath axes for location steps probably involving position-based predicates
; Result is represented in the form of
;  pos-result ::= (listof pos-nodeset)
;  pos-nodeset ::= (listof (cons node order-num))
; Each pos-nodeset is a result of applying the axis to a single node in the
; input nodeset. Pos-result can be informally considered as
;  (map axis-pos input-nodeset)
; Each node in the pos-nodeset comes with its order number. An order-num is
; an integer, possibly a negative one. A node precedes another node in
; document order if the order-num of the former node is less than the order-num
; of the latter node. Equal order-nums (in different pos-nodesets) correspond
; to equal nodes.
; Each pos-nodeset is sorted in accordance with the position() of each of its
; members. Consequently, order-nums increase within pos-nodeset for forward
; XPath axes and decrease for reverse XPath axes.

; Ancestor axis, for position-based filtering
(define (ddo:ancestor-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (letrec
        (; A hybrid of assq and memq
         (assmemq
          (lambda (key lst)
            (cond
              ((null? lst) #f)
              ((eq? key (caar lst)) lst)
              (else (assmemq key (cdr lst)))))))
      (lambda (node)   ; node or nodeset
        (let loop ((src (as-nodeset node))
                   (prev-ancestors '())
                   (ancs-alist '())
                   (pos-res '())
                   (vacant-num 1))
          ; ancs-alist ::= (listof (cons node pos-nodeset))               
          (if
           (null? src)  ; everyone processed
           pos-res
           (let ((curr (car src)))
             (cond
               ((or (not (sxml:context? curr))
                    (null? (sxml:context->ancestors-u curr)))
                ; no ancestors for this node
                (loop (cdr src) prev-ancestors ancs-alist pos-res vacant-num))
               ((and (not (null? prev-ancestors))
                     (eq? (car (sxml:context->ancestors-u curr))
                          (car prev-ancestors)))
                ; The common case of removing (some) duplicate result node-sets
                ; from consideration.
                (loop (cdr src) prev-ancestors ancs-alist pos-res vacant-num))
               (else
                (let rpt ((curr-ancs (sxml:context->ancestors-u curr))
                          (new-content '()))
                  ; new content - that didn't repeat with the previous
                  ; ancestors
                  (cond
                    ((or (null? curr-ancs)  ; all ancestors are new
                         ; the first repeated found
                         (memq (car curr-ancs) prev-ancestors))
                     => (lambda (prev-tail)
                          (call-with-values
                           (lambda()
                             (if
                              (pair? prev-tail)
                              (let ((t
                                     (assmemq (car prev-tail) ancs-alist)))
                                (values prev-tail t (cdar t)))
                              (values '() '() '())))
                           (lambda (prev-ancestors ancs-alist this-nset)
                             (let creat ((prev-ancestors prev-ancestors)
                                       (ancs-alist ancs-alist)
                                       (vacant-num vacant-num)
                                       (this-nset this-nset)
                                       (new-content new-content))
                             (if
                              (null? new-content)  ; everyone processed
                              (loop (cdr src)
                                    prev-ancestors
                                    ancs-alist
                                    (cons this-nset pos-res)
                                    vacant-num)
                              (let ((new-this-nset
                                     (if
                                      (test-pred? (caar new-content))
                                      ; add to the result
                                      (cons
                                       (cons
                                        (draft:smart-make-context
                                         (caar new-content)
                                         (cdar new-content)
                                         num-anc)
                                        vacant-num)
                                       this-nset)
                                      this-nset)))
                                (creat (car new-content)
                                       (cons
                                        (cons
                                         (caar new-content)
                                         new-this-nset)
                                        ancs-alist)
                                       (+ vacant-num 1)
                                       new-this-nset
                                       (cdr new-content)))))))))
                    (else
                     (rpt (cdr curr-ancs)
                          (cons curr-ancs new-content))))))))))))))

; Ancestor-or-self axis, for position-based filtering
(define (ddo:ancestor-or-self-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (letrec
        (; A hybrid of assq and memq
         (assmemq
          (lambda (key lst)
            (cond
              ((null? lst) #f)
              ((eq? key (caar lst)) lst)
              (else (assmemq key (cdr lst)))))))
      (lambda (node)   ; node or nodeset
        (let loop ((src (as-nodeset node))
                   (prev-ancestors '())
                   (ancs-alist '())
                   (pos-res '())
                   (vacant-num 1))
          ; ancs-alist ::= (listof (cons node pos-nodeset))               
          (if
           (null? src)  ; everyone processed
           pos-res
           (let rpt ((curr-ancs (sxml:context->content (car src)))
                     (new-content '()))
             ; new content - that didn't repeat with the previous
             ; ancestors
             (cond
               ((or (null? curr-ancs)  ; all ancestors are new
                    ; or the first repeated found
                    (memq (car curr-ancs) prev-ancestors))
                => (lambda (prev-tail)                     
                     (call-with-values
                      (lambda ()
                        (if
                         (pair? prev-tail)
                         (let ((t (assmemq (car prev-tail) ancs-alist)))
                           (values prev-tail t (cdar t)))
                         (values '() '() '())))
                      (lambda (prev-ancestors ancs-alist this-nset)
                        (let creat ((prev-ancestors prev-ancestors)
                                    (ancs-alist ancs-alist)
                                    (vacant-num vacant-num)
                                    (this-nset this-nset)
                                    (new-content new-content))
                          (if
                           (null? new-content)  ; everyone processed
                           (loop (cdr src)
                                 prev-ancestors
                                 ancs-alist
                                 (cons this-nset pos-res)
                                 vacant-num)
                           (let ((new-this-nset
                                  (if
                                   (test-pred? (caar new-content))
                                   ; add to the result
                                   (cons
                                    (cons
                                     (draft:smart-make-context
                                      (caar new-content)
                                      (cdar new-content)
                                      num-anc)
                                     vacant-num)
                                    this-nset)
                                   this-nset)))
                             (creat (car new-content)
                                    (cons
                                     (cons
                                      (caar new-content)
                                      new-this-nset)
                                     ancs-alist)
                                    (+ vacant-num 1)
                                    new-this-nset
                                    (cdr new-content)))))))))
               (else
                (rpt (cdr curr-ancs)
                     (cons curr-ancs new-content)))))))))))

; Child axis, for position-based filtering
(define (ddo:child-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?)))
    (letrec
        (; Creates a pos-nodeset
         (create-pos-nset
          (lambda (nset ancestors vacant-num)
            (if (null? nset)
                '()
                (cons
                 (cons (if (null? ancestors)
                           (car nset)
                           (draft:make-context (car nset) ancestors))
                       vacant-num)
                 (create-pos-nset (cdr nset) ancestors (+ vacant-num 1))))))
         (src-walk
          ; curr-node - current input node (probably context)
          ; src - the remaining nodes in the input nodeset
          ; order-num - the order number of the current result node
          ; Returns: (values pos-result new-src new-order-num)
          (lambda (curr-node src order-num)
            (let ((curr-children
                   (child (sxml:context->node curr-node))))
              (if
               (null? curr-children)  ; nothing to do for this curr-node
               (values '() src order-num)
               (let ((curr-ancestors (draft:list-head
                                      (sxml:context->content curr-node)
                                      num-anc)))
                 (if
                  (null? src)  ; no searching for descendants required
                  (values (list (create-pos-nset
                                 ((sxml:filter test-pred?) curr-children)
                                 curr-ancestors order-num))
                          src  ; always null
                          #f  ; nobody cares of order-num anymore
                          )
                  (let loop ((src src)
                             (next-node (sxml:context->node (car src)))
                             (curr-children (cdr curr-children))
                             (desc-to-scan (list (car curr-children)))
                             (this-res
                              (if
                               (test-pred? (car curr-children))
                               (list
                                (cons
                                 (if (null? curr-ancestors)
                                     (car curr-children)
                                     (draft:make-context
                                      (car curr-children) curr-ancestors))
                                 order-num))
                               '()))
                             (pos-result '())
                             (order-num (+ order-num 1)))
                    (cond
                      ((null? desc-to-scan)
                       ; we can proceed to next curr-children
                       (if
                        (null? curr-children)
                        (values (cons (reverse this-res) pos-result)
                                src
                                order-num)
                        (loop src next-node
                              (cdr curr-children)
                              (list (car curr-children))
                              (if
                               (test-pred? (car curr-children))
                               (cons
                                (cons
                                 (if (null? curr-ancestors)
                                     (car curr-children)
                                     (draft:make-context
                                      (car curr-children) curr-ancestors))
                                 order-num)
                                this-res)
                               this-res)
                              pos-result
                              (+ order-num 1))))
                      ; There are descendants to be scanned
                      ((eq? (car desc-to-scan) next-node)
                       (call-with-values
                        (lambda ()
                          (src-walk (car src)
                                    (cdr src)
                                    order-num))
                        (lambda (new-pos-res new-src new-order-num)
                          (if
                           (null? new-src)  ; no more nodes in src nodeset
                           (values
                            (cons
                             (append
                              (reverse this-res)
                              (create-pos-nset
                               ((sxml:filter test-pred?) curr-children)
                               curr-ancestors order-num))
                             (append pos-result new-pos-res))
                            new-src  ; always null
                            #f  ; nobody cares of this number anymore
                            )
                           (loop new-src
                                 (sxml:context->node (car new-src))
                                 curr-children
                                 (cdr desc-to-scan)  ; descendants processed
                                 this-res
                                 (append pos-result new-pos-res)
                                 new-order-num)))))
                      (else  ; proceed to the next descendant
                       (loop src next-node curr-children
                             (append  ; content-to-scan
                              (ddo:attr-child (car desc-to-scan))
                              (cdr desc-to-scan))
                             this-res
                             pos-result
                             order-num)))))))))))
      (lambda (node)   ; node or nodeset
        (let rpt ((src (as-nodeset node))
                  (pos-result '())
                  (order-num 1))
          (if
           (null? src)  ; all processed
           (filter  ; removing empty result nodesets
            (lambda (x) (not (null? x)))
            pos-result)
           (call-with-values
            (lambda () (src-walk (car src) (cdr src) order-num))
            (lambda (new-pos-res new-src new-order-num)
              (rpt new-src
                   (append pos-result new-pos-res)
                   new-order-num)))))))))

; Descendant axis, for position-based filtering
; When no node in the input node-set is a descendant of another node in the
; input node-set, the ordinary draft:descendant function can be used
(define (ddo:descendant-pos test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (child (sxml:child sxml:node?))
         (desc (draft:descendant test-pred? num-anc)))
    (letrec                 
        ((src-walk
          ; curr-node - current input node (probably context)
          ; src - the remaining nodes in the input nodeset
          ; order-num - the order number of the current result node
          ; Returns: (values pos-result new-src new-order-num)
          (lambda (curr-node src order-num)
            (let loop ((src src)
                       (next-node (if (null? src)
                                      #f
                                      (sxml:context->node (car src))))
                       (content-to-scan
                        (let ((cntnt (sxml:context->content curr-node)))
                          (map
                           (lambda (c) (cons c cntnt))
                           (child (sxml:context->node curr-node)))))                      
                       (this-res '())
                       (pos-result '())
                       (order-num order-num))
              (if
               (null? content-to-scan)
               (values (cons (reverse this-res) pos-result)
                       src
                       (+ order-num 1))
               (let ((curr-cntnt (car content-to-scan)))
                 (if
                  (eq? (car curr-cntnt) next-node)
                  (call-with-values
                   (lambda () (src-walk (car src)
                                        (cdr src)
                                        (+ order-num 1)))
                   (lambda (new-pos-res new-src new-order-num)
                     (loop new-src
                           (if (null? new-src)
                               #f
                               (sxml:context->node (car new-src)))
                           (cdr content-to-scan)  ; descendants processed
                           (append
                            (reverse (car new-pos-res))
                            (if  ; this res
                             (test-pred? (car curr-cntnt))
                             (cons
                              (cons
                               (draft:smart-make-context
                                (car curr-cntnt) (cdr curr-cntnt) num-anc)
                               order-num)
                              this-res)
                             this-res))
                           (append pos-result new-pos-res)
                           new-order-num)))
                  (loop src
                        next-node
                        (append  ; content-to-scan
                         (map
                          (lambda (c) (cons c curr-cntnt))
                          (child (car curr-cntnt)))
                         (cdr content-to-scan))
                        (if  ; this res
                         (test-pred? (car curr-cntnt))  ; satisfies the node test
                         (cons
                          (cons
                           (draft:smart-make-context
                            (car curr-cntnt) (cdr curr-cntnt) num-anc)
                           order-num)
                          this-res)
                         this-res)
                        pos-result
                        (+ order-num 1)))))))))
      (lambda (node)   ; node or nodeset
        (let rpt ((src (as-nodeset node))
                  (pos-result '())
                  (order-num 1))
          (if
           (null? src)  ; all processed
           (filter  ; removing empty result nodesets
            (lambda (x) (not (null? x)))
            pos-result)
           (call-with-values
            (lambda () (src-walk (car src) (cdr src) order-num))
            (lambda (new-pos-res new-src new-order-num)
              (rpt new-src
                   (append pos-result new-pos-res)
                   new-order-num)))))))))

; Descendant-or-selt axis, for position-based filtering
; When no node in the input node-set is a descendant of another node in the
; input node-set, the ordinary draft:descendant function can be used
(define (ddo:descendant-or-self-pos test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (child (sxml:child sxml:node?)))
    (letrec                 
        ((src-walk
          ; curr-node - current input node (probably context)
          ; src - the remaining nodes in the input nodeset
          ; order-num - the order number of the current result node
          ; Returns: (values pos-result new-src new-order-num)
          (lambda (curr-node src order-num)
            (let loop ((src src)
                       (next-node (if (null? src)
                                      #f
                                      (sxml:context->node (car src))))
                       (content-to-scan
                        (list (sxml:context->content curr-node)))
                       (this-res '())
                       (pos-result '())
                       (order-num order-num))
              (if
               (null? content-to-scan)
               (values (cons (reverse this-res) pos-result)
                       src
                       (+ order-num 1))
               (let ((curr-cntnt (car content-to-scan)))
                 (if
                  (eq? (car curr-cntnt) next-node)
                  (call-with-values
                   (lambda () (src-walk (car src) (cdr src) order-num))
                   (lambda (new-pos-res new-src new-order-num)
                     (loop new-src
                           (if (null? new-src)
                               #f
                               (sxml:context->node (car new-src)))
                           (cdr content-to-scan)  ; descendants processed
                           (append
                            (reverse (car new-pos-res))
                            this-res)
                           (append pos-result new-pos-res)
                           new-order-num)))
                  (loop src
                        next-node
                        (append  ; content-to-scan
                         (map
                          (lambda (c) (cons c curr-cntnt))
                          (child (car curr-cntnt)))
                         (cdr content-to-scan))
                        (if  ; this res
                         (test-pred? (car curr-cntnt))  ; satisfies the node test
                         (cons
                          (cons
                           (draft:smart-make-context
                            (car curr-cntnt) (cdr curr-cntnt) num-anc)
                           order-num)
                          this-res)
                         this-res)
                        pos-result
                        (+ order-num 1)))))))))
      (lambda (node)   ; node or nodeset
        (let rpt ((src (as-nodeset node))
                  (pos-result '())
                  (order-num 1))
          (if
           (null? src)  ; all processed
           (filter  ; removing empty result nodesets
            (lambda (x) (not (null? x)))
            pos-result)
           (call-with-values
            (lambda () (src-walk (car src) (cdr src) order-num))
            (lambda (new-pos-res new-src new-order-num)
              (rpt new-src
                   (append pos-result new-pos-res)
                   new-order-num)))))))))

; Following-sibling axis, for position-based filtering
(define (ddo:following-sibling-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?)))
    (letrec
        ((associate-num
          (lambda (nset ancestors vacant-num)
            (if (null? nset)
                nset
                (cons
                 (cons
                  (if (null? ancestors)
                      (car nset)
                      (draft:make-context (car nset) ancestors))
                  vacant-num)
                 (associate-num (cdr nset) ancestors (+ vacant-num 1))))))         
         ; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  (values pos-result new-src new-vacant-num)
         (process-single
          (lambda (curr src vacant-num)
            (if
             (or (not (sxml:context? curr))
                 (null? (sxml:context->ancestors-u curr)))
             ; Siblings cannot be identified
             (values '() src vacant-num)
             (cond
               ((memq (sxml:context->node-u curr)
                      (cdr  ; parent is an element => cdr gives its children
                       (car (sxml:context->ancestors-u curr))))
                =>
                (lambda (foll-siblings)
                  (let ((ancestors
                         (draft:list-head
                          (sxml:context->ancestors-u curr) num-anc)))
                    ; Scanning descendants of the context node
                    (let loop ((foll-siblings (cdr foll-siblings))
                               (descs (child (car foll-siblings)))
                               (src (ddo:discard-attributes
                                     (car foll-siblings) src))
                               (vacant-num vacant-num)
                               (res '())
                               (pos-res '()))
                      (cond
                        ((null? src)
                         (values
                          (cons
                           (append 
                            (reverse res)
                            (associate-num
                             foll-siblings ancestors vacant-num))
                           pos-res)
                          src  ; always null
                          #f  ; nobody cares of this number anymore
                          ))                        
                        ((null? descs)  ; descendants of current foll-sibling
                         (if
                          (null? foll-siblings)  ; that stuff is over
                          (values (cons (reverse res) pos-res)
                                  src
                                  vacant-num)                           
                          (let ((new-res
                                 (if (test-pred? (car foll-siblings))
                                     (cons
                                      (cons
                                       (if (null? ancestors)
                                           (car foll-siblings)
                                           (draft:make-context
                                            (car foll-siblings) ancestors))
                                       vacant-num)
                                      res)
                                     res)))
                            (if                               
                             (eq? (car foll-siblings)
                                  (sxml:context->node (car src)))
                             (call-with-values
                              (lambda ()
                                (process-single
                                 (car src) (cdr src) (+ vacant-num 1)))
                              (lambda (new-pos-res new-src new-vacant)
                                (values (cons
                                         (append
                                          (reverse new-res)
                                          (if (null? new-pos-res)
                                              '() (car new-pos-res)))
                                         (append pos-res new-pos-res))
                                        new-src
                                        new-vacant)))
                             (loop (cdr foll-siblings)
                                   (ddo:attr-child (car foll-siblings))
                                   (ddo:discard-attributes
                                    (car foll-siblings) src)
                                   (+ vacant-num 1)
                                   new-res
                                   pos-res)))))
                        ((eq? (car descs) (sxml:context->node (car src)))
                         ; His siblings are on the way
                         (call-with-values
                          (lambda ()
                            (process-single
                             (car src) (cdr src) vacant-num))
                          (lambda (new-pos-res new-src new-vacant)
                            (loop foll-siblings
                                  (cdr descs)  ; descendants processed
                                  new-src
                                  new-vacant
                                  res
                                  (cons pos-res new-pos-res)))))
                        (else
                         (loop foll-siblings
                               (append (child (car descs)) (cdr descs))
                               (ddo:discard-attributes (car descs) src)
                               vacant-num
                               res
                               pos-res)))))))
               (else
                (values '() src vacant-num)))))))
      (lambda (node)   ; node or nodeset
        (if
         (nodeset? node)
         (let iter ((src node)
                    (pos-res '())
                    (vacant-num 1))
           (if
            (null? src)
            (filter  ; removing empty result nodesets
             (lambda (x) (not (null? x)))
             pos-res)
            (call-with-values
             (lambda () (process-single (car src) (cdr src) vacant-num))
             (lambda (new-pos-res new-src new-vacant)
               (iter new-src
                     (append pos-res new-pos-res)
                     new-vacant))))))))))

; Parent axis, for position-based filtering
; We won't reinvent the wheel here. We'll use ddo:parent and apply the fact
; that for every single input node the parent axis produces no more than a
; single result node
(define (ddo:parent-pos test-pred? . num-ancestors)
  (let ((parent (apply ddo:parent (cons test-pred? num-ancestors))))
    (letrec
        ((add-order-num
          (lambda (num nset)
            (if (null? nset)
                nset
                (cons (list (cons (car nset) num))
                      (add-order-num (+ num 1) (cdr nset)))))))
      (lambda (node)   ; node or nodeset
        (add-order-num 1 (parent node))))))

; Preceding-sibling axis, for position-based filtering
(define (ddo:preceding-sibling-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
        (child (sxml:child sxml:node?))
        (reverse-desc  ; reverse descendants of node
         (lambda (node)
           (let scan ((more (ddo:attr-child node))
                      (res '()))
             (if (null? more)  ; result formed
                 res
                 (scan (append (ddo:attr-child (car more))
                               (cdr more))
                       (cons (car more) res)))))))
    (letrec
        ((associate-num
          (lambda (nset ancestors vacant-num)
            (if (null? nset)
                nset
                (cons
                 (cons
                  (if (null? ancestors)
                      (car nset)
                      (draft:make-context (car nset) ancestors))
                  vacant-num)
                 (associate-num (cdr nset) ancestors (- vacant-num 1))))))         
         ; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  (values pos-result new-src new-vacant-num)
         (process-single
          (lambda (curr src vacant-num)
            (if
             (or (not (sxml:context? curr))
                 (null? (sxml:context->ancestors-u curr)))
             ; Siblings cannot be identified
             (values '() src vacant-num)
             (cond               
               ((memq (sxml:context->node-u curr)
                      (reverse
                       (cdr  ; parent is an element => cdr gives its children
                        (car (sxml:context->ancestors-u curr)))))
                =>
                (lambda (prec-siblings)  ; prec-siblings + self
                  (if
                   (null? (cdr prec-siblings))  ; no preceding siblings
                   (values '() src vacant-num)
                   (let ((ancestors
                         (draft:list-head
                          (sxml:context->ancestors-u curr) num-anc)))
                    ; Scanning descendants of the context node
                    (let loop ((prec-siblings (cdr prec-siblings))
                               (descs (reverse-desc (cadr prec-siblings)))
                               (src src)
                               (vacant-num vacant-num)
                               (res '())
                               (pos-res '()))
                      (cond
                        ((null? src)
                         (values
                          (cons
                           (append
                            (reverse res)
                            (associate-num
                             ; DL: was: (if nonself? prec-siblings (cdr prec-siblings))
                             prec-siblings
                             ancestors vacant-num))
                           pos-res)
                          src  ; always null
                          #f  ; nobody cares of this number anymore
                          ))
                        ((null? descs)  ; descendants of current prec-sibling
                         (let ((new-res
                                 (if (test-pred? (car prec-siblings))
                                     (cons
                                      (cons
                                       (if (null? ancestors)
                                           (car prec-siblings)
                                           (draft:make-context
                                            (car prec-siblings) ancestors))
                                       vacant-num)
                                      res)
                                     res)))
                           (cond                             
                             ((eq? (car prec-siblings)  ; to be now added
                                   (sxml:context->node (car src)))
                              (call-with-values
                               (lambda ()
                                 (process-single
                                  (car src) (cdr src) (- vacant-num 1)))
                               (lambda (new-pos-res new-src new-vacant)
                                 (values (cons
                                          (append
                                           (reverse new-res)
                                           (if (null? new-pos-res)
                                               '() (car new-pos-res)))
                                          (append pos-res new-pos-res))
                                         new-src
                                         new-vacant))))
                             ((null? (cdr prec-siblings))  ; that stuff is over
                              (values (cons (reverse new-res) pos-res)
                                      src
                                      vacant-num))
                             (else
                              (loop (cdr prec-siblings)
                                    (reverse-desc (cadr prec-siblings))
                                    src
                                    (- vacant-num 1)
                                    new-res
                                    pos-res)))))
                        ((eq? (car descs) (sxml:context->node (car src)))
                         ; His siblings are on the way
                         (call-with-values
                          (lambda ()
                            (process-single
                             (car src) (cdr src) vacant-num))
                          (lambda (new-pos-res new-src new-vacant)
                            (loop prec-siblings
                                (cdr descs)  ; descendants processed
                                new-src
                                new-vacant
                                res
                                (append pos-res new-pos-res)))))
                        (else
                         (loop prec-siblings                               
                               (cdr descs)
                               src
                               vacant-num
                               res
                               pos-res))))))))
               (else
                (values '() src vacant-num)))))))
      (lambda (node)   ; node or nodeset
        (if
         (nodeset? node)
         (let iter ((src (reverse node))
                    (pos-res '())
                    (vacant-num -1))
           (if
            (null? src)
            (filter  ; removing empty result nodesets
             (lambda (x) (not (null? x)))
             pos-res)
            (call-with-values
             (lambda () (process-single (car src) (cdr src) vacant-num))
             (lambda (new-pos-res new-src new-vacant)
               (iter new-src
                     (append new-pos-res pos-res)
                     new-vacant))))))))))

;-------------------------------------------------
; Particular case: all nodes in the input node-set are on the same level of
; hierarchy within a document
; In this case, some axes can be implemented more effectively

; Following axis for the special case
(define (ddo:following-single-level-pos test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (descend (draft:descendant-or-self test-pred? num-anc))
         (follow (draft:following test-pred? num-anc)))
    (letrec
        (; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  pos-result
         (process-single
          (lambda (curr src vacant-num)
            (cond
              ((null? src)  ; no more nodes
               (let add-labels ((to-scan (follow curr))
                                (res '())
                                (vacant-num vacant-num))
                 (if (null? to-scan)
                     (list (reverse res))
                     (add-labels (cdr to-scan)
                                 (cons (cons (car to-scan) vacant-num) res)
                                 (+ vacant-num 1)))))
              ((not (sxml:context? curr))  ; can't find following nodes
               (cons '() (process-single (car src) (cdr src) vacant-num)))
              (else
               (let ((next (sxml:context->node (car src))))
                 (let loop ((this-level (sxml:context->node-u curr))
                            (ancs-to-view (sxml:context->ancestors-u curr))
                            (content-set '())
                            (pos-nset '())
                            (vacant-num vacant-num))
                   (cond
                     ((null? content-set)  ; no one to scan at this level
                      (cond
                        ((null? ancs-to-view)
                         (cons
                          (reverse pos-nset)
                          (process-single (car src) (cdr src) vacant-num)))
                        ((memq next (sxml:attr-list (car ancs-to-view)))
                         ; next is an attribute
                         (let ((pos-result
                                (process-single (car src) (cdr src) vacant-num)))
                           (cons
                            (append (reverse pos-nset) (car pos-result))
                            pos-result)))
                        (else  ; go to the next level                       
                         (loop
                          (car ancs-to-view)
                          (cdr ancs-to-view)                                    
                          (map
                           (lambda (n) (cons n (cdr ancs-to-view)))
                           (cond
                             ((memq this-level
                                    (cdr  ; parent is an element => children
                                     (car ancs-to-view)))
                              => cdr)
                             (else  ; curr-node is an attribute node
                              ((select-kids sxml:node?) (car ancs-to-view)))))
                          pos-nset
                          vacant-num))))
                     ((memq next (sxml:attr-list (caar ancs-to-view)))
                      ; next node is an attribute of currently scanned node
                      (let ((pos-result
                             (process-single (car src) (cdr src) vacant-num)))
                        (cons
                         (append (reverse pos-nset) (car pos-result))
                         pos-result)))                     
                     ((eq? (caar content-set) next)
                      ; current node is eq to the next one in src
                      (let add-desc ((to-scan
                                      (descend
                                       (draft:smart-make-context
                                        (caar content-set)
                                        (cdar content-set)
                                        num-anc)))
                                     (pos-nset pos-nset)
                                     (vacant-num vacant-num))
                        (if
                         (null? to-scan)
                         (let ((pos-result
                                (process-single
                                 (car src) (cdr src) vacant-num)))
                           (cons
                            (append (reverse pos-nset) (car pos-result))
                            pos-result))
                         (add-desc (cdr to-scan)
                                   (cons (cons (car to-scan) vacant-num)
                                         pos-nset)                                   
                                   (+ vacant-num 1)))))
                     (else  ; go on to scan the next node
                      (loop
                       this-level
                       ancs-to-view
                       (append
                        (map
                         (lambda (n) (cons n (car content-set)))
                         ((sxml:child sxml:node?) (caar content-set)))
                        (cdr content-set))
                       (if
                        (test-pred? (caar content-set))
                        (cons (cons (draft:smart-make-context
                                     (caar content-set) (cdar content-set)
                                     num-anc)
                                    vacant-num)
                              pos-nset)
                        pos-nset)                           
                       (+ vacant-num 1)))))))))))
      (lambda (node)  ; node or nodeset
        (let ((nset (as-nodeset node)))
          (if (null? nset)  ; nothing to be done
              nset
              (filter  ; removing empty result nodesets
               (lambda (x) (not (null? x)))
               (process-single (car nset) (cdr nset) 1))))))))

; Following-sibling axis for the special case
(define (ddo:following-sibling-single-level-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (letrec
        (; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  pos-result
         (process-single
          (lambda (curr src vacant-num)
            (if
             (or (not (sxml:context? curr))
                 (null? (sxml:context->ancestors-u curr)))
             ; Siblings cannot be identified
             (if (null? src)  ; recursion is over
                 '()
                 (process-single (car src) (cdr src) vacant-num))             
             (cond
               ((memq (sxml:context->node-u curr)
                      (cdr  ; parent is an element => cdr gives its children
                       (car (sxml:context->ancestors-u curr))))
                => (lambda (foll-siblings)
                     (let ((ancestors
                            (draft:list-head
                             (sxml:context->ancestors-u curr) num-anc)))
                       (if
                        (null? src)  ; no more nodes
                        (let no-more ((foll-siblings (cdr foll-siblings))
                                      (res '())
                                      (vacant-num vacant-num))
                          (if
                           (null? foll-siblings)  ; everyone processed
                           (list (reverse res))
                           (no-more
                            (cdr foll-siblings)
                            (if (test-pred? (car foll-siblings))
                                (cons
                                 (cons
                                  (if (null? ancestors)
                                      (car foll-siblings)
                                      (draft:make-context
                                       (car foll-siblings) ancestors))
                                  vacant-num)
                                 res)
                                res)
                            (+ vacant-num 1))))
                        ; else there are more nodes in src
                        (let ((next (sxml:context->node (car src))))
                          (let more ((foll-siblings (cdr foll-siblings))
                                     (res '())
                                     (vacant-num vacant-num))
                            (if
                             (null? foll-siblings)  ; everyone processed
                             (cons
                              (reverse res)
                              (process-single (car src) (cdr src) vacant-num))
                             (let ((new-res
                                    (if (test-pred? (car foll-siblings))
                                        (cons
                                         (cons
                                          (if (null? ancestors)
                                              (car foll-siblings)
                                              (draft:make-context
                                               (car foll-siblings) ancestors))
                                          vacant-num)
                                         res)
                                        res)))
                               (if
                                (eq? (car foll-siblings) next)  ; recursion
                                (let ((pos-res
                                       (process-single
                                        (car src)
                                        (cdr src)
                                        (+ vacant-num 1))))
                                  (if
                                   (null? pos-res)  ; this shouldn't occur
                                   (list (reverse new-res))
                                   (cons (append
                                          (reverse new-res) (car pos-res))
                                         pos-res)))
                                (more (cdr foll-siblings)
                                      new-res
                                      (+ vacant-num 1)))))))))))
               (else  ; no following siblings
                (if (null? src)  ; recursion is over
                    '()
                    (process-single (car src) (cdr src) vacant-num))))))))
      (lambda (node)  ; node or nodeset
        (let ((nset (as-nodeset node)))
          (if (null? nset)  ; nothing to be done
              nset
              (filter  ; removing empty result nodesets
               (lambda (x) (not (null? x)))
               (process-single (car nset) (cdr nset) 1))))))))

; Parent axis, for position-based filtering
; This function has very much the same with ddo:parent-pos. In future, we
; should use a meta-function for paremetrization of these two
(define (ddo:parent-single-level-pos test-pred? . num-ancestors)
  (let ((parent
         (apply ddo:parent-single-level (cons test-pred? num-ancestors))))
    (letrec
        ((add-order-num
          (lambda (num nset)
            (if (null? nset)
                nset
                (cons (list (cons (car nset) num))
                      (add-order-num (+ num 1) (cdr nset)))))))
      (lambda (node)   ; node or nodeset
        (add-order-num 1 (parent node))))))

; Preceding axis for the special case
(define (ddo:preceding-single-level-pos test-pred? . num-ancestors)
  (let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
         (descend (draft:descendant-or-self test-pred? num-anc))
         (precede (draft:preceding test-pred? num-anc)))
    (letrec
        (; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  pos-result
         (process-single
          (lambda (curr src vacant-num)
            (cond
              ((null? src)  ; no more nodes
               (let add-labels ((to-scan (precede curr))
                                (res '())
                                (vacant-num vacant-num))
                 (if (null? to-scan)
                     (list (reverse res))
                     (add-labels (cdr to-scan)
                                 (cons (cons (car to-scan) vacant-num) res)
                                 (- vacant-num 1)))))
              ((not (sxml:context? curr))  ; can't find following nodes
               (cons '() (process-single (car src) (cdr src) vacant-num)))
              (else
               (let ((next (sxml:context->node (car src))))
                 (let loop ((this-level (sxml:context->node-u curr))
                            (ancs-to-view (sxml:context->ancestors-u curr))
                            (content-set '())
                            (pos-nset '())
                            (vacant-num vacant-num))
                   (cond
                     ((null? content-set)  ; no one to scan at this level
                      (if
                       (null? ancs-to-view)
                       (cons
                        (reverse pos-nset)
                        (process-single (car src) (cdr src) vacant-num))
                       (loop
                        (car ancs-to-view)
                        (cdr ancs-to-view)                          
                        (reverse
                         (map
                          sxml:context->content
                          (descend
                           (map
                            (lambda (n)
                              (draft:smart-make-context
                               n (cdr ancs-to-view) num-anc))
                            (cond
                              ((memq this-level
                                     (reverse
                                      ((select-kids sxml:node?)
                                       (car ancs-to-view))))                             
                               => (lambda (nset) (reverse (cdr nset))))
                              (else  ; curr-node is an attribute node
                               '()))))))
                          pos-nset
                          vacant-num)))                     
                     ((eq? (caar content-set) next)
                      ; current node is eq to the next one in src
                      (let ((pos-result
                             (process-single
                              (car src)
                              (cdr src)
                              (- vacant-num 1))))
                        (cons
                         (append
                          (reverse
                           (if
                            (test-pred? (caar content-set))
                            (cons (cons (draft:smart-make-context
                                         (caar content-set) (cdar content-set)
                                         num-anc)
                                        vacant-num)
                                  pos-nset)
                            pos-nset))
                          (car pos-result))
                         pos-result)))
                     (else  ; go on to scan the next node
                      (loop
                       this-level
                       ancs-to-view                       
                       (cdr content-set)
                       (if
                        (test-pred? (caar content-set))
                        (cons (cons (draft:smart-make-context
                                     (caar content-set) (cdar content-set)
                                     num-anc)
                                    vacant-num)
                              pos-nset)
                        pos-nset)
                       (- vacant-num 1)))))))))))
      (lambda (node)  ; node or nodeset
        (let ((nset (reverse (as-nodeset node))))
          (if (null? nset)  ; nothing to be done
              nset
              (filter  ; removing empty result nodesets
               (lambda (x) (not (null? x)))
               (process-single (car nset) (cdr nset) -1))))))))

; Preceding-sibling axis for the special case
(define (ddo:preceding-sibling-single-level-pos test-pred? . num-ancestors)
  (let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
    (letrec
        (; curr - current context to be processed
         ; src - remaining source contexts
         ; vacant-num - order number for the result node
         ; Returns:  pos-result
         (process-single
          (lambda (curr src vacant-num)
            (if
             (or (not (sxml:context? curr))
                 (null? (sxml:context->ancestors-u curr)))
             ; Siblings cannot be identified
             (if (null? src)  ; recursion is over
                 '()
                 (process-single (car src) (cdr src) vacant-num))             
             (cond
               ((memq (sxml:context->node-u curr)
                      (reverse
                       (cdr  ; parent is an element => cdr gives its children
                        (car (sxml:context->ancestors-u curr)))))
                => (lambda (prec-siblings)
                     (let ((ancestors
                            (draft:list-head
                             (sxml:context->ancestors-u curr) num-anc)))
                       (if
                        (null? src)  ; no more nodes
                        (let no-more ((prec-siblings (cdr prec-siblings))
                                      (res '())
                                      (vacant-num vacant-num))
                          (if
                           (null? prec-siblings)  ; everyone processed
                           (list (reverse res))
                           (no-more
                            (cdr prec-siblings)
                            (if (test-pred? (car prec-siblings))
                                (cons
                                 (cons
                                  (if (null? ancestors)
                                      (car prec-siblings)
                                      (draft:make-context
                                       (car prec-siblings) ancestors))
                                  vacant-num)
                                 res)
                                res)
                            (- vacant-num 1))))
                        ; else there are more nodes in src
                        (let ((next (sxml:context->node (car src))))
                          (let more ((prec-siblings (cdr prec-siblings))
                                     (res '())
                                     (vacant-num vacant-num))
                            (if
                             (null? prec-siblings)  ; everyone processed
                             (cons
                              (reverse res)
                              (process-single (car src) (cdr src) vacant-num))
                             (let ((new-res
                                    (if (test-pred? (car prec-siblings))
                                        (cons
                                         (cons
                                          (if (null? ancestors)
                                              (car prec-siblings)
                                              (draft:make-context
                                               (car prec-siblings) ancestors))
                                          vacant-num)
                                         res)
                                        res)))
                               (if
                                (eq? (car prec-siblings) next)  ; recursion
                                (let ((pos-res
                                       (process-single
                                        (car src)
                                        (cdr src)
                                        (- vacant-num 1))))
                                  (if
                                   (null? pos-res)  ; this shouldn't occur
                                   (list (reverse new-res))
                                   (cons (append
                                          (reverse new-res) (car pos-res))
                                         pos-res)))
                                (more (cdr prec-siblings)
                                      new-res
                                      (- vacant-num 1)))))))))))
               (else  ; no preceding siblings
                (if (null? src)  ; recursion is over
                    '()
                    (process-single (car src) (cdr src) vacant-num))))))))
      (lambda (node)  ; node or nodeset
        (let ((nset (reverse (as-nodeset node))))
          (if (null? nset)  ; nothing to be done
              nset
              (reverse
               (filter  ; removing empty result nodesets
                (lambda (x) (not (null? x)))
                (process-single (car nset) (cdr nset) -1)))))))))

(provide (all-defined))