Revision: 6130
Author: iratqq
Date: Thu Dec 31 15:47:51 2009
Log: * scm/sxpathlib.scm:
* scm/sxml-tools.scm:
- New files.
- Note that sxml of attribute symbol is using '%.
That is not the same as original version that attribute symbol is '@,
because sigscheme's (read) doesn't allow (quote @).
* scm/Makefile.am (SCM_FILES):
- Add sxpathlib.scm and sxml-tools.scm.
http://code.google.com/p/uim/source/detail?r=6130
Added:
/trunk/scm/sxml-tools.scm
/trunk/scm/sxpathlib.scm
Modified:
/trunk/scm/Makefile.am
=======================================
--- /dev/null
+++ /trunk/scm/sxml-tools.scm Thu Dec 31 15:47:51 2009
@@ -0,0 +1,889 @@
+;; S X M L T o o l s
+; $Revision: 3.14 $ from $Date: 2003/12/23 05:39:31 $:
+;
+; This software is in Public Domain.
+; IT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
+;
+; Please send bug reports and comments to [email protected]
+; Kirill Lisovsky
+;
+; SXML normal form used for normalization-dependent functions:
+; If attr-list is present it's always the second in SXML element.
+; If aux-list is present - then list of attributes is always
+; included, and aux-list is always the third.
+; Minimized form is just the same, but all the empty aux-lists are
+; absent, and empty attr-lists are present only in elements with aux-lists
+; present.
+
+;==============================================================================
+; Auxiliary functions.
+
+(define (make-char-quotator char-encoding)
+ (let ((bad-chars (map car char-encoding)))
+
+ ; Check to see if str contains one of the characters in charset,
+ ; from the position i onward. If so, return that character's index.
+ ; otherwise, return #f
+ (define (index-cset str i charset)
+ (let loop ((i i))
+ (and (< i (string-length str))
+ (if (memv (string-ref str i) charset) i
+ (loop (inc i))))))
+
+ ; The body of the function
+ (lambda (str)
+ (let ((bad-pos (index-cset str 0 bad-chars)))
+ (if (not bad-pos) str ; str had all good chars
+ (let loop ((from 0) (to bad-pos))
+ (cond
+ ((>= from (string-length str)) '())
+ ((not to)
+ (cons (substring str from (string-length str)) '()))
+ (else
+ (let ((quoted-char
+ (cdr (assv (string-ref str to) char-encoding)))
+ (new-to
+ (index-cset str (inc to) bad-chars)))
+ (if (< from to)
+ (cons
+ (substring str from to)
+ (cons quoted-char (loop (inc to) new-to)))
+ (cons quoted-char (loop (inc to) new-to))))))))))
+))
+
+; unlike filter-map from SRFI-1 this function uses separate predicate
+; and mapping functions.
+; Applies proc to all the elements of source list that satisfy the
predicate
+; and return the list of the results.
+(define (filter-and-map pred proc lis)
+ (let rpt ((l lis))
+ (if (null? l)
+ '()
+ (if (pred (car l))
+ (cons (proc (car l)) (rpt (cdr l)))
+ (rpt (cdr l))))))
+
+; Applies pred to every member of lst and yields #t if all the results
+; are #t
+(define (check-list pred lst)
+ (cond
+ ((null? lst) #t)
+ ((pred (car lst))
+ (check-list pred (cdr lst)))
+ (else #f)))
+
+; Returns attr-list node for a given obj
+; or #f if it is absent
+(define (sxml:attr-list-node obj)
+ (if (and (not (null? (cdr obj)))
+ (pair? (cadr obj))
+ (eq? '% (caadr obj)))
+ (cadr obj)
+ #f))
+
+; Returns attr-list wrapped in list
+; or '((%)) if it is absent and aux-list is present
+; or '() if both lists are absent
+(define (sxml:attr-as-list obj)
+ (cond
+ ((sxml:attr-list-node obj)
+ => list)
+ ((sxml:aux-list-node obj)
+ '((%)))
+ (else '())))
+
+
+; Returns aux-list node for a given obj
+; or #f if it is absent
+(define (sxml:aux-list-node obj)
+ (if
+ (or (null? (cdr obj))
+ (null? (cddr obj))
+ (not (pair? (caddr obj)))
+ (not (eq? (caaddr obj) '%%)))
+ #f
+ (caddr obj)))
+
+; Returns aux-list wrapped in list
+; or '() if it is absent
+(define (sxml:aux-as-list obj)
+ (cond
+ ((sxml:aux-list-node obj)
+ => list)
+ (else '())))
+
+; optimized (string-rindex name #\:)
+; returns position of a separator between namespace-id and LocalName
+(define-macro (sxml:find-name-separator len)
+ `(let rpt ((pos (mm ,len)))
+ (cond
+ ((negative? pos) #f)
+ ((char=? #\: (string-ref name pos)) pos)
+ (else (rpt (mm pos))))))
+
+
+;==============================================================================
+; Predicates
+
+; Predicate which returns #t if given element <obj> is empty.
+; Empty element has no nested elements, text nodes, PIs, Comments or
entities
+; but it may contain attributes or namespace-id.
+; It is a SXML counterpart of XML empty-element.
+(define (sxml:empty-element? obj)
+ (not
+ ((select-first-kid
+ (lambda(x)
+ (or ((ntype-names?? '(*PI* *COMMENT* *ENTITY*)) x)
+ ((ntype?? '*) x)
+ (string? x)))) obj)))
+
+; Returns #t if the given <obj> is shallow-normalized SXML element.
+; The element itself has to be normalised but its nested elements are not
tested.
+(define (sxml:shallow-normalized? obj)
+ (or
+ (null? (cdr obj))
+ (and (or
+ (and
+ (pair? (cadr obj))
+ (eq? (caadr obj) '%))
+ (not ((select-first-kid (ntype-names?? '(% %%))) obj)))
+ (or (null? (cddr obj))
+ (and (pair? (caddr obj))
+ (eq? (caaddr obj) '%%))
+ (not ((select-first-kid (ntype?? '%%)) obj))))))
+
+; Returns #t if the given <obj> is normalized SXML element.
+; The element itself and all its nested elements have to be normalised.
+(define (sxml:normalized? obj)
+ (and
+ (sxml:shallow-normalized? obj)
+ (check-list
+ (lambda(x)
+ (if
+ (sxml:element? x)
+ (sxml:normalized? x)
+ #t))
+ (sxml:content obj))
+ ))
+
+; Returns #t if the given <obj> is shallow-minimized SXML element.
+; The element itself has to be minimised but its nested elements are not
tested.
+(define (sxml:shallow-minimized? obj)
+ (and
+ (sxml:shallow-normalized? obj)
+ (not (and (sxml:aux-list-node obj)
+ (null? (sxml:aux-list obj))))
+ (not (and (sxml:attr-list-node obj)
+ (null? (sxml:attr-list obj))
+ (not (sxml:aux-list-node obj))))))
+
+; Returns #t if the given <obj> is minimized SXML element.
+; The element itself and all its nested elements have to be minimised.
+(define (sxml:minimized? obj)
+ (and
+ (sxml:shallow-minimized? obj)
+ (check-list
+ (lambda(x)
+ (if
+ (sxml:element? x)
+ (sxml:minimized? x)
+ #t))
+ (sxml:content obj))
+ ))
+
+;==============================================================================
+; Accessors
+
+; Returns a name of a given SXML node
+; It is introduced for the sake of encapsulation.
+(define sxml:name car)
+
+; A version of sxml:name, which returns #f if the given <obj> is
+; not a SXML element.
+; Otherwise returns its name.
+(define (sxml:element-name obj)
+ (and ((ntype?? '*) obj)
+ (car obj)))
+
+; Safe version of sxml:name, which returns #f if the given <obj> is
+; not a SXML node.
+; Otherwise returns its name.
+(define (sxml:node-name obj)
+ (and (pair? obj)
+ (symbol? (car obj))
+ (car obj)))
+
+; Returns Local Part of Qualified Name (Namespaces in XML production [6])
+; for given obj, which is ":"-separated suffix of its Qualified Name
+; If a name of a node given is NCName (Namespaces in XML production [4]),
then
+; it is returned as is.
+; Please note that while SXML name is a symbol this function returns a
string.
+(define (sxml:ncname obj)
+ (let* ((name (symbol->string (car obj)))
+ (len (string-length name)))
+ (cond
+ ((sxml:find-name-separator len)
+ => (lambda (pos)
+ (substring name (+ pos 1) len)))
+ (else name))))
+
+; Returns namespace-id part of given name, or #f if it's LocalName
+(define (sxml:name->ns-id sxml-name)
+ (let* ((name (symbol->string sxml-name)))
+ (cond
+ ((sxml:find-name-separator (string-length name))
+ => (lambda (pos)
+ (substring name 0 pos)))
+ (else #f))))
+
+
+; Returns the content of given SXML element or nodeset (just text and
element
+; nodes) representing it as a list of strings and nested elements in
document
+; order. This list is empty if <obj> is empty element or empty list.
+(define (sxml:content obj)
+ (((if (nodeset? obj)
+ sxml:filter
+ select-kids)
+ (lambda(x)
+ (or
+ (string? x) ; ((ntype?? '*text*) x)
+ ((ntype?? '*) x))))
+ obj))
+
+; Returns a string which combines all the character data
+; from text node childrens of the given SXML element
+; or "" if there is no text node children
+(define (sxml:text obj)
+ (let ((tnodes
+ ((select-kids
+ string?)
+ obj)))
+ (cond
+ ((null? tnodes) "")
+ ((null? (cdr tnodes))
+ (car tnodes))
+ (else (string-intersperse tnodes "")))))
+
+;------------------------------------------------------------------------------
+; Normalization-dependent accessors
+;
+;
+; "Universal" accessors are less effective but may be used for
non-normalized SXML
+; Safe accessors are named with suffix '-u'
+;
+; "Fast" accessors are optimized for normalized SXML data.
+; They are not applicable to arbitrary non-normalized SXML data
+; Their names has no specific suffixes
+
+; Returns all the content of normalized SXML element except attr-list and
+; aux-list.
+; Thus it includes PI, COMMENT and ENTITY nodes as well as TEXT and
ELEMENT nodes
+; returned by sxml:content.
+; Returns a list of nodes in document order or empty list if <obj> is
empty
+; element or empty list.
+; This function is faster than sxml:content
+(define (sxml:content-raw obj)
+ ((if (and (not (null? (cdr obj)))
+ (pair? (cadr obj)) (eq? (caadr obj) '%))
+ (if (and (not (null? (cddr obj)))
+ (pair? (caddr obj)) (eq? (caaddr obj) '%%))
+ cdddr
+ cddr)
+ cdr) obj))
+
+
+; Returns the list of attributes for given element or nodeset.
+; Analog of ((sxpath '(% *)) obj)
+; Empty list is returned if there is no list of attributes.
+(define (sxml:attr-list-u obj)
+ (cond (((select-first-kid (ntype?? '%)) obj)
+ => cdr)
+ (else '())))
+
+; Returns the list of auxiliary nodes for given element or nodeset.
+; Analog of ((sxpath '(%% *)) obj)
+; Empty list is returned if a list of auxiliary nodes is absent.
+(define (sxml:aux-list obj)
+ (if
+ (or (null? (cdr obj))
+ (null? (cddr obj))
+ (not (pair? (caddr obj)))
+ (not (eq? (caaddr obj) '%%)))
+ '()
+ (cdaddr obj)))
+
+; Returns the list of auxiliary nodes for given element or nodeset.
+; Analog of ((sxpath '(%% *)) obj)
+; Empty list is returned if a list of auxiliary nodes is absent.
+(define (sxml:aux-list-u obj)
+ (cond (((select-first-kid (ntype?? '%%)) obj)
+ => cdr)
+ (else '())))
+
+; Return the first aux-node with <aux-name> given in SXML element <obj>
+; or #f is such a node is absent.
+; NOTE: it returns just the FIRST node found even if multiple nodes are
+; present, so it's mostly intended for nodes with unique names
+(define (sxml:aux-node obj aux-name)
+ (cond
+ ((assq aux-name (sxml:aux-list obj)))
+ (else #f)))
+
+; Return a list of aux-node with <aux-name> given in SXML element <obj>
+; or '() if such a node is absent.
+(define (sxml:aux-nodes obj aux-name)
+ (filter
+ (lambda(x) (eq? aux-name (car x)))
+ (sxml:aux-list obj)))
+
+; Accessor for an attribute <attr-name> of given SXML element <obj> which
+; It returns:
+; the value of the attribute if the attribute is present
+; #f if there is no such an attribute in the given element
+(define (sxml:attr obj attr-name)
+ (cond
+ ((assq attr-name (sxml:attr-list obj))
+ => cadr)
+ (else #f)))
+
+; Extracts a value of attribute with given name from attr-list
+(define (sxml:attr-from-list attr-list name)
+ (cond
+ ((assq name attr-list)
+ => cadr)
+ (else #f)))
+
+; Accessor for a numerical attribute <attr-name> of given SXML element
<obj>
+; which It returns:
+; a value of the attribute as the attribute as a number if the attribute
+; is present and its value may be converted to number using
string->number
+; #f if there is no such an attribute in the given element or
+; its value can't be converted to a number
+(define (sxml:num-attr obj attr-name)
+ (cond
+ ((assq attr-name (sxml:attr-list obj))
+ => (lambda(x) (string->number (cadr x))))
+ (else #f)))
+
+; Accessor for an attribute <attr-name> of given SXML element <obj> which
+; may also be an attributes-list or nodeset (usually content of SXML
element)
+;
+; It returns:
+; the value of the attribute if the attribute is present
+; #f if there is no such an attribute in the given element
+(define (sxml:attr-u obj attr-name)
+ (cond
+ ((assq attr-name
+ ; the list of attributes is computed below
+ (cond
+ ((and (not (null? (cdr obj)))
+ (pair? (cadr obj))
+ (eq? '% (caadr obj)))
+ (cdadr obj)) ; fast track for normalized elements
+ ((eq? '% (car obj))
+ (cdr obj)) ; if applied to attr-list
+ (else (sxml:attr-list-u obj))))
+ => cadr)
+ (else #f)))
+
+; Returns the list of namespaces for given element.
+; Analog of ((sxpath '(%% *NAMESPACES* *)) obj)
+; Empty list is returned if there is no list of namespaces.
+(define (sxml:ns-list obj)
+ (cond ((assv '*NAMESPACES* (sxml:aux-list obj))
+ => cdr)
+ (else '())))
+
+; Returns the list of namespace-assoc's for given namespace-id in
+; SXML element <obj>.
+; Analog of ((sxpath '(%% *NAMESPACES* namespace-id)) obj)
+; Empty list is returned if there is no namespace-assoc with namespace-id
+; given.
+(define (sxml:ns-id->nodes obj namespace-id)
+ (filter
+ (lambda(x)
+ (eq? (car x) namespace-id))
+ (sxml:ns-list obj)))
+
+; It returns:
+; A URI's for namespace-id given
+; #f if there is no namespace-assoc with namespace-id given
+(define (sxml:ns-id->uri obj namespace-id)
+ (cond
+ ((assq namespace-id (sxml:ns-list obj))
+ => cadr)
+ (else #f)))
+
+; Returns a list of namespace-assocs nodes for NS URI given
+(define (sxml:ns-uri->nodes obj URI)
+ (filter
+ (lambda (ns-assoc)
+ (string=? (cadr ns-assoc) URI))
+ (sxml:ns-list obj)))
+
+; Returns a namespace-id for NS URI given
+(define (sxml:ns-uri->id obj URI)
+ (let rpt ((ns-assocs (sxml:ns-list obj)))
+ (cond
+ ((null? ns-assocs) #f)
+ ((string=? (cadar ns-assocs) URI)
+ (caar ns-assocs))
+ (else (rpt (cdr ns-assocs)))
+ )))
+
+; Returns namespace-id for given namespace-assoc list
+(define sxml:ns-id car)
+
+; Returns URI for given namespace-assoc list
+(define sxml:ns-uri cadr)
+
+; It returns namespace prefix for given namespace-assoc list
+; Original (as in XML document) prefix for namespace-id given
+; has to be strored as the third element in namespace-assoc list
+; if it is different from namespace-id.
+; If original prefix is omitted in namespace-assoc then
+; namespace-id is used instead
+(define (sxml:ns-prefix ns-assoc)
+ (if (> (length ns-assoc) 2)
+ (caddr ns-assoc)
+ (car ns-assoc)))
+
+;==============================================================================
+; Data modification functions
+; Constructors and mutators for normalized SXML data
+;
+; This functions are optimized for normalized SXML data.
+; They are not applicable to arbitrary non-normalized SXML data
+;
+; Most of the functions are provided in two variants:
+; 1. side-effect intended functions for linear update of given elements.
+; Their names are ended with exclamation mark.
+; An example:
+; sxml:change-content!
+; 2. pure functions without side-effects which return modified elements.
+; An example:
+; sxml:change-content
+
+; Change the content of given SXML element to <new-content>
+; If <new-content> is an empty list then the <obj> is transformed
+; The resulting SXML element is normalized
+; Former name sxml:content!
+(define (sxml:change-content! obj new-content)
+ (set-cdr! obj
+ `(
+ ,@(sxml:attr-as-list obj)
+ ,@(sxml:aux-as-list obj)
+ ,@new-content)))
+
+; Change the content of given SXML element to <new-content>
+; If <new-content> is an empty list then the <obj> is transformed
+; to an empty element
+; The resulting SXML element is normalized
+(define (sxml:change-content obj new-content)
+ `(,(sxml:name obj)
+ ,@(sxml:attr-as-list obj)
+ ,@(sxml:aux-as-list obj)
+ ,@new-content))
+
+; The resulting SXML element is normalized, if <new-attrlist> is empty,
+; the cadr of <obj> is (%)
+(define (sxml:change-attrlist obj new-attrlist)
+ `(,(sxml:name obj)
+ ,@(cond
+ (new-attrlist
+ `((% ,@new-attrlist)))
+ ((sxml:aux-list-node obj)
+ '((%)))
+ (else `()))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj)))
+
+; The resulting SXML element is normalized, if <new-attrlist> is empty,
+; the cadr of <obj> is (%)
+; Former name sxml:attrlist!
+(define (sxml:change-attrlist! obj new-attrlist)
+ (set-cdr! obj
+ `(
+ ,@(cond
+ (new-attrlist
+ `((% ,@new-attrlist)))
+ ((sxml:aux-list-node obj)
+ '((%)))
+ (else `()))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj))))
+
+; Change a name of SXML element destructively
+; Former name was 'sxml:name!'
+(define (sxml:change-name! obj new-name)
+ (set-car! obj new-name))
+
+; Returns SXML element with its name changed
+(define (sxml:change-name obj new-name)
+ (cons new-name (cdr obj)))
+
+; Returns SXML element <obj> with attribute <attr> added or #f
+; if the attribute with given name already exists,
+; <attr> is (<attr-name> <attr-value>)
+; Pure functional counterpart to sxml:add-attr!
+(define (sxml:add-attr obj attr)
+ (let ((attr-list (sxml:attr-list obj)))
+ (if (assq (car attr) attr-list)
+ #f
+ `(,(sxml:name obj)
+ (% ,@(cons attr attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj)))))
+
+; Add an attribute <attr> for an element <obj>
+; Returns #f if the attribute with given name already exists.
+; The resulting SXML node is normalized.
+; Linear update counterpart to sxml:add-attr
+(define (sxml:add-attr! obj attr)
+ (let ((attr-list (sxml:attr-list obj)))
+ (if (assq (car attr) attr-list)
+ #f
+ (begin
+ (set-cdr! obj
+ `(
+ (% ,@(cons attr attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj)))
+ obj))))
+
+
+; Returns SXML element <obj> with changed value of attribute <attr> or #f
+; if where is no attribute with given name.
+; <attr> is (<attr-name> <attr-value>)
+(define (sxml:change-attr obj attr)
+ (let ((attr-list (sxml:attr-list obj)))
+ (if (null? attr-list)
+ #f
+ (cond
+ ((assv (car attr) attr-list)
+ => (lambda (y)
+ `(,(sxml:name obj)
+ (% ,@(map
+ (lambda(at)
+ (if
+ (eq? at y)
+ attr
+ at))
+ attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj)
+ )))
+ (else #f)))))
+
+; Change value of the attribute for element <obj>
+; <attr> is (<attr-name> <attr-value>)
+; Returns #f if where is no such attribute
+(define (sxml:change-attr! obj attr)
+ (let ((x (sxml:attr-list obj)))
+ (if (null? x)
+ #f
+ (cond
+ ((assv (car attr) x) => (lambda (y)
+ (set-cdr! y (cdr attr)) obj))
+ (else #f)))))
+
+; Set attribute <attr> of element <obj>
+; If there is no such attribute the new one is added
+(define (sxml:set-attr obj attr)
+ (let ((attr-list (sxml:attr-list obj)))
+ (cond
+ ((assv (car attr) attr-list)
+ => (lambda (y)
+ `(,(sxml:name obj)
+ (% ,@(map
+ (lambda(at)
+ (if
+ (eq? at y)
+ attr
+ at))
+ attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj)
+ )))
+ (else
+ `(,(sxml:name obj)
+ (% ,@(cons attr attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj))))
+ ))
+
+; Set attribute <attr> of element <obj>
+; If there is no such attribute the new one is added
+(define (sxml:set-attr! obj attr)
+ (let ((attr-list (sxml:attr-list obj)))
+ (cond
+ ((assv (car attr) attr-list)
+ => (lambda (x) (set-cdr! x (cdr attr))))
+ (else (set-cdr! obj
+ `((% ,@(cons attr attr-list))
+ ,@(sxml:aux-as-list obj)
+ ,@(sxml:content obj))))
+ )))
+
+; Returns SXML element <obj> with an auxiliary node <aux-node> added
+(define (sxml:add-aux obj aux-node)
+ `(,(sxml:name obj)
+ (% ,@(sxml:attr-list obj))
+ (%% ,@(cons aux-node (sxml:aux-list obj)))
+ ,@(sxml:content obj)))
+
+; Add an auxiliary node <aux-node> for an element <obj>
+(define (sxml:add-aux! obj aux-node)
+ (set-cdr! obj
+ `(
+ (% ,@(sxml:attr-list obj))
+ (%% ,@(cons aux-node (sxml:aux-list obj)))
+ ,@(sxml:content obj)))
+ obj)
+
+; Eliminates empty lists of attributes and aux-lists for given SXML element
+; <obj> and its descendants ("minimize" it)
+; Returns: minimized and normalized SXML element
+(define (sxml:squeeze! obj)
+ (set-cdr! obj
+ `(,@(cond
+ ((sxml:attr-list-node obj)
+ => (lambda (atl)
+ (if (and (null? (cdr atl))
+ (null? (sxml:aux-list obj)))
+ '()
+ (list atl))))
+ (else '()))
+ ,@(cond ((sxml:aux-list-node obj)
+ => (lambda (axl)
+ (if (null? (cdr axl))
+ '()
+ (list axl))))
+ (else '()))
+ ,@(map
+ (lambda(x)
+ (cond
+ (((ntype?? '*) x)
+ (sxml:squeeze! x)
+ x)
+ (else x)))
+ (sxml:content obj))
+ ))
+ )
+
+
+; Eliminates empty lists of attributes and aux-lists for given SXML element
+; <obj> and its descendants ("minimize" it)
+; Returns: minimized and normalized SXML element
+(define (sxml:squeeze obj)
+ `(,(sxml:name obj)
+ ,@(cond
+ ((sxml:attr-list-node obj)
+ => (lambda (atl)
+ (if (and (null? (cdr atl))
+ (null? (sxml:aux-list obj)))
+ '()
+ (list atl))))
+ (else '()))
+ ,@(cond ((sxml:aux-list-node obj)
+ => (lambda (axl)
+ (if (null? (cdr axl))
+ '()
+ (list axl))))
+ (else '()))
+ ,@(map
+ (lambda(x)
+ (cond
+ (((ntype?? '*) x)
+ (sxml:squeeze x))
+ (else x)))
+ (sxml:content obj))))
+
+; Eliminates empty lists of attributes and ALL aux-lists for given SXML
element
+; <obj> and its descendants
+; Returns: minimized and normalized SXML element
+(define (sxml:clean obj)
+ `(,(sxml:name obj)
+ ,@(cond
+ ((sxml:attr-list-node obj)
+ => (lambda (atl)
+ (if (null? (cdr atl))
+ '()
+ (list atl))))
+ (else '()))
+ ,@(map
+ (lambda(x)
+ (cond
+ (((ntype?? '*) x)
+ (sxml:clean x))
+ (else x)))
+ (sxml:content obj))))
+;==============================================================================
+; SXPath-related
+
+;------------------------------------------------------------------------------
+; Extensions
+
+; select-first-kid:: Pred -> Node -> Node
+; Given a Node, return its first child that satisfy
+; the test-pred?
+; Returns #f if there is no such a child
+; select-first-kid:: Pred -> Nodeset -> Node
+; The same as above, but select among children of all the nodes in
+; the Nodeset
+(define (select-first-kid test-pred?)
+ (lambda(obj)
+ (let rpt ((lst (if (symbol? (car obj))
+ (cdr obj)
+ obj)))
+ (cond
+ ((null? lst) #f)
+ ((and (pair? (car lst))
+ (test-pred? (car lst)))
+ (car lst))
+ (else (rpt (cdr lst))))
+ )))
+
+;------------------------------------------------------------------------------
+; Fast node-parent
+
+; Returns a function of one argument - SXML element - which returns its
parent
+; node using *PARENT* pointer in aux-list
+; '*TOP-PTR* may be used as a pointer to root node
+; It return an empty list when applyed to root node
+(define (sxml:node-parent rootnode)
+ (lambda(obj)
+ (cond
+ ((sxml:aux-node obj '*PARENT*)
+ => (lambda(x)
+ (if
+ (eq? '*TOP-PTR* (cadr x))
+ rootnode
+ ((cadr x)))))
+ ((and (pair? obj)
+ (eq? (car obj) '*TOP* ))
+ '())
+ (else (sxml:error nl "PARENT pointer is absent in: " obj nl)
+ ))))
+
+(define (sxml:add-parents obj . top-ptr)
+ (let rpt
+ ((elt obj)
+ (p '*TOP*)
+ (at-aux (if (eq? (sxml:name obj) '*TOP*)
+ (list (cons '%% (sxml:aux-list-u obj)))
+ (list
+ (cons '% (sxml:attr-list obj))
+ (cons '%% (cons `(*PARENT* ,(lambda() (car top-ptr)))
+ (sxml:aux-list obj))))))
+ ) ; *TOP* is a parent for top-level element
+ (let* ((h (list (sxml:name elt)))
+ (b (append
+ at-aux
+ (map
+ (lambda(x)
+ (cond
+ (((ntype?? '*) x)
+ (rpt x h
+ (list
+ (cons '% (sxml:attr-list x))
+ (cons '%% (cons `(*PARENT* ,(lambda() h))
+ (sxml:aux-list x))))
+ ))
+ (else x)))
+ (sxml:content elt)))))
+ (set-cdr! h b)
+ h)))
+
+; Lookup an element using its ID
+(define (sxml:lookup id index)
+ (cond
+ ((assoc id index)
+ => cdr)
+ (else #f)))
+
+;==============================================================================
+; Markup generation
+
+;------------------------------------------------------------------------------
+; XML
+
+; Creates the XML markup for attributes.
+(define (sxml:attr->xml attr)
+ (list " " (sxml:ncname attr)
+ "='" (cadr attr) "'"))
+
+; Return a string or a list of strings where all the occurences of
+; characters < > & " ' in a given string are replaced by corresponding
+; character entity references. See also: sxml:string->html
+(define sxml:string->xml
+ (make-char-quotator
+ '((#\< . "<") (#\> . ">") (#\& . "&")
+ (#\" . """) (#\' . "'"))))
+
+; A version of dispatch-node specialized and optimized for SXML->XML
+; transformation.
+(define (sxml:sxml->xml tree)
+ (cond
+ ((nodeset? tree)
+ (map (lambda (a-tree)
+ (sxml:sxml->xml a-tree))
+ tree))
+ ((pair? tree)
+ (let* ((name (sxml:name tree)) ; NS (URI-prefixed) not supported
+ (nm (symbol->string name))
+ (content (sxml:content-raw tree)))
+ `("<" ,nm ,@(map sxml:attr->xml (sxml:attr-list tree))
+ ,@(if (null? content) '("/>")
+ `(">" ,@(sxml:sxml->xml content) "</" ,nm ">")))))
+ ((string? tree) (sxml:string->xml tree)) ; *text*
+ (else (sxml:error "sxml->html - unexpected type of node: " tree))))
+
+
+;------------------------------------------------------------------------------
+; HTML
+
+; Creates the HTML markup for attributes.
+(define (sxml:attr->html attr)
+ (if (equal? "" (cadr attr))
+ (list " " (sxml:ncname attr))
+ (list " " (sxml:ncname attr) "='" (cadr attr) "'")))
+
+
+
+; Given a string, check to make sure it does not contain characters
+; < > & " that require encoding. Return either the original
+; string, or a list of string fragments with special characters
+; replaced by appropriate character entities.
+; Borrowed from Oleg Kiselyov's XML-to-HTML.scm (where its name is
+; string->goodHTML)
+(define sxml:string->html
+ (make-char-quotator
+ '((#\< . "<") (#\> . ">") (#\& . "&") (#\" . """))))
+
+
+; This predicate yields #t for "unterminated" HTML 4.0 tags
+(define (sxml:non-terminated-html-tag? tag)
+ (memq tag
+ '(area base basefont br col frame hr img input isindex link meta
param)))
+
+
+; A version of dispatch-node specialized and optimized for SXML->HTML
+; transformation.
+(define (sxml:sxml->html tree)
+ (cond
+ ((nodeset? tree)
+ (map (lambda (a-tree)
+ (sxml:sxml->html a-tree))
+ tree))
+ ((pair? tree)
+ (let* ((name (sxml:name tree))
+ (nm (symbol->string name))
+ (content (sxml:content-raw tree)))
+ `("<" ,nm ,@(map sxml:attr->html (sxml:attr-list tree))
+ ,@(if (null? content)
+ (if (sxml:non-terminated-html-tag? name) '(">") '("/>"))
+ `(">" ,@(sxml:sxml->html content) "</" ,nm ">")))))
+ ((string? tree) (sxml:string->html tree)) ; *text*
+ (else (sxml:error "sxml->html - unexpected type of node: " tree))))
+
=======================================
--- /dev/null
+++ /trunk/scm/sxpathlib.scm Thu Dec 31 15:47:51 2009
@@ -0,0 +1,536 @@
+;; XML processing in Scheme
+; SXPath -- SXML Query Language
+;
+; $Id: sxpathlib.scm,v 1.1 2003-07-22 11:22:11 shirok Exp $
+;
+; This code is in Public Domain
+; It's based on SXPath by Oleg Kiselyov, and multiple improvements
+; implemented by Dmitry Lizorkin.
+;
+; The list of differences from original SXPath.scm my be found in
changelog.txt
+;
+; Kirill Lisovsky [email protected]
+;
+; * * *
+;
+; SXPath is a query language for SXML, an instance of XML Information
+; set (Infoset) in the form of s-expressions. See SSAX.scm for the
+; definition of SXML and more details. SXPath is also a translation into
+; Scheme of an XML Path Language, XPath:
+; http://www.w3.org/TR/xpath
+; XPath and SXPath describe means of selecting a set of Infoset's items
+; or their properties.
+;
+; To facilitate queries, XPath maps the XML Infoset into an explicit
+; tree, and introduces important notions of a location path and a
+; current, context node. A location path denotes a selection of a set of
+; nodes relative to a context node. Any XPath tree has a distinguished,
+; root node -- which serves as the context node for absolute location
+; paths. Location path is recursively defined as a location step joined
+; with a location path. A location step is a simple query of the
+; database relative to a context node. A step may include expressions
+; that further filter the selected set. Each node in the resulting set
+; is used as a context node for the adjoining location path. The result
+; of the step is a union of the sets returned by the latter location
+; paths.
+;
+; The SXML representation of the XML Infoset (see SSAX.scm) is rather
+; suitable for querying as it is. Bowing to the XPath specification,
+; we will refer to SXML information items as 'Nodes':
+; <Node> ::= <Element> | <attributes-coll> | <attrib>
+; | "text string" | <PI>
+; This production can also be described as
+; <Node> ::= (name . <Nodeset>) | "text string"
+; An (ordered) set of nodes is just a list of the constituent nodes:
+; <Nodeset> ::= (<Node> ...)
+; Nodesets, and Nodes other than text strings are both lists. A
+; <Nodeset> however is either an empty list, or a list whose head is not
+; a symbol. A symbol at the head of a node is either an XML name (in
+; which case it's a tag of an XML element), or an administrative name
+; such as '@'. This uniform list representation makes processing rather
+; simple and elegant, while avoiding confusion. The multi-branch tree
+; structure formed by the mutually-recursive datatypes <Node> and
+; <Nodeset> lends itself well to processing by functional languages.
+;
+; A location path is in fact a composite query over an XPath tree or
+; its branch. A singe step is a combination of a projection, selection
+; or a transitive closure. Multiple steps are combined via join and
+; union operations. This insight allows us to _elegantly_ implement
+; XPath as a sequence of projection and filtering primitives --
+; converters -- joined by _combinators_. Each converter takes a node
+; and returns a nodeset which is the result of the corresponding query
+; relative to that node. A converter can also be called on a set of
+; nodes. In that case it returns a union of the corresponding queries over
+; each node in the set. The union is easily implemented as a list
+; append operation as all nodes in a SXML tree are considered
+; distinct, by XPath conventions. We also preserve the order of the
+; members in the union. Query combinators are high-order functions:
+; they take converter(s) (which is a Node|Nodeset -> Nodeset function)
+; and compose or otherwise combine them. We will be concerned with
+; only relative location paths [XPath]: an absolute location path is a
+; relative path applied to the root node.
+;
+; Similarly to XPath, SXPath defines full and abbreviated notations
+; for location paths. In both cases, the abbreviated notation can be
+; mechanically expanded into the full form by simple rewriting
+; rules. In case of SXPath the corresponding rules are given as
+; comments to a sxpath function, below. The regression test suite at
+; the end of this file shows a representative sample of SXPaths in
+; both notations, juxtaposed with the corresponding XPath
+; expressions. Most of the samples are borrowed literally from the
+; XPath specification, while the others are adjusted for our running
+; example, tree1.
+;
+
+;; Read-only decrement
+(define-macro (mm x) `(- ,x 1))
+
+;=============================================================================
+; Basic converters and applicators
+; A converter is a function
+; type Converter = Node|Nodeset -> Nodeset
+; A converter can also play a role of a predicate: in that case, if a
+; converter, applied to a node or a nodeset, yields a non-empty
+; nodeset, the converter-predicate is deemed satisfied. Throughout
+; this file a nil nodeset is equivalent to #f in denoting a failure.
+
+; Returns #t if given object is a nodeset
+(define (nodeset? x)
+ (or (and (pair? x) (not (symbol? (car x)))) (null? x)))
+
+; If x is a nodeset - returns it as is, otherwise wrap it in a list.
+(define (as-nodeset x)
+ (if (nodeset? x) x (list x)))
+
+;-----------------------------------------------------------------------------
+; Node test
+; The following functions implement 'Node test's as defined in
+; Sec. 2.3 of XPath document. A node test is one of the components of a
+; location step. It is also a converter-predicate in SXPath.
+
+; Predicate which returns #t if <obj> is SXML element, otherwise returns
#f.
+(define (sxml:element? obj)
+ (and (pair? obj)
+ (symbol? (car obj))
+ (not (memq (car obj)
+ ; '(% %% *PI* *COMMENT* *ENTITY* *NAMESPACES*)
+ ; the line above is a workaround for old SXML
+ '(% %% *PI* *COMMENT* *ENTITY*)))))
+
+; The function ntype-names?? takes a list of acceptable node names as a
+; criterion and returns a function, which, when applied to a node,
+; will return #t if the node name is present in criterion list and #f
+; othervise.
+; ntype-names?? :: ListOfNames -> Node -> Boolean
+(define (ntype-names?? crit)
+ (lambda(node)
+ (and (pair? node)
+ (memq (car node) crit))))
+
+; The function ntype?? takes a type criterion and returns
+; a function, which, when applied to a node, will tell if the node
satisfies
+; the test.
+; ntype?? :: Crit -> Node -> Boolean
+;
+; The criterion 'crit' is
+; one of the following symbols:
+; id - tests if the Node has the right name (id)
+; % - tests if the Node is an <attributes-list>
+; * - tests if the Node is an <Element>
+; *text* - tests if the Node is a text node
+; *data* - tests if the Node is a data node
+; (text, number, boolean, etc., but not pair)
+; *PI* - tests if the Node is a PI node
+; *COMMENT* - tests if the Node is a COMMENT node
+; *ENTITY* - tests if the Node is a ENTITY node
+; *any* - #t for any type of Node
+(define (ntype?? crit)
+ (case crit
+ ((*) sxml:element?)
+ ((*any*) (lambda (node) #t))
+ ((*text*) (lambda (node) (string? node)))
+ ((*data*) (lambda (node) (not (pair? node))))
+ (else (lambda (node) (and (pair? node) (eq? crit (car node)))))
+ ))
+
+; This function takes a namespace-id, and returns a predicate
+; Node -> Boolean, which is #t for nodes with this very namespace-id.
+; ns-id is a string
+; (ntype-namespace-id?? #f) will be #t for nodes with non-qualified names.
+(define (ntype-namespace-id?? ns-id)
+ (lambda (node)
+ (and (pair? node)
+ (not (memq (car node)
+ '(% %% *PI* *COMMENT* *ENTITY*)))
+ (let ((nm (symbol->string (car node))))
+ (cond
+ ((string-rindex nm #\:)
+ => (lambda (pos)
+ (and
+ (= pos (string-length ns-id))
+ (string-prefix? ns-id nm))))
+ (else (not ns-id)))))))
+;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+; This function takes a predicate and returns it inverted
+; That is if the given predicate yelds #f or '() the inverted one
+; yields the given node (#t) and vice versa.
+(define (sxml:invert pred)
+ (lambda(node)
+ (case (pred node)
+ ((#f '()) node)
+ (else #f))))
+
+; Curried equivalence converter-predicates
+(define (node-eq? other)
+ (lambda (node)
+ (eq? other node)))
+
+(define (node-equal? other)
+ (lambda (node)
+ (equal? other node)))
+
+; node-pos:: N -> Nodeset -> Nodeset, or
+; node-pos:: N -> Converter
+; Select the N'th element of a Nodeset and return as a singular Nodeset;
+; Return an empty nodeset if the Nth element does not exist.
+; ((node-pos 1) Nodeset) selects the node at the head of the Nodeset,
+; if exists; ((node-pos 2) Nodeset) selects the Node after that, if
+; exists.
+; N can also be a negative number: in that case the node is picked from
+; the tail of the list.
+; ((node-pos -1) Nodeset) selects the last node of a non-empty nodeset;
+; ((node-pos -2) Nodeset) selects the last but one node, if exists.
+(define (node-pos n)
+ (lambda (nodeset)
+ (cond
+ ((not (nodeset? nodeset)) '())
+ ((null? nodeset) nodeset)
+ ((eqv? n 1) (list (car nodeset)))
+ ((negative? n) ((node-pos (+ n 1 (length nodeset))) nodeset))
+ (else
+ (assert (positive? n))
+ ((node-pos (mm n)) (cdr nodeset))))))
+
+; filter:: Converter -> Converter
+; A filter applicator, which introduces a filtering context. The argument
+; converter is considered a predicate, with either #f or nil result meaning
+; failure.
+(define (sxml:filter pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton
nset)
+ (let loop ((lst (as-nodeset lst))
+ (res '()))
+ (if (null? lst)
+ (reverse res)
+ (let ((pred-result (pred? (car lst))))
+ (loop (cdr lst)
+ (if (and pred-result (not (null? pred-result)))
+ (cons (car lst) res)
+ res)))))))
+
+; take-until:: Converter -> Converter, or
+; take-until:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f
or
+; nil. Return the elements of the input nodeset that have been processed
+; till that moment (that is, which fail the predicate).
+; take-until is a variation of the filter above: take-until passes
+; elements of an ordered input set till (but not including) the first
+; element that satisfies the predicate.
+; The nodeset returned by ((take-until (not pred)) nset) is a subset --
+; to be more precise, a prefix -- of the nodeset returned by
+; ((filter pred) nset)
+(define (take-until pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton
nset)
+ (let loop ((lst (as-nodeset lst)))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ '()
+ (cons (car lst) (loop (cdr lst)))))
+ ))))
+
+; take-after:: Converter -> Converter, or
+; take-after:: Pred -> Node|Nodeset -> Nodeset
+; Given a converter-predicate and a nodeset, apply the predicate to
+; each element of the nodeset, until the predicate yields anything but #f
or
+; nil. Return the elements of the input nodeset that have not been
processed:
+; that is, return the elements of the input nodeset that follow the first
+; element that satisfied the predicate.
+; take-after along with take-until partition an input nodeset into three
+; parts: the first element that satisfies a predicate, all preceding
+; elements and all following elements.
+(define (take-after pred?)
+ (lambda (lst) ; a nodeset or a node (will be converted to a singleton
nset)
+ (let loop ((lst (as-nodeset lst)))
+ (if (null? lst) lst
+ (let ((pred-result (pred? (car lst))))
+ (if (and pred-result (not (null? pred-result)))
+ (cdr lst)
+ (loop (cdr lst))))
+ ))))
+
+; Apply proc to each element of lst and return the list of results.
+; if proc returns a nodeset, splice it into the result
+;
+; From another point of view, map-union is a function Converter->Converter,
+; which places an argument-converter in a joining context.
+(define (map-union proc lst)
+ (if (null? lst) lst
+ (let ((proc-res (proc (car lst))))
+ ((if (nodeset? proc-res) append cons)
+ proc-res (map-union proc (cdr lst))))))
+
+; node-reverse :: Converter, or
+; node-reverse:: Node|Nodeset -> Nodeset
+; Reverses the order of nodes in the nodeset
+; This basic converter is needed to implement a reverse document order
+; (see the XPath Recommendation).
+(define node-reverse
+ (lambda (node-or-nodeset)
+ (if (not (nodeset? node-or-nodeset)) (list node-or-nodeset)
+ (reverse node-or-nodeset))))
+
+; node-trace:: String -> Converter
+; (node-trace title) is an identity converter. In addition it prints out
+; a node or nodeset it is applied to, prefixed with the 'title'.
+; This converter is very useful for debugging.
+(define (node-trace title)
+ (lambda (node-or-nodeset)
+ (cout nl "-->" title " :")
+ (pp node-or-nodeset)
+ node-or-nodeset))
+
+
+;------------------------------------------------------------------------------
+; Converter combinators
+;
+; Combinators are higher-order functions that transmogrify a converter
+; or glue a sequence of converters into a single, non-trivial
+; converter. The goal is to arrive at converters that correspond to
+; XPath location paths.
+;
+; From a different point of view, a combinator is a fixed, named
+; _pattern_ of applying converters. Given below is a complete set of
+; such patterns that together implement XPath location path
+; specification. As it turns out, all these combinators can be built
+; from a small number of basic blocks: regular functional composition,
+; map-union and filter applicators, and the nodeset union.
+
+
+
+; select-kids:: Pred -> Node -> Nodeset
+; Given a Node, return an (ordered) subset its children that satisfy
+; the Pred (a converter, actually)
+; select-kids:: Pred -> Nodeset -> Nodeset
+; The same as above, but select among children of all the nodes in
+; the Nodeset
+;
+; More succinctly, the signature of this function is
+; select-kids:: Converter -> Converter
+(define (select-kids test-pred?)
+ (lambda (node) ; node or node-set
+ (cond
+ ((null? node) node)
+ ((not (pair? node)) '()) ; No children
+ ((symbol? (car node))
+ ((sxml:filter test-pred?) (cdr node))) ; it's a single node
+ (else (map-union (select-kids test-pred?) node)))))
+
+
+; node-self:: Pred -> Node -> Nodeset, or
+; node-self:: Converter -> Converter
+; Similar to select-kids but apply to the Node itself rather
+; than to its children. The resulting Nodeset will contain either one
+; component, or will be empty (if the Node failed the Pred).
+(define node-self sxml:filter)
+
+
+; node-join:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-join:: [Converter] -> Converter
+; join the sequence of location steps or paths as described
+; in the title comments above.
+(define (node-join . selectors)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (selectors selectors))
+ (if (null? selectors) nodeset
+ (loop
+ (if (nodeset? nodeset)
+ (map-union (car selectors) nodeset)
+ ((car selectors) nodeset))
+ (cdr selectors))))))
+
+
+; node-reduce:: [LocPath] -> Node|Nodeset -> Nodeset, or
+; node-reduce:: [Converter] -> Converter
+; A regular functional composition of converters.
+; From a different point of view,
+; ((apply node-reduce converters) nodeset)
+; is equivalent to
+; (foldl apply nodeset converters)
+; i.e., folding, or reducing, a list of converters with the nodeset
+; as a seed.
+(define (node-reduce . converters)
+ (lambda (nodeset) ; Nodeset or node
+ (let loop ((nodeset nodeset) (converters converters))
+ (if (null? converters) nodeset
+ (loop ((car converters) nodeset) (cdr converters))))))
+
+
+; node-or:: [Converter] -> Converter
+; This combinator applies all converters to a given node and
+; produces the union of their results.
+; This combinator corresponds to a union, '|' operation for XPath
+; location paths.
+; (define (node-or . converters)
+; (lambda (node-or-nodeset)
+; (if (null? converters) node-or-nodeset
+; (append
+; ((car converters) node-or-nodeset)
+; ((apply node-or (cdr converters)) node-or-nodeset)))))
+; More optimal implementation follows
+(define (node-or . converters)
+ (lambda (node-or-nodeset)
+ (let loop ((result '()) (converters converters))
+ (if (null? converters) result
+ (loop (append result (or ((car converters) node-or-nodeset) '()))
+ (cdr converters))))))
+
+
+; node-closure:: Converter -> Converter
+; Select all _descendants_ of a node that satisfy a converter-predicate.
+; This combinator is similar to select-kids but applies to
+; grand... children as well.
+; This combinator implements the "descendant::" XPath axis
+; Conceptually, this combinator can be expressed as
+; (define (node-closure f)
+; (node-or
+; (select-kids f)
+; (node-reduce (select-kids (ntype?? '*)) (node-closure f))))
+; This definition, as written, looks somewhat like a fixpoint, and it
+; will run forever. It is obvious however that sooner or later
+; (select-kids (ntype?? '*)) will return an empty nodeset. At
+; this point further iterations will no longer affect the result and
+; can be stopped.
+(define (node-closure test-pred?)
+ (lambda (node) ; Nodeset or node
+ (let loop ((parent node) (result '()))
+ (if (null? parent) result
+ (loop (sxml:child-elements parent)
+ (append result
+ ((select-kids test-pred?) parent)))
+ ))))
+
+;=============================================================================
+; Unified with sxpath-ext and sxml-tools
+
+; According to XPath specification 2.3, this test is true for any
+; XPath node.
+; For SXML auxiliary lists and lists of attributes has to be excluded.
+(define (sxml:node? node)
+ (not (and
+ (pair? node)
+ (memq (car node) '(% %%)))))
+
+; Returns the list of attributes for a given SXML node
+; Empty list is returned if the given node os not an element,
+; or if it has no list of attributes
+(define (sxml:attr-list obj)
+ (if (and (sxml:element? obj)
+ (not (null? (cdr obj)))
+ (pair? (cadr obj))
+ (eq? '% (caadr obj)))
+ (cdadr obj)
+ '()))
+
+; Attribute axis
+(define (sxml:attribute test-pred?)
+ (let ((fltr (sxml:filter test-pred?)))
+ (lambda (node)
+ (fltr
+ (apply append
+ (map
+ sxml:attr-list
+ (as-nodeset node)))))))
+
+; Child axis
+; This function is similar to 'select-kids', but it returns an empty
+; child-list for PI, Comment and Entity nodes
+(define (sxml:child test-pred?)
+ (lambda (node) ; node or node-set
+ (cond
+ ((null? node) node)
+ ((not (pair? node)) '()) ; No children
+ ((memq (car node) '(*PI* *COMMENT* *ENTITY*)) ; PI, Comment or
Entity
+ '()) ; No children
+ ((symbol? (car node)) ; it's a single node
+ ((sxml:filter test-pred?) (cdr node)))
+ (else (map-union (sxml:child test-pred?) node)))))
+
+; Parent axis
+; Given a predicate, it returns a function
+; RootNode -> Converter
+; which which yields a
+; node -> parent
+; converter then applied to a rootnode.
+; Thus, such a converter may be constructed using
+; ((sxml:parent test-pred) rootnode)
+; and returns a parent of a node it is applied to.
+; If applied to a nodeset, it returns the
+; list of parents of nodes in the nodeset. The rootnode does not have
+; to be the root node of the whole SXML tree -- it may be a root node
+; of a branch of interest.
+; The parent:: axis can be used with any SXML node.
+(define (sxml:parent test-pred?)
+ (lambda (root-node) ; node or nodeset
+ (lambda (node) ; node or nodeset
+ (if (nodeset? node)
+ (map-union ((sxml:parent test-pred?) root-node) node)
+ (let rpt ((pairs
+ (apply append
+ (map
+ (lambda (root-n)
+ (map
+ (lambda (arg) (cons arg root-n))
+ (append
+ (sxml:attr-list root-n)
+ (sxml:child-nodes root-n))))
+ (as-nodeset root-node)))
+ ))
+ (if (null? pairs)
+ '()
+ (let ((pair (car pairs)))
+ (if (eq? (car pair) node)
+ ((sxml:filter test-pred?) (list (cdr pair)))
+ (rpt (append
+ (map
+ (lambda (arg) (cons arg (car pair)))
+ (append
+ (sxml:attr-list (car pair))
+ (sxml:child-nodes (car pair))))
+ (cdr pairs)
+ ))))))))))
+
+
+;=============================================================================
+; Popular short cuts
+
+; node-parent:: RootNode -> Converter
+; (node-parent rootnode) yields a converter that returns a parent of a
+; node it is applied to. If applied to a nodeset, it returns the list
+; of parents of nodes in the nodeset.
+; Given the notation of Philip Wadler's paper on semantics of XSLT,
+; parent(x) = { y | y=subnode*(root), x=subnode(y) }
+; Therefore, node-parent is not the fundamental converter: it can be
+; expressed through the existing ones. Yet node-parent is a rather
+; convenient converter. It corresponds to a parent:: axis of SXPath.
+;
+; Please note: this function is provided for backward compatibility
+; with SXPath/SXPathlib ver. 3.5.x.x and earlier.
+; Now it's a particular case of 'sxml:parent' application:
+(define node-parent (sxml:parent (ntype?? '*any*)))
+
+(define sxml:child-nodes (sxml:child sxml:node?))
+
+(define sxml:child-elements (select-kids sxml:element?))
+
=======================================
--- /trunk/scm/Makefile.am Thu Dec 31 15:44:40 2009
+++ /trunk/scm/Makefile.am Thu Dec 31 15:47:51 2009
@@ -51,7 +51,8 @@
sqlite3.scm \
lolevel.scm \
input-parse.scm match.scm \
- http-client.scm http-server.scm
+ http-client.scm http-server.scm \
+ sxml-tools.scm sxpathlib.scm
ETAGS_ARGS=$(SCM_FILES) $(GENERATED_SCM_FILES)