racket/collects/web-server/tests/tmp/sxml/xpath-context_xlink.ss
Jay McCarthy 19d59da08b Moving temporary code
svn: r7822
2007-11-23 18:56:31 +00:00

2984 lines
112 KiB
Scheme

; Module header is generated automatically
#cs(module xpath-context_xlink mzscheme
(require (lib "string.ss" "srfi/13"))
(require "sxpathlib.ss")
(require "sxml-tools.ss")
(require "sxpath-ext.ss")
(require "xpath-parser.ss")
(require "txpath.ss")
(require "xpath-ast.ss")
(require (lib "htmlprag.ss" "web-server/tests/tmp/htmlprag"))
(require (lib "ssax.ss" "web-server/tests/tmp/ssax"))
;; Context-based XPath implementation
;
; This software is in Public Domain.
; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
;
; Please send bug reports and comments to:
; lisovsky@acm.org Kirill Lisovsky
; lizorkin@hotbox.ru Dmitry Lizorkin
;
; <nodeset> ::= ( <nodeset-member>* )
; <nodeset-member> ::= <node> | <context>
; <context> ::= ( *CONTEXT* <node> <ancestor>* )
; <node> - an SXML node (a context node)
; <ancestor>* - context node's parent, grandparent, grandgrandparent etc.
;
; A CONTEXT doesn't contain more ANCESTORs than actually required for
; evaluating the location path. This is achieved by means of an "intellectual"
; parsing of the location path. The number of ANCESTORs stored in the CONTEXT
; can differ for different path steps.
;=========================================================================
; Basic operations over context
; A fast however unsafe predicate
; Assumes that the 'node' provided is a pair
(define (sxml:context-u? node)
(eq? (car node) '*CONTEXT*))
; Safer predicate
(define (sxml:context? node)
(and (pair? node) (eq? (car node) '*CONTEXT*)))
;-------------------------------------------------
; Accessors
; Fast however unsafe accessors
; Assume that the argument is the proper context
(define sxml:context->node-u cadr)
(define sxml:context->ancestors-u cddr)
(define sxml:context->content-u cdr)
; Safe accessors
; Can be applied to both a context and an ordinary node
(define (sxml:context->node context)
(if (sxml:context? context) (cadr context) context))
(define (sxml:context->ancestors context)
(if (sxml:context? context) (cddr context) '()))
(define (sxml:context->content context)
(if (sxml:context? context) (cdr context) (list context)))
; Given a context-set, converts it to a nodeset
(define (draft:contextset->nodeset obj)
(if (nodeset? obj)
(map sxml:context->node obj)
obj))
;-------------------------------------------------
; Mutators
; Constructor
(define (draft:make-context node ancestors)
(cons '*CONTEXT* (cons node ancestors)))
; A smarter constructor
; Makes context only when required, with the 'num-anc' required
(define (draft:smart-make-context node ancestors num-anc)
(if
(or (and num-anc (zero? num-anc))
(null? ancestors))
node ; no need for context construction
(cons '*CONTEXT*
(cons node
(draft:list-head ancestors num-anc)))))
; Provided a 'nodeset' of sibling nodes, wraps each into context
; If 'ancestors' is empty, keeps 'nodeset' unchanged
(define (draft:siblings->context-set nodeset ancestors)
(if (null? ancestors)
nodeset
(map
(lambda (node) (draft:make-context node ancestors))
nodeset)))
;-------------------------------------------------
; Operations on num-ancestors
; Complexity results from #f as a value for num-ancestors (which means that the
; number of ancestors is infinite)
(define (draft:na+ na1 na2)
(if
(or (not na1) (not na2)) ; either argument is infinite
#f
(+ na1 na2)))
(define (draft:na-minus na value)
(if (not na) na (- na value)))
; Minus, with the result that is always non-negative
(define (draft:na-minus-nneg na value)
(cond
((not na) na)
((< (- na value) 0) 0)
(else (- na value))))
(define (draft:na-max . na-lst)
(cond
((null? na-lst) 0)
((member #f na-lst) #f)
(else (apply max na-lst))))
(define (draft:na-min . na-lst)
(if
(null? na-lst) 0
(let ((num-lst (filter (lambda (x) x) na-lst)))
(if (null? num-lst) #f ; all na-lst consists of #f
(apply min num-lst)))))
(define (draft:na> na1 na2)
(cond
((not na2) ; second argument in infinite
#f)
((not na1) ; first argument is infinite
#t)
(else ; niether argument is infinite
(> na1 na2))))
(define (draft:na>= na1 na2)
(cond
((not na2) ; second argument in infinite
(not na1))
((not na1) ; first argument is infinite
#t)
(else ; niether argument is infinite
(>= na1 na2))))
;=========================================================================
; Misc helpers
; Similar to R5RS 'list-tail' but returns the new list consisting of the first
; 'k' members of 'lst'
; If k>(length lst) or k=#f, lst is returned
; NOTE1: k=#f is used in this implementation to represent positive infinity
; NOTE2: Unless k=#f, the result is always a newly allocated list. This is the
; main methodological difference between this function and R5RS 'list-tail'
(define (draft:list-head lst k)
(letrec
((list-head
(lambda (lst k)
(if (or (null? lst) (zero? k))
'()
(cons (car lst) (list-head (cdr lst) (- k 1)))))))
(if k
(list-head lst k)
lst)))
; Returns the last member of the list
; It is an error for the list to be empty
(define (draft:list-last lst)
(if (null? (cdr lst))
(car lst)
(draft:list-last (cdr lst))))
; Constructs the (listof value), whose length is num
(define (draft:make-list value num)
(if (= num 0)
'()
(cons value (draft:make-list value (- num 1)))))
; Similar to txp:signal-semantic-error, but returns #f
(define (draft:signal-semantic-error . text)
(apply txp:signal-semantic-error text)
#f)
; The top of the SXML document?
(define (draft:top? node)
(and (pair? node) (eq? (car node) '*TOP*)))
; Removes eq duplicates from the nodeset
(define (draft:remove-eq-duplicates nodeset)
(cond
((null? nodeset) nodeset)
((memq (car nodeset) (cdr nodeset))
(draft:remove-eq-duplicates (cdr nodeset)))
(else
(cons (car nodeset) (draft:remove-eq-duplicates (cdr nodeset))))))
; Reaches the root of the root of the contextset
; Result: nodeset
(define (draft:reach-root contextset)
(let ((nodeset (map
(lambda (node)
(if
(sxml:context? node)
(draft:list-last (sxml:context->ancestors-u node))
node))
contextset)))
(if (or (null? nodeset) (null? (car nodeset))) ; (length nodeset)<=1
nodeset
(draft:remove-eq-duplicates nodeset))))
; Recovers context for each node of the nodeset
; Context recovery is performed in its usual technique: searching from the
; root of the document. As a result, this function can be fairly slow.
; In this implementation, it is sometimes called after an XPath 'id' function,
; for location paths like "id(name)/.."
; By nature of 'id-index', context is lost when we access elements by their
; ID. It may be a good idea to rework the structure of 'id-index' to make it
; more suitable for purposes of this context-based XPath implementation.
; A good news is that only a few elements are usually selected by XPath 'id'
; function, thus the overhead of searching from the root node might be
; acceptable in this case.
(define (draft:recover-contextset nodeset root-node num-anc)
(map
(lambda (node)
(draft:smart-make-context
node
(((sxml:ancestor (lambda (x) #t)) root-node) node)
num-anc))
nodeset))
;-------------------------------------------------
; For sxpath: handling a procedure as a location step
; Makes a context-set from a nodeset supplied, with the num-anc required
; ancestors-set ::= (listof ancestors)
; ancestors ::= (listof node)
; Members of the nodeset are known to be descendants-or-selves of
; (map car ancestors-set)
(define (draft:find-proper-context nodeset ancestors-set num-anc)
(map
(lambda (node)
(if
(sxml:context? node) ; already a context
node ; nothing to be done
(let loop ((this-level ancestors-set)
(next-level '()))
(if
(null? this-level) ; this level fully analyzed
(if (null? next-level) ; failed to find
node
(loop next-level '()))
(let ((ancestors (car this-level)))
(if
(eq? node (car ancestors)) ; proper ancestors found
(draft:make-context
node
(draft:list-head (cdr ancestors) num-anc))
(loop (cdr this-level)
(append
(map
(lambda (n) (cons n ancestors))
((sxml:child sxml:node?) (car ancestors)))
(map
(lambda (n) (cons n ancestors))
((sxml:attribute (lambda (x) #t)) (car ancestors)))
next-level))))))))
nodeset))
;=========================================================================
; XPath axes
; Implementation is based on the concept of context
; Compared to "general" SXPath, a new optional argument was added:
; NUM-ANCESTORS - number of node's ancestors that will be required later in
; the location path. For example, NUM-ANCESTORS=1 means that the node's parent
; only must be remembered in the CONTEXT, grandparents will not be required
; Ancestor axis
(define (draft:ancestor test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(if
(sxml:context? node)
(let loop ((ancs-to-view (sxml:context->ancestors-u node))
(res '()))
(cond
((null? ancs-to-view) ; processed everyone
(reverse res) ; reverse document order required
)
((test-pred? (car ancs-to-view)) ; can add it to result
(loop
(cdr ancs-to-view)
(cons
(draft:smart-make-context
(car ancs-to-view) (cdr ancs-to-view) num-anc)
res)))
(else ; current node doesn't satisfy the predicate
(loop (cdr ancs-to-view) res))))
'() ; no ancestors
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Ancestor-or-self axis
(define (draft:ancestor-or-self test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(cond
((sxml:context? node)
(let loop ((ancs-to-view (sxml:context->content-u node))
(res '()))
(cond
((null? ancs-to-view) ; processed everyone
(reverse res) ; reverse document order required
)
((test-pred? (car ancs-to-view)) ; can add it to result
(loop
(cdr ancs-to-view)
(cons
(draft:smart-make-context
(car ancs-to-view) (cdr ancs-to-view) num-anc)
res)))
(else ; current node doesn't satisfy the predicate
(loop (cdr ancs-to-view) res)))))
; ordinary SXML node
((test-pred? node) ; satisfies the predicate
(list node))
(else
'())))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Attribute axis
; Borrows much from draft:child
(define (draft:attribute test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(cond
((not (pair? node)) '()) ; no attributes
; (car node) is always a symbol
((sxml:context-u? node) ; a context node
(draft:siblings->context-set
((sxml:filter test-pred?)
(sxml:attr-list (sxml:context->node-u node)))
(draft:list-head (sxml:context->content-u node) num-anc)))
(else ; an ordinary node, and is a pair
(draft:siblings->context-set
((sxml:filter test-pred?) (sxml:attr-list node))
(draft:list-head (list node) num-anc)))))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Child axis
(define (draft:child test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(cond
((not (pair? node)) '()) ; no children
; (car node) is always a symbol
((sxml:context-u? node) ; a context node
(draft:siblings->context-set
((select-kids test-pred?) (sxml:context->node-u node))
(draft:list-head (sxml:context->content-u node) num-anc)))
; an ordinary node, and is a pair
((memq (car node) '(*PI* *COMMENT* *ENTITY*))
'())
(else
(draft:siblings->context-set
((sxml:filter test-pred?) (cdr node)) ; like in 'select-kids'
(draft:list-head (list node) num-anc)))))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Descendant axis
(define (draft:descendant test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(child (draft:child sxml:node? num-anc))
(this-axis
(lambda (node) ; not a nodeset
(let rpt ((res '())
(more (child node)))
(if (null? more)
(reverse res)
(rpt
(if (test-pred? (sxml:context->node (car more)))
(cons (car more) res)
res)
(append (child (car more)) (cdr more))))))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Descendant-or-self axis
(define (draft:descendant-or-self test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(child (draft:child sxml:node? num-anc))
(this-axis
(lambda (node) ; not a nodeset
(let rpt ((res '())
(more (list node)))
(if (null? more)
(reverse res)
(rpt
(if (test-pred? (sxml:context->node (car more)))
(cons (car more) res)
res)
(append (child (car more)) (cdr more))))))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Following axis
(define (draft:following test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(descend (draft:descendant-or-self test-pred? num-anc))
(this-axis
(lambda (node) ; not a nodeset
(if
(sxml:context? node)
(let loop ((curr-node (sxml:context->node-u node))
(ancs-to-view (sxml:context->ancestors-u node))
(res '()))
(if
(null? ancs-to-view) ; processed everyone
res
(loop
(car ancs-to-view)
(cdr ancs-to-view)
(append
res
(descend
(draft:siblings->context-set
(cond
((memq curr-node
(cdr ; parent is an element => cdr gives its children
(car ancs-to-view)))
=> cdr)
(else ; curr-node is an attribute node
((select-kids sxml:node?) (car ancs-to-view))))
(draft:list-head ancs-to-view num-anc)))))))
'() ; no following members
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Following-sibling axis
(define (draft:following-sibling test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(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 (foll-siblings)
(draft:siblings->context-set
((sxml:filter test-pred?) (cdr foll-siblings))
(draft:list-head
(sxml:context->ancestors-u node) num-anc))))
(else ; no following siblings
'()))
'() ; no parent => no siblings
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Namespace axis
; Borrows much from draft:child
(define (draft:namespace test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(cond
((not (pair? node)) '()) ; no namespaces
; (car node) is always a symbol
((sxml:context-u? node) ; a context node
(draft:siblings->context-set
((sxml:filter test-pred?)
(sxml:ns-list (sxml:context->node-u node)))
(draft:list-head (sxml:context->content-u node) num-anc)))
(else ; an ordinary node, and is a pair
(draft:siblings->context-set
((sxml:filter test-pred?) (sxml:ns-list node))
(draft:list-head (list node) num-anc)))))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Parent axis
(define (draft:parent test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(lambda (node) ; not a nodeset
(if
(and (sxml:context? node)
(not (null? (sxml:context->ancestors-u node)))
(test-pred? (car (sxml:context->ancestors-u node))))
(draft:smart-make-context
(car (sxml:context->ancestors-u node))
(cdr (sxml:context->ancestors-u node))
num-anc)
'() ; no parent
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Preceding axis
(define (draft:preceding test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(descend (draft:descendant-or-self test-pred? num-anc))
(this-axis
(lambda (node) ; not a nodeset
(if
(sxml:context? node)
(let loop ((curr-node (sxml:context->node-u node))
(ancs-to-view (sxml:context->ancestors-u node))
(to-descend '()))
(cond
((null? ancs-to-view) ; processed everyone
(map-union
(lambda (node) (reverse (descend node)))
to-descend))
((memq curr-node
(reverse
((select-kids sxml:node?)
(car ancs-to-view))))
=> (lambda (prec-siblings)
(loop
(car ancs-to-view)
(cdr ancs-to-view)
(append
to-descend
(draft:siblings->context-set
(cdr prec-siblings)
(draft:list-head ancs-to-view num-anc))))))
(else ; no preceding siblings
(loop (car ancs-to-view)
(cdr ancs-to-view)
to-descend))))
'() ; no preceding members
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Preceding-sibling axis
(define (draft:preceding-sibling test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(this-axis
(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)))))
=> (lambda (prec-siblings)
(draft:siblings->context-set
((sxml:filter test-pred?) (cdr prec-siblings))
(draft:list-head
(sxml:context->ancestors-u node) num-anc))))
(else ; no preceding siblings
'()))
'() ; no parent => no siblings
))))
(lambda (node) ; node or nodeset
(if (nodeset? node)
(map-union this-axis node)
(this-axis node)))))
; Self axis
; num-ancestors is not used here
(define (draft:self test-pred? . num-ancestors)
(sxml:filter
(lambda (node) (test-pred? (sxml:context->node node)))))
;==========================================================================
; XPath Core Function Library
;-------------------------------------------------
; 4.1 Node Set Functions
; last()
(define (draft:core-last num-anc)
(lambda (nodeset position+size var-binding)
(cdr position+size)))
; position()
(define (draft:core-position num-anc)
(lambda (nodeset position+size var-binding)
(car position+size)))
; count(node-set)
(define (draft:core-count num-anc arg-func)
(lambda (nodeset position+size var-binding)
(let ((res (arg-func nodeset position+size var-binding)))
(cond
((nodeset? res) (length res))
(else
(sxml:xpointer-runtime-error
"count() function - an argument is not a nodeset")
0)))))
; id(object)
(define (draft:core-id num-anc arg-func)
(lambda (nodeset position+size var-binding)
(let* ((root-node (draft:reach-root nodeset))
(id-nset ((sxml:child (ntype?? 'id-index))
((sxml:child (ntype?? '@@)) root-node))))
(if
(null? id-nset) ; no id-index
'() ; ID function returns an empty nodeset
(let ((res ((sxml:id (cdar id-nset)) ; implemented in "sxpath-ext.scm"
(draft:contextset->nodeset
(arg-func nodeset position+size var-binding)))))
(if (and num-anc (zero? num-anc)) ; no ancestors required
res
(draft:recover-contextset res root-node num-anc)))))))
; local-name(node-set?)
(define (draft:core-local-name num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(let ((nodeset (draft:contextset->nodeset nodeset)))
(cond
((null? nodeset) "")
((not (pair? (car nodeset))) "") ; no name
(else
(let ((name (symbol->string (caar nodeset))))
(cond
((string-rindex name #\:)
=> (lambda (pos)
(substring name (+ pos 1) (string-length name))))
(else ; a NCName
name)))))))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(let ((obj
(draft:contextset->nodeset
(func nodeset position+size var-binding))))
(cond
((null? obj) "") ; an empty nodeset
((not (nodeset? obj))
(sxml:xpointer-runtime-error
"NAME function - an argument is not a nodeset")
"")
((not (pair? (car obj))) "") ; no name
(else
(let ((name (symbol->string (caar obj))))
(cond
((string-rindex name #\:)
=> (lambda (pos)
(substring
name (+ pos 1) (string-length name))))
(else ; a NCName
name))))))))))
; namespace-uri(node-set?)
(define (draft:core-namespace-uri num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(let ((nodeset (draft:contextset->nodeset nodeset)))
(cond
((null? nodeset) "")
((not (pair? (car nodeset))) "") ; no name
(else
(let ((name (symbol->string (caar nodeset))))
(cond
((string-rindex name #\:)
=> (lambda (pos)
(substring name 0 pos)))
(else ; a NCName
"")))))))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(let ((obj
(draft:contextset->nodeset
(func nodeset position+size var-binding))))
(cond
((null? obj) "") ; an empty nodeset
((not (nodeset? obj))
(sxml:xpointer-runtime-error
"NAME function - an argument is not a nodeset")
"")
((not (pair? (car obj))) "") ; no name
(else
(let ((name (symbol->string (caar obj))))
(cond
((string-rindex name #\:)
=> (lambda (pos)
(substring name 0 pos)))
(else ""))))))))))
; name(node-set?)
(define (draft:core-name num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(let ((nodeset (draft:contextset->nodeset nodeset)))
(cond
((null? nodeset) "")
((not (pair? (car nodeset))) "") ; no name
(else
(symbol->string (caar nodeset))))))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(let ((obj
(draft:contextset->nodeset
(func nodeset position+size var-binding))))
(cond
((null? obj) "") ; an empty nodeset
((not (nodeset? obj))
(sxml:xpointer-runtime-error
"NAME function - an argument is not a nodeset")
"")
((not (pair? (car obj))) "") ; no name
(else
(symbol->string (caar obj)))))))))
;-------------------------------------------------
; 4.2 String Functions
; string(object?)
(define (draft:core-string num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(sxml:string
(draft:contextset->nodeset nodeset)))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(sxml:string
(draft:contextset->nodeset
(func nodeset position+size var-binding)))))))
; concat(string, string, string*)
(define (draft:core-concat num-anc . arg-func-lst)
(lambda (nodeset position+size var-binding)
(apply
string-append
(map
(lambda (f)
(sxml:string
(draft:contextset->nodeset
(f nodeset position+size var-binding))))
arg-func-lst))))
; starts-with(string, string)
(define (draft:core-starts-with num-anc arg-func1 arg-func2)
(lambda (nodeset position+size var-binding)
(let ((str1 (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(str2 (sxml:string
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding)))))
(string-prefix? str2 str1))))
; contains(string, string)
(define (draft:core-contains num-anc arg-func1 arg-func2)
(lambda (nodeset position+size var-binding)
(let ((str1 (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(str2 (sxml:string
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding)))))
(if (substring? str2 str1) #t #f) ; must return a boolean
)))
; substring-before(string, string)
(define (draft:core-substring-before num-anc arg-func1 arg-func2)
(lambda (nodeset position+size var-binding)
(let* ((str1 (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(str2 (sxml:string
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding))))
(pos (substring? str2 str1)))
(if (not pos) ; STR1 doesn't contain STR2
""
(substring str1 0 pos)))))
; substring-after(string, string)
(define (draft:core-substring-after num-anc arg-func1 arg-func2)
(lambda (nodeset position+size var-binding)
(let* ((str1 (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(str2 (sxml:string
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding))))
(pos (substring? str2 str1)))
(if
(not pos) ; STR1 doesn't contain STR2
""
(substring
str1 (+ pos (string-length str2)) (string-length str1))))))
; substring(string, number, number?)
(define (draft:core-substring num-anc arg-func1 arg-func2 . arg-func3)
(if (null? arg-func3) ; no third argument supplied
(lambda (nodeset position+size var-binding)
(let ((str (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(num1 (sxml:number
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding)))))
(let ((len (string-length str))
(start (- (inexact->exact (round num1)) 1)))
(if (> start len)
""
(substring str (if (< start 0) 0 start) len)))))
(let ((arg-func3 (car arg-func3)))
(lambda (nodeset position+size var-binding)
(let ((str (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(num1 (sxml:number
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding))))
(num2 (sxml:number
(draft:contextset->nodeset
(arg-func3 nodeset position+size var-binding)))))
(let* ((len (string-length str))
(start (- (inexact->exact (round num1)) 1))
(fin (+ start (inexact->exact (round num2)))))
(if (or (> start len) (< fin 0) (< fin start))
""
(substring str
(if (< start 0) 0 start)
(if (> fin len) len fin)))))))))
; string-length(string?)
(define (draft:core-string-length num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(string-length
(sxml:string (draft:contextset->nodeset nodeset))))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(string-length
(sxml:string
(draft:contextset->nodeset
(func nodeset position+size var-binding))))))))
; normalize-space(string?)
(define (draft:core-normalize-space num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(let rpt ((src (string-split
(sxml:string (draft:contextset->nodeset nodeset))
sxml:whitespace))
(res '()))
(cond
((null? src)
(apply string-append (reverse res)))
((= (string-length (car src)) 0) ; empty string
(rpt (cdr src) res))
((null? res)
(rpt (cdr src) (cons (car src) res)))
(else
(rpt (cdr src) (cons (car src) (cons " " res)))))))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(let rpt ((src (string-split
(sxml:string
(draft:contextset->nodeset
(func nodeset position+size var-binding)))
sxml:whitespace))
(res '()))
(cond
((null? src)
(apply string-append (reverse res)))
((= (string-length (car src)) 0) ; empty string
(rpt (cdr src) res))
((null? res)
(rpt (cdr src) (cons (car src) res)))
(else
(rpt (cdr src) (cons (car src) (cons " " res))))))))))
; translate(string, string, string)
(define (draft:core-translate num-anc arg-func1 arg-func2 arg-func3)
(lambda (nodeset position+size var-binding)
(let ((str1 (sxml:string
(draft:contextset->nodeset
(arg-func1 nodeset position+size var-binding))))
(str2 (sxml:string
(draft:contextset->nodeset
(arg-func2 nodeset position+size var-binding))))
(str3 (sxml:string
(draft:contextset->nodeset
(arg-func3 nodeset position+size var-binding)))))
(let ((alist
(let while ((lst2 (string->list str2))
(lst3 (string->list str3))
(alist '()))
(cond
((null? lst2) (reverse alist))
((null? lst3)
(append
(reverse alist)
(map
(lambda (ch) (cons ch #f))
lst2)))
(else
(while
(cdr lst2)
(cdr lst3)
(cons (cons (car lst2) (car lst3)) alist)))))))
(let rpt ((lst1 (string->list str1))
(res '()))
(cond
((null? lst1) (list->string (reverse res)))
((assoc (car lst1) alist)
=> (lambda (pair)
(if (cdr pair)
(rpt (cdr lst1) (cons (cdr pair) res))
(rpt (cdr lst1) res))))
(else
(rpt (cdr lst1) (cons (car lst1) res)))))))))
;-------------------------------------------------
; 4.3 Boolean Functions
; boolean(object)
(define (draft:core-boolean num-anc arg-func)
(lambda (nodeset position+size var-binding)
(sxml:boolean
(arg-func nodeset position+size var-binding))))
; not(boolean)
(define (draft:core-not num-anc arg-func)
(lambda (nodeset position+size var-binding)
(not (sxml:boolean
(arg-func nodeset position+size var-binding)))))
; true()
(define (draft:core-true num-anc)
(lambda (nodeset position+size var-binding) #t))
; false()
(define (draft:core-false num-anc)
(lambda (nodeset position+size var-binding) #f))
; lang(string)
(define (draft:core-lang num-anc arg-func)
(lambda (nodeset position+size var-binding)
(let ((arg (sxml:string
(draft:contextset->nodeset
(arg-func nodeset position+size var-binding))))
(lng
((draft:child (ntype?? '*text*))
((draft:attribute (ntype?? 'xml:lang))
((draft:ancestor-or-self (lambda (x) #t))
(car nodeset) ; context-node = (car nodeset)
)))))
(and (not (null? lng))
(or (string-ci=? arg (car lng))
(string-prefix-ci? (string-append arg "-") (car lng)))))))
;-------------------------------------------------
; 4.4 Number Functions
; number(object?)
(define (draft:core-number num-anc . arg-func) ; optional argument
(if (null? arg-func) ; no argument supplied
(lambda (nodeset position+size var-binding)
(sxml:number (draft:contextset->nodeset nodeset)))
(let ((func (car arg-func)))
(lambda (nodeset position+size var-binding)
(sxml:number
(draft:contextset->nodeset
(func nodeset position+size var-binding)))))))
; sum(node-set)
(define (draft:core-sum num-anc arg-func)
(lambda (nodeset position+size var-binding)
(let ((res (arg-func nodeset position+size var-binding)))
(cond
((nodeset? res)
(apply +
(map
(lambda (node)
(sxml:number
(sxml:string-value (sxml:context->node node))))
res)))
(else
(sxml:xpointer-runtime-error
"SUM function - an argument is not a nodeset")
0)))))
; floor(number)
(define (draft:core-floor num-anc arg-func)
(lambda (nodeset position+size var-binding)
(inexact->exact
(floor (sxml:number
(draft:contextset->nodeset
(arg-func nodeset position+size var-binding)))))))
; ceiling(number)
(define (draft:core-ceiling num-anc arg-func)
(lambda (nodeset position+size var-binding)
(inexact->exact
(ceiling (sxml:number
(draft:contextset->nodeset
(arg-func nodeset position+size var-binding)))))))
; round(number)
(define (draft:core-round num-anc arg-func)
(lambda (nodeset position+size var-binding)
(inexact->exact
(round (sxml:number
(draft:contextset->nodeset
(arg-func nodeset position+size var-binding)))))))
;=========================================================================
; XPath AST processing
; AST is considered to be properly formed
; {5} <AxisSpecifier> ::= (axis-specifier <AxisName> )
; {6} <AxisName> ::= (ancestor)
; | (ancestor-or-self)
; | (attribute)
; | (child)
; | (descendant)
; | (descendant-or-self)
; | (following)
; | (following-sibling)
; | (namespace)
; | (parent)
; | (preceding)
; | (preceding-sibling)
; | (self)
; | (arc) ; the following 3 are added by SXLink
; | (traverse)
; | (traverse-arc)
; Returns: (list lambda num-ancestors pass-vars?)
; pass-vars? - a boolean: whether var-bindings must be passed to the axis
(define (draft:ast-axis-specifier op num-anc)
(if
(not (eq? (car op) 'axis-specifier))
(draft:signal-semantic-error "not an AxisSpecifier - " op)
(case (caadr op) ; AxisName
((ancestor)
(list draft:ancestor #f #f))
((ancestor-or-self)
(list draft:ancestor-or-self #f #f))
((attribute)
(list draft:attribute (draft:na-minus-nneg num-anc 1) #f))
((child)
(list draft:child (draft:na-minus-nneg num-anc 1) #f))
((descendant)
(list draft:descendant (draft:na-minus-nneg num-anc 1) #f))
((descendant-or-self)
(list draft:descendant-or-self num-anc #f))
((following)
(list draft:following #f #f))
((following-sibling)
(list draft:following-sibling (draft:na-max num-anc 1) #f))
((namespace)
(list draft:namespace (draft:na-minus-nneg num-anc 1) #f))
((parent)
(list draft:parent (draft:na+ num-anc 1) #f))
((preceding)
(list draft:preceding #f #f))
((preceding-sibling)
(list draft:preceding-sibling (draft:na-max num-anc 1) #f))
((self)
(list draft:self num-anc #f))
((arc)
(list xlink:axis-arc #f #f))
((traverse)
(list xlink:axis-traverse #f #t))
((traverse-arc)
(list xlink:axis-traverse-arc #f #t))
(else
(draft:signal-semantic-error "unknown AxisName - " op)))))
; {7} <NodeTest> ::= (node-test (*))
; | (node-test (namespace-uri <String> ))
; | (node-test (namespace-uri <String> )?
; (local-name <String> ))
; | (node-test (comment))
; | (node-test (text))
; | (node-test (pi <String>? ))
; | (node-test (point))
; | (node-test (range))
; + added by sxpath native syntax:
; | (node-test (equal? <SXML-node> ))
; | (node-test (eq? <SXML-node> ))
; | (node-test (names <String>+ ))
; | (node-test (not-names <String>+ ))s
(define (draft:ast-node-test op)
(if
(not (eq? (car op) 'node-test))
(draft:signal-semantic-error "not an NodeTest - " op)
(case (caadr op) ; NodeTest name
((*)
(ntype?? '*))
((namespace-uri)
(cond
((= (length op) 2) ; NodeTest in the form of prefix:*
(ntype-namespace-id?? (cadadr op)))
((eq? (caaddr op) 'local-name)
(ntype?? (string->symbol
(string-append (cadadr op) ":" (cadr (caddr op))))))
(else
(draft:signal-semantic-error "improper QName NodeTest - " op))))
((local-name)
(ntype?? (string->symbol (cadadr op))))
((comment)
(ntype?? '*COMMENT*))
((text)
(ntype?? '*text*))
((pi)
(if (= (length (cadr op)) 2) ; PI target supplied
(let ((target (string->symbol (cadadr op))))
(lambda (node)
(and (pair? node)
(eq? (car node) '*PI*)
(equal? (cadr node) target))))
(lambda (node)
(and (pair? node) (eq? (car node) '*PI*)))))
((node) sxml:node?)
((point)
(draft:signal-semantic-error
"point() NodeTest is not supported by this implementation"))
((range)
(draft:signal-semantic-error
"range() NodeTest is not supported by this implementation"))
((equal?)
(node-equal? (cadadr op)))
((eq?)
(node-eq? (cadadr op)))
((names)
(ntype-names?? (cdadr op)))
((not-names)
(sxml:complement (ntype-names?? (cdadr op))))
(else
(draft:signal-semantic-error "unknown NodeTest - " op)))))
;-------------------------------------------------
; In this section, each function accepts 2 arguments
; op - S-expression which represents the operation
; num-anc - how many ancestors are required in the context after that
; operation
; and returns either #f, which signals of a semantic error, or
; (cons (lambda (nodeset position+size var-binding) ...)
; num-anc-it-requires)
; position+size - the same to what was called 'context' in TXPath-1
; {1} <LocationPath> ::= <RelativeLocationPath>
; | <AbsoluteLocationPath>
(define (draft:ast-location-path op num-anc)
(case (car op)
((absolute-location-path)
(draft:ast-absolute-location-path op num-anc))
((relative-location-path)
(draft:ast-relative-location-path op num-anc))
(else
(draft:signal-semantic-error "improper LocationPath - " op))))
; {2} <AbsoluteLocationPath> ::= (absolute-location-path <Step>* )
(define (draft:ast-absolute-location-path op num-anc)
(cond
((not (eq? (car op) 'absolute-location-path))
(draft:signal-semantic-error "not an AbsoluteLocationPath - " op))
((null? (cdr op)) ; no Steps
(cons
(lambda (nodeset position+size var-binding)
(draft:reach-root nodeset))
#f))
(else
(and-let*
((steps-res (draft:ast-step-list (cdr op) num-anc)))
(cons
(if
(null? (cdar steps-res)) ; only a single step
(let ((step-impl (caar steps-res)))
(lambda (nodeset position+size var-binding)
(step-impl
(draft:reach-root nodeset) position+size var-binding)))
(let ((converters (car steps-res)))
(lambda (nodeset position+size var-binding)
(let rpt ((nset (draft:reach-root nodeset))
(fs converters))
(if (null? fs)
nset
(rpt ((car fs) nset position+size var-binding)
(cdr fs)))))))
#f)))))
; {3} <RelativeLocationPath> ::= (relative-location-path <Step>+ )
(define (draft:ast-relative-location-path op num-anc)
(if
(not (eq? (car op) 'relative-location-path))
(draft:signal-semantic-error "not a RelativeLocationPath - " op)
(and-let*
((steps-res (draft:ast-step-list (cdr op) num-anc)))
(cons
(if
(null? (cdar steps-res)) ; only a single step
(caar steps-res)
(let ((converters (car steps-res)))
(lambda (nodeset position+size var-binding)
(let rpt ((nset nodeset)
(fs converters))
(if (null? fs)
nset
(rpt ((car fs) nset position+size var-binding)
(cdr fs)))))))
(cdr steps-res)))))
; {4} <Step> ::= (step <AxisSpecifier> <NodeTest> <Predicate>* )
; | (range-to (expr <Expr>) <Predicate>* )
(define (draft:ast-step op num-anc)
(cond
((eq? (car op) 'range-to)
(draft:signal-semantic-error "range-to function not implemented"))
((eq? (car op) 'filter-expr) ; can be produced by sxpath
(draft:ast-filter-expr op num-anc))
((eq? (car op) 'lambda-step) ; created by sxpath
(cons
(let ((proc (cadr op)))
(if
(and num-anc (zero? num-anc)) ; no ancestors required
(lambda (node position+size var-binding)
(proc (draft:contextset->nodeset (as-nodeset node))
var-binding))
(lambda (node position+size var-binding)
(draft:find-proper-context
(proc (draft:contextset->nodeset (as-nodeset node))
var-binding)
(append
(map sxml:context->content (as-nodeset node))
(apply append ; nodes that can be obtained through var values
(map
(lambda (pair)
(if (nodeset? (cdr pair))
(map sxml:context->content (cdr pair))
'()))
var-binding)))
num-anc))))
num-anc))
((eq? (car op) 'step)
(if
(null? (cdddr op)) ; no Predicates
(and-let*
((axis-lst (draft:ast-axis-specifier (cadr op) num-anc))
(ntest (draft:ast-node-test (caddr op))))
(let ((axis ((car axis-lst) ntest num-anc)))
(cons
(if (caddr axis-lst) ; var-binding is to be passed
(lambda (nodeset position+size var-binding)
(axis nodeset var-binding))
(lambda (nodeset position+size var-binding)
(axis nodeset)))
(cadr axis-lst))))
(and-let*
((preds-res (draft:ast-predicate-list (cdddr op) 0))
(axis-lst (draft:ast-axis-specifier
(cadr op) (draft:na-max num-anc (cdr preds-res))))
(ntest (draft:ast-node-test (caddr op))))
(let ((axis ((car axis-lst)
ntest (draft:na-max num-anc (cdr preds-res))))
(pred-impl-lst (car preds-res)))
(cons
(if
(caddr axis-lst) ; variables are to be passed to the axis
(lambda (nodeset position+size var-binding)
(map-union
(lambda (node)
(let loop ((nset (axis node var-binding))
(preds pred-impl-lst))
(if
(null? preds)
nset
(loop ((car preds) nset position+size var-binding)
(cdr preds)))))
nodeset))
(lambda (nodeset position+size var-binding)
(map-union
(lambda (node)
(let loop ((nset (axis node))
(preds pred-impl-lst))
(if
(null? preds)
nset
(loop ((car preds) nset position+size var-binding)
(cdr preds)))))
nodeset)))
(cadr axis-lst))))))
(else
(draft:signal-semantic-error "not a Step - " op))))
; {4a} ( <Step>+ )
; Returns (cons (listof step-impl) num-anc) or #f
(define (draft:ast-step-list step-lst num-anc)
(let loop ((steps-to-view (reverse step-lst))
(res-lst '())
(num-anc num-anc))
(if
(null? steps-to-view) ; everyone processed
(cons res-lst num-anc)
(and-let*
((step-res (draft:ast-step (car steps-to-view) num-anc)))
(loop
(cdr steps-to-view)
(cons (car step-res) res-lst)
(cdr step-res))))))
; {8} <Predicate> ::= (predicate <Expr> )
; NOTE: num-anc is dummy here, since it is always 0 for Predicates
(define (draft:ast-predicate op num-anc)
(if
(not (eq? (car op) 'predicate))
(draft:signal-semantic-error "not an Predicate - " op)
(and-let*
((expr-res (draft:ast-expr (cadr op) 0)))
(let ((pred (car expr-res)))
(cons
(lambda (nodeset position+size var-binding)
(if
(null? nodeset) ; already empty
nodeset ; nothing to filter
(let ((size (length nodeset))) ; context size
(let loop ((nset nodeset)
(res '())
(pos 1))
(if
(null? nset)
(reverse res)
(let ((value (pred (list (car nset))
(cons pos size)
var-binding)))
(loop (cdr nset)
(if (if (number? value)
(= value pos)
(sxml:boolean value))
(cons (car nset) res)
res)
(+ pos 1))))))))
(cdr expr-res))))))
; {8a} ( <Predicate>+ )
; Returns (cons (listof pred-impl) num-anc) or #f
; NOTE: num-anc is dummy here, since it is always 0 for Predicates
(define (draft:ast-predicate-list op-lst num-anc)
(let ((pred-res-lst
(map
(lambda (op) (draft:ast-predicate op 0))
op-lst)))
(if
(member #f pred-res-lst) ; error detected
#f
(cons
(map car pred-res-lst)
(apply draft:na-max (map cdr pred-res-lst))))))
; {9} <Expr> ::= <OrExpr>
; | <AndExpr>
; | <EqualityExpr>
; | <RelationalExpr>
; | <AdditiveExpr>
; | <MultiplicativeExpr>
; | <UnionExpr>
; | <PathExpr>
; | <FilterExpr>
; | <VariableReference>
; | <Literal>
; | <Number>
; | <FunctionCall>
; | <LocationPath>
(define (draft:ast-expr op num-anc)
(case (car op)
((or)
(draft:ast-or-expr op num-anc))
((and)
(draft:ast-and-expr op num-anc))
((= !=)
(draft:ast-equality-expr op num-anc))
((< > <= >=)
(draft:ast-relational-expr op num-anc))
((+ -)
(draft:ast-additive-expr op num-anc))
((* div mod)
(draft:ast-multiplicative-expr op num-anc))
((union-expr)
(draft:ast-union-expr op num-anc))
((path-expr)
(draft:ast-path-expr op num-anc))
((filter-expr)
(draft:ast-filter-expr op num-anc))
((variable-reference)
(draft:ast-variable-reference op num-anc))
((literal)
(draft:ast-literal op num-anc))
((number)
(draft:ast-number op num-anc))
((function-call)
(draft:ast-function-call op num-anc))
((absolute-location-path)
(draft:ast-absolute-location-path op num-anc))
((relative-location-path)
(draft:ast-relative-location-path op num-anc))
(else
(draft:signal-semantic-error "unknown Expr - " op))))
; {10} <OrExpr> ::= (or <Expr> <Expr>+ )
; NOTE: num-anc is dummy here, since it is always 0 for OrExpr
(define (draft:ast-or-expr op num-anc)
(let ((expr-res-lst
(map
(lambda (expr) (draft:ast-expr expr 0))
(cdr op))))
(if
(member #f expr-res-lst) ; error detected
#f
(let ((expr-impls (map car expr-res-lst)))
(cons
(lambda (nodeset position+size var-binding)
(let rpt ((fs expr-impls))
(cond
((null? fs) #f)
((sxml:boolean ((car fs) nodeset position+size var-binding)) #t)
(else (rpt (cdr fs))))))
(apply draft:na-max (map cdr expr-res-lst)))))))
; {11} <AndExpr> ::= (and <Expr> <Expr>+ )
; NOTE: num-anc is dummy here, since it is always 0 for AndExpr
(define (draft:ast-and-expr op num-anc)
(let ((expr-res-lst
(map
(lambda (expr) (draft:ast-expr expr 0))
(cdr op))))
(if
(member #f expr-res-lst) ; error detected
#f
(let ((expr-impls (map car expr-res-lst)))
(cons
(lambda (nodeset position+size var-binding)
(let rpt ((fs expr-impls))
(cond
((null? fs) #t)
((not
(sxml:boolean ((car fs) nodeset position+size var-binding)))
#f)
(else (rpt (cdr fs))))))
(apply draft:na-max (map cdr expr-res-lst)))))))
; {12} <EqualityExpr> ::= (= <Expr> <Expr> )
; | (!= <Expr> <Expr> )
; NOTE: num-anc is dummy here, since it is always 0 for EqualityExpr
(define (draft:ast-equality-expr op num-anc)
(and-let*
((left-lst (draft:ast-expr (cadr op) 0))
(right-lst (draft:ast-expr (caddr op) 0)))
(let ((cmp-op (cadr (assq (car op) `((= ,sxml:equal?)
(!= ,sxml:not-equal?)))))
(left (car left-lst))
(right (car right-lst)))
(cons
(lambda (nodeset position+size var-binding)
(cmp-op
(draft:contextset->nodeset
(left nodeset position+size var-binding))
(draft:contextset->nodeset
(right nodeset position+size var-binding))))
(draft:na-max (cdr left-lst) (cdr right-lst))))))
; {13} <RelationalExpr> ::= (< <Expr> <Expr> )
; | (> <Expr> <Expr> )
; | (<= <Expr> <Expr> )
; | (>= <Expr> <Expr> )
; NOTE: num-anc is dummy here, since it is always 0 for RelationalExpr
(define (draft:ast-relational-expr op num-anc)
(and-let*
((left-lst (draft:ast-expr (cadr op) 0))
(right-lst (draft:ast-expr (caddr op) 0)))
(let ((cmp-op
(sxml:relational-cmp
(cadr (assq (car op) `((< ,<) (> ,>) (<= ,<=) (>= ,>=))))))
(left (car left-lst))
(right (car right-lst)))
(cons
(lambda (nodeset position+size var-binding)
(cmp-op
(draft:contextset->nodeset
(left nodeset position+size var-binding))
(draft:contextset->nodeset
(right nodeset position+size var-binding))))
(draft:na-max (cdr left-lst) (cdr right-lst))))))
; {14} <AdditiveExpr> ::= (+ <Expr> <Expr> )
; | (- <Expr> <Expr>? )
; NOTE: num-anc is dummy here, since it is always 0 for AdditiveExpr
(define (draft:ast-additive-expr op num-anc)
(let ((expr-res-lst
(map
(lambda (expr) (draft:ast-expr expr 0))
(cdr op))))
(if
(member #f expr-res-lst) ; error detected
#f
(let ((add-op (cadr (assq (car op) `((+ ,+) (- ,-)))))
(expr-impls (map car expr-res-lst)))
(cons
(lambda (nodeset position+size var-binding)
(apply
add-op
(map
(lambda (expr)
(sxml:number
(draft:contextset->nodeset
(expr nodeset position+size var-binding))))
expr-impls)))
(apply draft:na-max (map cdr expr-res-lst)))))))
; {15} <MultiplicativeExpr> ::= (* <Expr> <Expr> )
; | (div <Expr> <Expr> )
; | (mod <Expr> <Expr> )
; NOTE: num-anc is dummy here, since it is always 0 for MultiplicativeExpr
(define (draft:ast-multiplicative-expr op num-anc)
(and-let*
((left-lst (draft:ast-expr (cadr op) 0))
(right-lst (draft:ast-expr (caddr op) 0)))
(let ((mul-op
(cadr (assq (car op) `((* ,*) (div ,/) (mod ,remainder)))))
(left (car left-lst))
(right (car right-lst)))
(cons
(lambda (nodeset position+size var-binding)
(mul-op
(sxml:number
(draft:contextset->nodeset
(left nodeset position+size var-binding)))
(sxml:number
(draft:contextset->nodeset
(right nodeset position+size var-binding)))))
(draft:na-max (cdr left-lst) (cdr right-lst))))))
; {16} <UnionExpr> ::= (union-expr <Expr> <Expr>+ )
(define (draft:ast-union-expr op num-anc)
(let ((expr-res-lst
(map
(lambda (expr) (draft:ast-expr expr 0))
(cdr op))))
(if
(member #f expr-res-lst) ; error detected
#f
(let ((expr-impls (map car expr-res-lst)))
(cons
(lambda (nodeset position+size var-binding)
(let rpt ((res '())
(fs expr-impls))
(if
(null? fs)
res
(let ((nset ((car fs) nodeset position+size var-binding)))
(rpt
(append
res
(cond
((not (nodeset? nset))
(sxml:xpointer-runtime-error
"expected - nodeset instead of " nset)
'())
(else nset)))
(cdr fs))))))
(apply draft:na-max (map cdr expr-res-lst)))))))
; {17} <PathExpr> ::= (path-expr <FilterExpr> <Step>+ )
(define (draft:ast-path-expr op num-anc)
(and-let*
((steps-res (draft:ast-step-list (cddr op) num-anc))
(filter-lst (draft:ast-filter-expr (cadr op) (cdr steps-res))))
(let ((init-impl (car filter-lst))
(converters (car steps-res)))
(cons
(lambda (nodeset position+size var-binding)
(let ((nset
(init-impl nodeset position+size var-binding)))
(let rpt ((nset
(cond
((nodeset? nset) nset)
(else
(sxml:xpointer-runtime-error
"expected - nodeset instead of " nset)
'())))
(fs converters))
(if (null? fs)
nset
(rpt ((car fs) nset position+size var-binding)
(cdr fs))))))
(cdr filter-lst)))))
; {18} <FilterExpr> ::= (filter-expr (primary-expr <Expr> )
; <Predicate>* )
(define (draft:ast-filter-expr op num-anc)
(cond
((not (eq? (car op) 'filter-expr))
(draft:signal-semantic-error "not an FilterExpr - " op))
((not (eq? (caadr op) 'primary-expr))
(draft:signal-semantic-error "not an PrimaryExpr - " (cadr op)))
((null? (cddr op)) ; no Predicates
(draft:ast-expr (cadadr op) num-anc))
(else
(and-let*
((preds-res (draft:ast-predicate-list (cddr op) 0))
(expr-lst (draft:ast-expr
(cadadr op) (draft:na-max num-anc (cdr preds-res)))))
(let ((expr-impl (car expr-lst))
(pred-impl-lst (car preds-res)))
(cons
(lambda (nodeset position+size var-binding)
(let ((prim-res (expr-impl nodeset position+size var-binding)))
(let loop ((nset (cond
((nodeset? prim-res) prim-res)
(else
(sxml:xpointer-runtime-error
"expected - nodeset instead of " prim-res)
'())))
(preds pred-impl-lst))
(if
(null? preds)
nset
(loop ((car preds) nset position+size var-binding)
(cdr preds))))))
(cdr expr-lst)))))))
; {19} <VariableReference> ::= (variable-reference <String> )
(define (draft:ast-variable-reference op num-anc)
(let ((name (string->symbol (cadr op))))
(cons
(lambda (nodeset position+size var-binding)
(cond
((assoc name var-binding)
=> cdr)
(else
(sxml:xpointer-runtime-error "unbound variable - " name)
'())))
0)))
; {20} <Literal> ::= (literal <String> )
(define (draft:ast-literal op num-anc)
(let ((literal (cadr op)))
(cons
(lambda (nodeset position+size var-binding) literal)
0)))
; {21} <Number> :: (number <Number> )
(define (draft:ast-number op num-anc)
(let ((number (cadr op)))
(cons
(lambda (nodeset position+size var-binding) number)
0)))
; {22} <FunctionCall> ::= (function-call (function-name <String> )
; (argument <Expr> )* )
(define (draft:ast-function-call op num-anc)
(let ((core-alist
; (list fun-name min-num-args max-num-args na4res impl)
`((last 0 0 0 ,draft:core-last)
(position 0 0 0 ,draft:core-position)
(count 1 1 0 ,draft:core-count)
(id 1 1 #f ,draft:core-id)
(local-name 0 1 0 ,draft:core-local-name)
(namespace-uri 0 1 0 ,draft:core-namespace-uri)
(name 0 1 0 ,draft:core-name)
(string 0 1 0 ,draft:core-string)
(concat 2 -1 0 ,draft:core-concat)
(starts-with 2 2 0 ,draft:core-starts-with)
(contains 2 2 0 ,draft:core-contains)
(substring-before 2 2 0 ,draft:core-substring-before)
(substring-after 2 2 0 ,draft:core-substring-after)
(substring 2 3 0 ,draft:core-substring)
(string-length 0 1 0 ,draft:core-string-length)
(normalize-space 0 1 0 ,draft:core-normalize-space)
(translate 3 3 0 ,draft:core-translate)
(boolean 1 1 0 ,draft:core-boolean)
(not 1 1 0 ,draft:core-not)
(true 0 0 0 ,draft:core-true)
(false 0 0 0 ,draft:core-false)
(lang 1 1 #f ,draft:core-lang)
(number 0 1 0 ,draft:core-number)
(sum 1 1 0 ,draft:core-sum)
(floor 1 1 0 ,draft:core-floor)
(ceiling 1 1 0 ,draft:core-ceiling)
(round 1 1 0 ,draft:core-round))))
(cond
((not (eq? (caadr op) 'function-name))
(draft:signal-semantic-error "not an FunctionName - " (cadr op)))
((assq (string->symbol (cadadr op)) core-alist)
=> (lambda (description) ; Core function found
(cond
((< (length (cddr op)) (cadr description))
(draft:signal-semantic-error
"too few arguments for the Core Function call - "
(cadadr op)))
((and (>= (caddr description) 0)
(> (length (cddr op)) (caddr description)))
(draft:signal-semantic-error
"too many arguments for the Core Function call - "
(cadadr op)))
(else ; correct number of arguments
(and-let*
((args-impl (draft:ast-function-arguments (cddr op))))
(cons
; Producing a function implementation
(apply (list-ref description 4) num-anc args-impl)
(list-ref description 3)))))))
(else ; function definition not found
(draft:signal-semantic-error
"function call to an unknown function - " (cadadr op))))))
; {22a} ( (argument <Expr> )* )
; na-lst - number of ancestors required for each of the arguments
; Returns: (listof expr-impl) or #f
(define (draft:ast-function-arguments op-lst)
(let ((arg-res-lst
(map
(lambda (op)
(if
(not (eq? (car op) 'argument))
(draft:signal-semantic-error "not an Argument - " op)
(draft:ast-expr (cadr op) 0)))
op-lst)))
(if
(member #f arg-res-lst) ; semantic error detected
#f
(map car arg-res-lst))))
;-------------------------------------------------
; Section dedicated to XPointer AST
; {25} <XPointer> ::= <ChildSeq>
; | <FullXPtr>
; | <Expr>
(define (draft:ast-xpointer op num-anc)
(case (car op)
((child-seq)
(draft:ast-child-seq op num-anc))
((full-xptr)
(draft:ast-full-xptr op num-anc))
(else
(draft:ast-expr op num-anc))))
; {26} <ChildSeq> ::= (child-seq (name <String> ))
; | (child-seq (name <String> )?
; (number <Number> )+ )
(define (draft:ast-child-seq op num-anc)
(if
(eq? (caadr op) 'name)
(and-let*
((numbers-res (draft:ast-number-list (cddr op) num-anc)))
(let ((id-value (cadadr op))
(converters (car numbers-res))
(num-ancestors (cdr numbers-res)))
(cons
(lambda (nodeset position+size var-binding)
(let* ((root-node (draft:reach-root nodeset))
(id-nset ((sxml:child (ntype?? 'id-index))
((sxml:child (ntype?? '@@)) root-node))))
(if
(null? id-nset) ; no id-index
'()
(let ((nd (sxml:lookup id-value (cdar id-nset))))
(if (not nd)
'()
(let rpt ((nset
(if (and num-ancestors (zero? num-ancestors))
(list nd)
(draft:recover-contextset
(list nd) root-node num-ancestors)))
(fs converters))
(if (null? fs)
nset
(rpt ((car fs) nset) (cdr fs)))))))))
#f)))
(and-let*
((numbers-res (draft:ast-number-list (cdr op) num-anc)))
(let ((converters (car numbers-res)))
(cons
(lambda (nodeset position+size var-binding)
(let ((child-seq-impl
(lambda (node)
(let rpt ((nset nodeset) (fs converters))
(if (null? fs)
nset
(rpt ((car fs) nset) (cdr fs)))))))
(if (nodeset? nodeset)
(map-union child-seq-impl nodeset)
(child-seq-impl nodeset))))
(cdr numbers-res))))))
; {26a} ( (number <Number> )+ )
; Returns (cons (listof sxpath-converter) num-anc) or #f
(define (draft:ast-number-list number-lst num-anc)
(let loop ((to-view (reverse number-lst))
(res-lst '())
(num-anc num-anc))
(cond
((null? to-view) ; everyone processed
(cons res-lst num-anc))
((not (eq? (caar to-view) 'number))
(draft:signal-semantic-error "not an Number - " (car to-view)))
(else
(loop
(cdr to-view)
(cons (draft:child (ntype?? '*) num-anc)
(cons (node-pos (cadar to-view))
res-lst))
(draft:na-minus-nneg num-anc 1))))))
; {27} <FullXPtr> ::= (full-xptr <Expr> <Expr>+ )
(define (draft:ast-full-xptr op num-anc)
(let ((expr-res-lst
(map
(lambda (expr) (draft:ast-expr expr 0))
(cdr op))))
(if
(member #f expr-res-lst) ; error detected
#f
(let ((expr-impls (map car expr-res-lst)))
(cons
(lambda (nodeset position+size var-binding)
(let rpt ((fs expr-impls))
(if (null? fs)
'()
(let ((nset ((car fs) nodeset position+size var-binding)))
(if (null? nset)
(rpt (cdr fs))
nset)))))
(apply draft:na-max (map cdr expr-res-lst)))))))
;=========================================================================
; Highest level API functions
; xpath-string - an XPath location path (a string)
; ns+na - can contain 'ns-binding' and/or 'num-ancestors' and/or none of them
; ns-binding - declared namespace prefixes (an optional argument)
; ns-binding ::= (listof (prefix . uri))
; prefix - a symbol
; uri - a string
; num-ancestors - number of ancestors required for resulting nodeset. Can
; generally be omitted and is than defaulted to 0, which denotes a _usual_
; nodeset. If a negative number, this signals that all ancestors should be
; remembered in the context
;
; Returns: (lambda (nodeset position+size var-binding) ...)
; position+size - the same to what was called 'context' in TXPath-1
; var-binding - XPath variable bindings (an optional argument)
; var-binding = (listof (var-name . value))
; var-name - (a symbol) a name of a variable
; value - its value. The value can have the following type: boolean, number,
; string, nodeset. NOTE: a node must be represented as a singleton nodeset
; Given a list of arguments, returns
; (values ns-binding num-anc)
(define (draft:arglist->ns+na arglst)
(let loop ((arglst arglst)
(ns-binding '())
(num-anc 0))
(cond
((null? arglst) (values ns-binding num-anc))
((pair? (car arglst))
(loop (cdr arglst) (car arglst) num-anc))
((number? (car arglst))
(loop (cdr arglst) ns-binding
(if (negative? (car arglst)) #f (car arglst))))
(else
(loop (cdr arglst) ns-binding num-anc)))))
; Helper for constructing several highest-level API functions
(define (draft:api-helper grammar-parser ast-parser)
(lambda (xpath-string . ns+na)
(call-with-values
(lambda () (draft:arglist->ns+na ns+na))
(lambda (ns-binding num-anc)
(and-let*
((ast (grammar-parser xpath-string ns-binding))
(impl-lst (ast-parser ast num-anc)))
(let ((res (car impl-lst)))
(lambda (node . var-binding)
((if (and num-anc (zero? num-anc))
draft:contextset->nodeset
(lambda (x) x))
(res (as-nodeset node) (cons 1 1)
;(xlink:add-docs-to-vars
; node
(if (null? var-binding)
var-binding (car var-binding))
; )
)))))))))
(define draft:xpath (draft:api-helper txp:xpath->ast draft:ast-location-path))
(define draft:xpointer (draft:api-helper txp:xpointer->ast draft:ast-xpointer))
(define draft:xpath-expr (draft:api-helper txp:expr->ast draft:ast-expr))
(define draft:sxpath (draft:api-helper txp:sxpath->ast draft:ast-expr))
; Aliases
(define txpath-with-context draft:xpath)
(define txpath/c draft:xpath)
(define sxpath-with-context draft:sxpath)
(define sxpath/c draft:sxpath)
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
; Automatically united by Module Manager
; Source filename: ../Ssax-sxml/sxml-tools/xpath-context.scm
;; XLink implementation and the API for XLink processing in Scheme
;
; This software is in Public Domain.
; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
;
; Please send bug reports and comments to:
; lisovsky@acm.org Kirill Lisovsky
; lizorkin@hotbox.ru Dmitry Lizorkin
;
; doc ::= '(*TOP*
; (@@
; (sxlink
; (declared-here <sxlink-arc>* )
; (embedded)?
; (outgoing
; (node <sxlink-arc>+ )*
; )
; )
; ... ; more aux list members
; )
; ...)
;==========================================================================
; XLink-related node tests
; They test whether an SXML node has a definite XLink type
; ATTENTION:
; 1. A non-prefixed XLink namespace uri is used for these node tests. If
; a prefix is used, these functions behave incorrectly.
; 2. These predicates should be used carefully - element's XLink-related
; meaning depends not only on its xlink:type attribute, but also on its
; position among other XLink element. For example, an element with an
; xlink:type="arc" attribute is not an arc element if it has anything other
; then an extended-link element as a parent
; Helper for predicates
; type - a string, is supposed to have one of the following values:
; "extended", "simple", "locator", "resource", "arc", "title".
; A lambda is returned. When applied to an SXML node, it determines
; whether the node's xlink:type attribute has a 'type' value.
(define (xlink:ntype?? type)
(lambda (node)
(let ((attval
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'http://www.w3.org/1999/xlink:type))
((select-kids (ntype?? '@)) node)))))
(if (null? attval) ; there is no xlink:type attribute
#f
(string=? (car attval) type)))))
; Node tests for different XLink elements
(define xlink:elem-extended? (xlink:ntype?? "extended"))
(define xlink:elem-simple? (xlink:ntype?? "simple"))
(define xlink:elem-locator? (xlink:ntype?? "locator"))
(define xlink:elem-resource? (xlink:ntype?? "resource"))
(define xlink:elem-arc? (xlink:ntype?? "arc"))
(define xlink:elem-title? (xlink:ntype?? "title"))
;==========================================================================
; Utility functions over document auxiliary information
;-------------------------------------------------
; Document's URI
; The following functions moved to "xlink-parser.scm"
; xlink:get-uri
; xlink:set-uri-for-sxlink-arcs
; Sets the URI for the SXML document
(define (xlink:set-uri uri doc)
(let ((aux-nset ((select-kids (ntype?? '@@)) doc)))
(if
(or (null? aux-nset) ; no aux node at all yet
; no sxlink/declared-here subnode
(null? ((select-kids (ntype?? 'declared-here))
((select-kids (ntype?? 'sxlink)) (car aux-nset)))))
(xlink:replace-branch ; inserts the @@/uri node in the document
doc '(@@ uri) (list uri))
(xlink:replace-branch
doc
'(@@)
(cdr
((xlink:branch-helper ; inserts URI to sxlink-arcs
(lambda (declared-here-node dummy)
(cons
(car declared-here-node)
(xlink:set-uri-for-sxlink-arcs
uri (cdr declared-here-node)))))
(xlink:replace-branch ; inserts (modified) URI
(car aux-nset) '(uri) (list uri))
'(sxlink declared-here)
'() ; dummy
))))))
;-------------------------------------------------
; Id-index of the document
; Returns the id-index of the SXML document
; #f is returned is there is no "@@/id-index" subtree in the document
(define (xlink:id-index doc)
(let ((nodeset ((select-kids (ntype?? 'id-index))
((select-kids (ntype?? '@@)) doc))))
(if (null? nodeset) ; there is no "@@/id-index" subtree
#f
(cdar nodeset))))
;-------------------------------------------------
; SXLink members of the auxiliary list
; Returns (listof sxlink-arc) located in "@@/sxlink/declared-here"
; These are SXLink arcs that are declared in this document
(define (xlink:arcs-declared-here doc)
((select-kids (ntype?? '*any*))
((select-kids (ntype?? 'declared-here))
((select-kids (ntype?? 'sxlink))
((select-kids (ntype?? '@@)) doc)))))
; Whether outgoing SXLink arcs are embedded into the document.
; This is denoted by the presense of "@@/sxlink/embedded" empty element.
(define (xlink:arcs-embedded? doc)
(not (null? ((select-kids (ntype?? 'embedded))
((select-kids (ntype?? 'sxlink))
((select-kids (ntype?? '@@)) doc))))))
; Returns the content of "@@/sxlink/outgoing"
; The result is the associative list between nodes of the document and
; SXLink arcs that start from the corresponding node
(define (xlink:arcs-outgoing doc)
((select-kids (ntype?? '*any*))
((select-kids (ntype?? 'outgoing))
((select-kids (ntype?? 'sxlink))
((select-kids (ntype?? '@@)) doc)))))
;==========================================================================
; Get the document by its URI
; Handler for error messages
(define (xlink:api-error . text)
(cerr "XLink API error: ")
(apply cerr text)
(cerr nl))
; Id+XLink parser parameterized
(define xlink:parser (ssax:multi-parser 'id 'xlink))
; Returns the SXML representation for the resource specified by REQ-URI.
; Resource types supported: XML and HTML. XML is parsed into SXML with SSAX,
; HTML is parsed with HTML Prag.
; Additionally, linking information is parsed. For XML, linking information is
; assumed to be specified with XLink. For HTML, <a> elements are treated as
; simple links.
; In case of an error (resource doesn't exist or its type is unsupported), an
; error is signalled with 'xlink:api-error' and #f is returned.
(define (xlink:get-document-by-uri req-uri)
(case (ar:resource-type req-uri)
((#f) ; resource doesn't exist
(xlink:api-error "resource doesn't exist: " req-uri)
#f)
((xml plain unknown)
(let* ((port (open-input-resource req-uri))
(doc (xlink:parser port)))
(close-input-port port)
(xlink:set-uri req-uri doc)))
((html)
(let* ((port (open-input-resource req-uri))
(doc (html->sxml port)))
(close-input-port port)
(SHTML->SHTML+xlink
(xlink:set-uri req-uri doc))))
(else ; unknown resource type
(xlink:api-error "resource type not supported: " req-uri)
#f)))
;==========================================================================
; Loading multiple documents by their URIs
;-------------------------------------------------
; Helper accessors to SXLink arcs
; Returns URIs of resources that participate in SXLink arcs
; sxlink-arcs ::= (listof sxlink-arc)
; Result: (listof string)
; The result may contain duplicates
(define (xlink:arcs-uris sxlink-arcs)
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri))
((select-kids (ntype-names?? '(from to))) sxlink-arcs))))
; Returns URIs of all linkbases encountered among SXLink arcs
; Result: (listof string)
; The result may contain duplicates
(define (xlink:arcs-linkbase-uris sxlink-arcs)
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri))
((select-kids (ntype?? 'to))
(filter (ntype?? 'linkbase) sxlink-arcs)))))
;-------------------------------------------------
; Working on the set of SXML documents
; doc-set ::= (listof document)
; Returns the list of URIs of the documents in the doc-set
(define (xlink:uris doc-set)
(filter
(lambda (x) x)
(map xlink:get-uri doc-set)))
; Removes equal duplicates from the list
(define (xlink:remove-equal-duplicates lst)
(cond
((null? lst) lst)
((member (car lst) (cdr lst))
(xlink:remove-equal-duplicates (cdr lst)))
(else
(cons (car lst) (xlink:remove-equal-duplicates (cdr lst))))))
; procedure xlink:find-doc :: URI-STRING (listof SXML-TREE) -> SXML-TREE
;
; Finding a document in 'doc-set' by its 'uri-string'.
; If there is no such document, #f is returned.
; doc-set ::= (listof SXML-TREE)
(define (xlink:find-doc uri-string doc-set)
(let loop ((doc-set doc-set))
(cond
((null? doc-set) #f)
((equal? (xlink:get-uri (car doc-set)) uri-string)
(car doc-set))
(else (loop (cdr doc-set))))))
;-------------------------------------------------
; Extending the set of documents with additional documents being referred to
; Returns a list of URIs which are refered by XLink markup
; Result: (listof string)
; The list may contain duplicates.
(define (xlink:referenced-uris doc-set)
(apply append
(map
(lambda (doc)
(xlink:arcs-uris (xlink:arcs-declared-here doc)))
doc-set)))
; Returns a list of linkbase URIs which are refered by XLink markup
; Result: (listof string)
; The list may contain duplicates.
(define (xlink:referenced-linkbase-uris doc-set)
(apply append
(map
(lambda (doc)
(xlink:arcs-linkbase-uris (xlink:arcs-declared-here doc)))
doc-set)))
; A helped low-level function for extending the doc-set with more documents
; Is parameterized with
; referenced-uris ::= (lambda (doc-set) ...)
; that would return URIs refered by XLink markup in the doc-set
; When parameterized, returns
; (lambda (doc-set . max-steps) ...)
; max-steps - maximal number of recursive steps
; The lambda returns the expanded doc-set
(define (xlink:add-documents-helper referenced-uris)
(lambda (doc-set . max-steps)
(let ((max-steps (if (null? max-steps) -1 (car max-steps))))
(let loop ((doc-set doc-set)
(loaded-uris (xlink:uris doc-set))
(to-load (referenced-uris doc-set))
(step 0))
(if
(or (null? to-load) (= step max-steps))
doc-set
(let rpt ((loaded-uris loaded-uris)
(to-load to-load)
(added-docs '()))
(cond
((null? to-load)
(loop (append added-docs doc-set)
loaded-uris
(referenced-uris added-docs)
(+ step 1)))
((member (car to-load) loaded-uris)
(rpt loaded-uris
(cdr to-load)
added-docs))
(else ; we load the linkbase
(let ((doc (xlink:get-document-by-uri (car to-load))))
(rpt (cons (car to-load) loaded-uris)
(cdr to-load)
(if doc (cons doc added-docs) added-docs)))))))))))
; Two most common parameterized functions. The first one recursively loads
; linkbases. The second one recursively loads all refered documents
(define xlink:add-linkbases-recursively
(xlink:add-documents-helper xlink:referenced-linkbase-uris))
(define xlink:add-documents-recursively
(xlink:add-documents-helper xlink:referenced-uris))
;-------------------------------------------------
; Higher-level functions
; Parameterized with options, returns
; (lambda (uri . uris) ...)
; which is the lambda for getting documents by their URIs
; Options include the following:
; 'linkbases - load linkbases recursively
; '(linkbases <number> ) - load linkbases recursively, with the maximal
; number of recursive steps defined by the <number> supplied
; 'docs - load documents recursively
; '(docs <number> ) - load documents recursively, with the maximal number
; of recursive steps defined by the <number> supplied
(define (xlink:get-documents-with-params . options)
(let ((get-initial-docs ; Returns documents by their URIs
(lambda (uris)
(filter ; keeps only correctly loaded documents
(lambda (x) x)
(map xlink:get-document-by-uri
(xlink:remove-equal-duplicates uris)))))
(linkbases-pairs
(filter
(lambda (option) (and (pair? option) (eq? (car option) 'linkbases)))
options))
(docs-pairs
(filter
(lambda (option) (and (pair? option) (eq? (car option) 'docs)))
options)))
(let ((linkbases? (or (memq 'linkbases options)
(not (null? linkbases-pairs))))
(max-steps-linkbases (if (null? linkbases-pairs)
-1
(cadar linkbases-pairs)))
(documents? (or (memq 'docs options)
(not (null? docs-pairs))))
(max-steps-documents (if (null? docs-pairs)
-1
(cadar docs-pairs))))
(cond
((and linkbases? documents?)
(lambda (uri . uris)
(xlink:add-linkbases-recursively
(xlink:add-documents-recursively
(get-initial-docs (cons uri uris))
max-steps-documents)
max-steps-linkbases)))
(linkbases?
(lambda (uri . uris)
(xlink:add-linkbases-recursively
(get-initial-docs (cons uri uris))
max-steps-linkbases)))
(documents?
(lambda (uri . uris)
(xlink:add-documents-recursively
(get-initial-docs (cons uri uris))
max-steps-documents)))
(else ; nothing extra to be loaded
(lambda (uri . uris) (get-initial-docs (cons uri uris))))))))
; The most common parameterized case.
; Loads documents and all linkbases
(define xlink:get-documents+linkbases
(xlink:get-documents-with-params 'linkbases))
;==========================================================================
; Working on the set of linked documents
; linked-docs ::= (listof document)
; alist ::= (listof
; (cons key (listof item)))
; For equal keys in the alist, the function unites the corresponding key values
; Returns the new alist
(define (xlink:unite-duplicate-keys-in-alist alist)
(let loop ((src alist)
(res '()))
(if
(null? src)
res
(let ((curr-key (caar src)))
(let rpt ((scan (cdr src))
(content (cdar src))
(other '()))
(cond
((null? scan)
(loop other
(cons (cons curr-key content)
res)))
((equal? (caar scan) curr-key)
(rpt (cdr scan)
(append content (cdar scan))
other))
(else ; a different key
(rpt (cdr scan) content
(cons (car scan) other)))))))))
; Documents exchange their SXLink arcs, such as each arc is moved to the
; "@@/sxlink/outgoing" branch of the document where the arc's starting
; resource is
; Additional SXLink arcs may be specified in the optional argument.
(define (xlink:docs-exchange-arcs doc-set . sxlink-arcs)
(let ((doc-set-uris (xlink:uris doc-set))
(sxlink-arcs (if (null? sxlink-arcs) '() (car sxlink-arcs))))
; outgoing-alist ::= (listof
; (cons uri
; (listof (cons node (listof sxlink-arc)))))
; declared-here-alist ::= (listof
; (cons uri (listof sxlink-arc)))
(let loop ((outgoing-alist (map
(lambda (doc)
(cons
(xlink:get-uri doc)
(xlink:arcs-outgoing doc)))
doc-set))
(declared-here-alist (map list doc-set-uris))
(arcs-to-scan
(append sxlink-arcs
(apply append
(map xlink:arcs-declared-here doc-set)))))
(if
(null? arcs-to-scan) ; all arcs processed
(let ((outgoing-alist
(xlink:unite-duplicate-keys-in-alist outgoing-alist))
(declared-here-alist
(xlink:unite-duplicate-keys-in-alist declared-here-alist)))
(map
(lambda (doc)
(let ((uri (xlink:get-uri doc)))
(xlink:replace-branch
doc
'(@@ sxlink)
`((declared-here
,@(cdr (assoc uri declared-here-alist)))
,@(if (xlink:arcs-embedded? doc) '((embedded)) '())
(outgoing
,@(xlink:unite-duplicate-keys-in-alist
(cdr (assoc uri outgoing-alist))))))))
doc-set))
(let* ((curr-arc (car arcs-to-scan))
(uri-from (car ; URI must be presented
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri))
((select-kids (ntype?? 'from))
curr-arc)))))
(uri-decl (car ; URI must be presented
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri))
((select-kids (ntype?? 'declaration))
curr-arc))))))
(if
(not (member uri-from doc-set-uris))
; This arc starts from none of the documents from doc-set
(loop outgoing-alist
(cons (list uri-decl curr-arc) declared-here-alist)
(cdr arcs-to-scan))
(let ((nodes ; nodes that are the starting resource
(let ((nodes-nset
((select-kids (ntype?? 'nodes))
((select-kids (ntype?? 'from))
curr-arc))))
(if
(not (null? nodes-nset))
(cdar nodes-nset)
(let ((xpointer-nset
((select-kids (ntype?? 'xpointer))
((select-kids (ntype?? 'from)) curr-arc)))
(starting-doc (xlink:find-doc uri-from doc-set)))
(if
(null? xpointer-nset) ; no XPointer
((select-kids (ntype?? '*)) ; document element
starting-doc)
(let ((func (sxml:xpointer (cadar xpointer-nset))))
(if
(not func) ; parser error
#f
(let ((starting-nset (func starting-doc)))
(if
(nodeset? starting-nset)
starting-nset
#f))))))))))
(if
nodes ; starting resource selects some nodes
(loop
(cons (cons uri-from
(map
(lambda (node) (list node curr-arc))
nodes))
outgoing-alist)
declared-here-alist
(cdr arcs-to-scan))
(loop outgoing-alist
(cons (list uri-decl curr-arc) declared-here-alist)
(cdr arcs-to-scan))))))))))
;-------------------------------------------------
; Embedding XLink arcs into the document
; The element node with embedded XLink arcs looks as follows
; element-node ::= (name
; (@ ...)
; (@@
; (sxlink <sxlink-arc>+ )
; ...) ; other members of the aux list
; ...)
; attribute-node ::= (name "value"
; (@@
; (sxlink <sxlink-arc>+ )
; ...) ; other members of the aux list
; )
; Takes SXLink arcs outgoing from the document and embeds these arcs into
; element and attribute nodes of the document.
; The modified document is returned
; The function doesn't make a copy of nodes that remain unchanged
(define (xlink:embed-arcs-into-document document)
(letrec
(; These helper functions return
; (values node outgoing-alist changed?)
; node - the (modified) node
; outgoing-alist ::= (listof (cons node (listof sxlink-arc)))
; changed? - whether the node was changed
(process-element-node
(lambda (node outgoing-alist)
(cond
((or (not (pair? node))
(eq? (car node) '@@))
; Text node or aux node
(values node outgoing-alist #f))
((eq? (car node) '@)
(call-with-values
(lambda ()
((process-nodeset process-attribute-node)
(cdr node) outgoing-alist))
(lambda (content new-out-alist changed?)
(if changed?
(values (cons '@ content)
new-out-alist
changed?)
(values node outgoing-alist changed?)))))
(else ; this is the element node
(call-with-values
(lambda ()
(cond
((assq node outgoing-alist)
=> (lambda (alist-member)
(values
(cdr alist-member)
(filter
(lambda (memb) (not (eq? memb alist-member)))
outgoing-alist))))
(else ; the node is not the starting resource
(values #f outgoing-alist))))
(lambda (outgoing-arcs new-out-alist)
(call-with-values
(lambda () ((process-nodeset process-element-node)
(cdr node) new-out-alist))
(lambda (content new-out-alist changed?)
(cond
((not (or outgoing-arcs changed?))
; node remains unchanged
(values node outgoing-alist changed?))
((not outgoing-arcs) ; no arcs from that node
(values (cons (car node) content)
new-out-alist
changed?))
(else ; the node is the starting resource
(let ((new-content
(if changed? content (cdr node))))
(values
(cond
((not (null? ; aux list presented
((select-kids (ntype?? '@@)) new-content)))
(xlink:append-branch
(cons (car node) new-content)
'(@@ sxlink) outgoing-arcs))
(((ntype?? '@) ; attribute node presented
(car new-content))
`(,(car node)
,(car content) ; attribute node
(@@ (sxlink ,@outgoing-arcs))
,@(cdr content)))
(else ; no attribute node
`(,(car node)
(@)
(@@ (sxlink ,@outgoing-arcs))
,@content)))
new-out-alist
#t))))))))))))
(process-attribute-node
(lambda (node outgoing-alist)
(cond
((assq node outgoing-alist)
=> (lambda (alist-member)
(values
(if
(null? ; no aux node in the attribute
((select-kids (ntype?? '@@)) node))
(append node
`((@@
(sxlink ,@(cdr alist-member)))))
(xlink:append-branch
node '(@@ sxlink) (cdr alist-member)))
(filter
(lambda (memb) (not (eq? memb alist-member)))
outgoing-alist)
#t)))
(else ; the attribute node is not a starting resource
(values node outgoing-alist #f)))))
; Is parameterized with one of the previous functions and
; processes the nodeset
(process-nodeset
(lambda (processing-func)
(lambda (nodeset outgoing-alist)
(let loop ((nset nodeset)
(out-alist outgoing-alist)
(changed? #f)
(res '()))
(if
(null? nset) ; nodeset processed
(values (reverse res)
out-alist
changed?)
(call-with-values
(lambda () (processing-func (car nset) out-alist))
(lambda (new-node new-out-alist ch?)
(loop (cdr nset)
new-out-alist
(or changed? ch?)
(cons new-node res))))))))))
(call-with-values
(lambda () ((process-nodeset process-element-node)
(cdr document)
(xlink:arcs-outgoing document)))
(lambda (content new-out-alist changed?)
(if (not changed?) ; the document remains unchanged
(xlink:replace-branch
document '(@@ sxlink embedded) '())
(xlink:replace-branch
(cons '*TOP* content)
'(@@ sxlink)
`((declared-here ,@(xlink:arcs-declared-here document))
(embedded)
(outgoing ,@new-out-alist))))))))
; Returns all embedded SXLink arcs in the document
; Result: (listof sxlink-arc)
(define (xlink:arcs-embedded doc)
(let ((get-kids
(select-kids
(lambda (node) (and (pair? node) (not (eq? '@@ (car node))))))))
(let loop ((nodes-to-scan (get-kids doc))
(res '()))
(if
(null? nodes-to-scan) ; everyone processed
(draft:remove-eq-duplicates res)
(loop
(append (get-kids (car nodes-to-scan)) (cdr nodes-to-scan))
(append
((select-kids (ntype?? '*any*))
((select-kids (ntype?? 'sxlink))
((select-kids (ntype?? '@@)) (car nodes-to-scan))))
res))))))
;==========================================================================
; Load documents with respect to the other documents
; Parameterized with options, returns
; (lambda (linked-docs uri . uris) ...)
; which is the lambda for getting more documents by their URIs
; Options include the following:
; 'linkbases - load linkbases recursively
; '(linkbases <number> ) - load linkbases recursively, with the maximal
; number of recursive steps defined by the <number>
; supplied
; 'docs - load documents recursively
; '(docs <number> ) - load documents recursively, with the maximal number
; of recursive steps defined by the <number> supplied
; 'embed - embed SXLink arcs into nodes that are starting resources for that
; arcs
; 'no-embed - don't embed SXLink arcs into documents loaded
(define (xlink:parameterized-load-with-respect-documents . options)
(let ((doc-getter (apply xlink:get-documents-with-params options))
(embed? (memq 'embed options))
(no-embed? (memq 'no-embed options)))
(lambda (linked-docs . uris)
(let* ((loaded-uris (xlink:uris linked-docs))
(req-docs
(xlink:docs-exchange-arcs
(filter
(lambda (x) x)
(map
(lambda (uri)
(if
(member uri loaded-uris) ; document already loaded
(xlink:find-doc uri linked-docs)
(xlink:get-document-by-uri uri)))
(xlink:remove-equal-duplicates uris)))
(apply append (map xlink:arcs-declared-here linked-docs)))))
(cond
(no-embed? req-docs)
((or embed? ; embed arcs
(member #t (map xlink:arcs-embedded? linked-docs)))
(map xlink:embed-arcs-into-document req-docs))
(else req-docs))))))
; The most common case of parametrization
(define xlink:get-docs-with-respect-to-loaded
(xlink:parameterized-load-with-respect-documents 'linkbase))
;==========================================================================
; Excluding documents from linked-docs
; TODO: to be implemented later
; Returns all SXLink arcs encountered in the document. This envolves:
; a) declared here arcs,
; b) outgoing arcs, and
; c) embedded arcs
; Returns (listof sxlink-arcs)
;(define (xlink:arcs-all doc)
; Returns linked-docs
;(define (xlink:exclude-documents linked-docs uri . uris)
;==========================================================================
; High-level API functions
; Parameterized with options, returns
; (lambda (uri . uris) ...)
; which is the lambda for getting documents by their URIs
; Options include the following:
; 'linkbases - load linkbases recursively
; '(linkbases <number> ) - load linkbases recursively, with the maximal
; number of recursive steps defined by the <number>
; supplied
; 'docs - load documents recursively
; '(docs <number> ) - load documents recursively, with the maximal number
; of recursive steps defined by the <number> supplied
; 'embed - embed SXLink arcs into nodes that are starting resources for that
; arcs
(define (xlink:load-linked-docs-with-params . options)
(let ((doc-getter (apply xlink:get-documents-with-params options)))
(if
(memq 'embed options) ; embed
(lambda (uri . uris)
(map
xlink:embed-arcs-into-document
(xlink:docs-exchange-arcs (apply doc-getter (cons uri uris)))))
(lambda (uri . uris)
(xlink:docs-exchange-arcs (apply doc-getter (cons uri uris)))))))
; procedure xlink:documents :: {REQ-URI}+ -> (listof SXML-TREE)
; procedure xlink:documents-embed :: {REQ-URI}+ -> (listof SXML-TREE)
;
; Both `xlink:documents' and `xlink:documents-embed' accept one or more
; strings as their arguments. Each string supplied denotes the URI of the
; requested document to be loaded. The requested document(s) are loaded
; and are represented in SXML. All XLink links declared in these document(s)
; are represented as a set of SXLink arcs. If any XLink links refer to XLink
; linkbases [<a href="http://www.w3.org/TR/xlink/#xlg">XLink</a>],
; these linkbases are additionally loaded, for additional SXLink arcs
; declared there.
;
; The starting resource for each SXLink arc is determined:
; 1. For each SXML document loaded, the function `xlink:document' adds all
; SXLink arcs whose starting resource is located within this document, to
; the auxiliary list of its document node (*TOP*).
; 2. The function 'xlink:documents-embed' embeds each SXLink arc into its
; starting resource-node, via auxiliary list of that node. For text nodes
; serving for starting resources, their SXLink arcs are stored in the
; auxiliary list of the document node (*TOP*), since SXML text nodes do
; not support their own auxiliary lists.
;
; Supported URI formats:
; + local file
; + http:// schema
;
; Supported document formats: XML and HTML. In the case of HTML,
; <A> hyperlinks are considered as XLink simple links.
;
; Result: (listof SXML-TREE)
; A particular SXML document can be located in this list using the
; function `xlink:find-doc'.
(define xlink:documents
(xlink:load-linked-docs-with-params 'linkbases))
(define xlink:documents-embed
(xlink:load-linked-docs-with-params 'linkbases 'embed))
;-------------------------------------------------
; Convenient function for getting a document by its URI
; procedure sxml:document :: REQ-URI [NAMESPACE-PREFIX-ASSIG] ->
; -> SXML-TREE
;
; Obtain a [possibly, remote] document by its URI
; Supported URI formats: local file and HTTP schema
; Supported document formats: XML and HTML
;
; REQ-URI - a string that contains the URI of the requested document
; NAMESPACE-PREFIX-ASSIG - is passed as-is to the SSAX parser: there it is
; used for assigning certain user prefixes to certain namespaces.
; NAMESPACE-PREFIX-ASSIG is an optional argument and has an effect for an
; XML resource only. For an HTML resource requested, NAMESPACE-PREFIX-ASSIG
; is silently ignored.
;
; Result: the SXML representation for the requested document
(define (sxml:document req-uri . namespace-prefix-assig)
(if
(string? req-uri)
(case (ar:resource-type req-uri)
((#f) ; resource doesn't exist
(xlink:api-error "resource doesn't exist: " req-uri)
#f)
((xml plain unknown)
(let* ((port (open-input-resource req-uri))
(doc (ssax:xml->sxml
port
(if (null? namespace-prefix-assig)
namespace-prefix-assig
(car namespace-prefix-assig)))))
(close-input-port port)
doc ; DL: can also add URI: (xlink:set-uri req-uri doc)
))
((html)
(let* ((port (open-input-resource req-uri))
(doc (html->sxml port)))
(close-input-port port)
doc ; DL: can also add URI: (xlink:set-uri req-uri doc)
))
(else ; unknown resource type
(xlink:api-error "resource type not supported: " req-uri)
#f))
; Otherwise: REQ-URI is not a string - producing an exception
(exc:signal ; relies on SRFI-12
(make-property-condition
'exn
'message
"sxml:document: expects type <string> as 1st argument"))))
;==========================================================================
; SXPath-related stuff
; Whether an SXLink arc
(define xlink:arc?
(ntype-names??
'(linkbase simple outbound inbound third-party local-to-local)))
;-------------------------------------------------
; Working with the administrative variable '*docs*
; Returns the value of the administrative SXPath variable '*docs*
; This variable stores linked-docs
(define (xlink:docs-variable var-binding)
(cond
((assq '*docs* var-binding)
=> cdr)
(else '())))
; Extends var-bindings with administative information about linked-docs
; node - a single node or a nodeset
(define (xlink:add-docs-to-vars node var-binding)
(if (assq '*docs* var-binding) ; variable already exists
var-binding
(cons
(cons '*docs*
(filter
(lambda (doc)
(and (draft:top? doc) (xlink:get-uri doc)))
(draft:reach-root (as-nodeset node))))
var-binding)))
;-------------------------------------------------
; Accessors to SXLink arcs that start from the given SXML node
; Returns SXLink arcs that are embedded into the node as aux list members
; Result: (listof sxlink-arc)
(define (xlink:node-embedded-arcs node)
(if (draft:top? node) ; the root node
'() ; no embedded arcs
((select-kids (ntype?? '*any*))
((select-kids (ntype?? 'sxlink))
((select-kids (ntype?? '@@)) node)))))
; Returns SXLink arcs that are specified at the top-level of the document and
; start from node
(define (xlink:node-arcs-on-top node document)
(cond
((assq node (xlink:arcs-outgoing document))
=> cdr)
(else '())))
; Returns all SXLink arcs (both embedded and specified at the top-level) that
; start from ther node
; The union of the two previous functions
(define (xlink:node-arcs node document)
(append (xlink:node-embedded-arcs node)
(xlink:node-arcs-on-top node document)))
;-------------------------------------------------
; Traversing SXLink arcs
; Traverse all SXLink arcs to their ending resources
; sxlink-arcs ::= (listof sxlink-arc)
; linked-docs ::= (listof document)
; num-ancestors - number of ancestors required for ending resources
(define (xlink:traverse-arcs sxlink-arcs linked-docs num-ancestors)
(let* ((arcs-to
((select-kids (ntype?? 'to)) sxlink-arcs))
(req-docs
(apply
xlink:get-docs-with-respect-to-loaded
(cons
linked-docs
(if
(and num-ancestors (zero? num-ancestors))
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri))
(filter ; elements that have a <nodes> subelement
(lambda (arc-to)
(null? ((select-kids (ntype?? 'nodes)) arc-to)))
arcs-to)))
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri)) arcs-to)))))))
;(pp req-docs)
(map-union
(lambda (arc-to)
(let ((nodes-nset
((select-kids (ntype?? 'nodes)) arc-to)))
(if
(and num-ancestors (zero? num-ancestors)
(not (null? nodes-nset)))
(cadar nodes-nset)
; otherwise we need the document and the XPointer node
(let ((doc (xlink:find-doc
(car ((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'uri)) arc-to)))
req-docs))
(xpointer-nset
((select-kids (ntype?? '*text*))
((select-kids (ntype?? 'xpointer)) arc-to))))
;(pp doc)
;(display xpointer-nset)
;(newline)
(cond
((not doc) ; document couldn't be loaded
'())
((null? xpointer-nset)
; no XPointer part => addresses the document element
((draft:child (ntype?? '*) num-ancestors)
doc))
(else
(let ((impl
(draft:xpointer (car xpointer-nset)
(if num-ancestors num-ancestors -1))))
(if
(not impl) ; parser error
'()
(let ((res (impl doc)))
(if
(nodeset? res)
res
(begin
(xlink:api-error
"XPointer fragment identifier doesn't "
"select any nodeset: " (car xpointer-nset))
'())))))))))))
arcs-to)))
;-------------------------------------------------
; Additional XPath axes
; XPath+XLink arc axis
; This axis returns all SXLink arcs that start from the context node
; num-ancestors is dummy here, since SXLink arcs don't have ancestors
(define (xlink:axis-arc test-pred? . num-ancestors)
(let ((this-axis
(lambda (node) ; not a nodeset
(let ((root-node
(if (sxml:context? node)
(draft:list-last (sxml:context->ancestors-u node))
node)))
(if (draft:top? root-node)
(xlink:node-arcs (sxml:context->node node) root-node)
(xlink:node-embedded-arcs (sxml:context->node node)))))))
(lambda (node) ; node or nodeset
(filter test-pred?
(if (nodeset? node)
(map-union this-axis node)
(this-axis node))))))
; XPath+XLink traverse axis
; This axis traverses from the context node
; The lambda produced additionally takes the var-binding. In var-binding, the
; linked-docs can be stored in the administrative variable '*docs*
(define (xlink:axis-traverse test-pred? . num-ancestors)
(let* ((num-anc (if (null? num-ancestors) 0 (car num-ancestors)))
(get-arcs ; returns SXLink arcs that start from a given node
(lambda (node) ; not a nodeset
(let ((root-node
(if (sxml:context? node)
(draft:list-last (sxml:context->ancestors-u node))
node)))
(if (draft:top? root-node)
(xlink:node-arcs (sxml:context->node node) root-node)
(xlink:node-embedded-arcs (sxml:context->node node)))))))
; node can be both a single node and a nodeset here
(lambda (node var-binding)
(filter
(lambda (node)
(test-pred? (sxml:context->node node)))
(xlink:traverse-arcs
(if (nodeset? node)
(map-union get-arcs node)
(get-arcs node))
(xlink:docs-variable var-binding)
num-anc)))))
; XPath+XLink traverse-arc axis
; The axis traverses from the context node that is an SXLink arc
; The lambda produced additionally takes the var-binding. In var-binding, the
; linked-docs can be stored in the administrative variable '*docs*
(define (xlink:axis-traverse-arc test-pred? . num-ancestors)
(let ((num-anc (if (null? num-ancestors) 0 (car num-ancestors))))
(lambda (node var-binding)
(filter
(lambda (node)
(test-pred? (sxml:context->node node)))
(xlink:traverse-arcs
(filter xlink:arc?
(draft:reach-root (as-nodeset node)))
(xlink:docs-variable var-binding)
num-anc)))))
(provide (all-defined)))