-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathmodif.scm
850 lines (814 loc) · 34.7 KB
/
modif.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
;;(include "../sxml-tools/sxml-tools.sch")
;;(include "../html-prag/htmlprag.sch")
;;(include "../multi-parser/id/srfi-12.sch")
;;(include "../multi-parser/id/http.sch")
;;(include "../libs/input-parse.sch")
;;(include "../libs/gambit/myenv.sch")
;;(include "../libs/gambit/common.sch")
;;(include "../ssax/SSAX-code.sch")
;; A tool for making functional-style modifications to SXML documents
;
; 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] Dmitry Lizorkin
;
; The basics of modification language design was inspired by Patrick Lehti
; and his data manipulation processor for XML Query Language:
; http://www.ipsi.fraunhofer.de/~lehti/
; However, with functional techniques we can do this better...
;==========================================================================
; Modification core
; Displays an error to stderr and returns #f
(define (sxml:modification-error . text)
(cerr "Modification error: ")
(apply cerr text)
(cerr nl)
#f)
; Separates the list into two lists with respect to the predicate
; Returns: (values res-lst1 res-lst2)
; res-lst1 - contains all members from the input lst that satisfy the pred?
; res-lst2 - contains the remaining members of the input lst
(define (sxml:separate-list pred? lst)
(let loop ((lst lst)
(satisfy '())
(rest '()))
(cond
((null? lst)
(values (reverse satisfy) (reverse rest)))
((pred? (car lst)) ; the first member satisfies the predicate
(loop (cdr lst)
(cons (car lst) satisfy) rest))
(else
(loop (cdr lst)
satisfy (cons (car lst) rest))))))
;-------------------------------------------------
; Miscellaneous helpers
; Asserts that the given obj is a proper attribute node.
; If this is the case, returns #t. Otherwise, calls sxml:modification-error
; with the appropriate error message.
; Handles singular attributes correctly. In accordance with SXML 3.0, accepts
; aux lists as attribute nodes
(define (sxml:assert-proper-attribute obj)
(if
(or (and (pair? obj) ; aux node - any content is acceptable
(not (null? obj))
(eq? (car obj) '@))
(and (list? obj) ; '() is not a list
(symbol? (car obj))
(or (null? (cdr obj)) ; singular attribute
(null? (cddr obj)))))
#t
(sxml:modification-error
"improper attribute node - " obj)))
; Unites a list of annot-attributes into a single annot-attributes.
; Ensures that every attribute is a proper one, and that there is no duplicate
; attributes
; annot-attributes-lst ::= (listof annot-attributes)
; In accordance with SXML specification, version 3.0:
; [3] <annot-attributes> ::= (@ <attribute>* <annotations>? )
; In case of an error, returns #f.
; In the correct case, returns: annot-attributes
(define (sxml:unite-annot-attributes-lists . annot-attributes-lst)
(if
(null? annot-attributes-lst) ; nothing to do
'()
(let iter-lst ((src annot-attributes-lst)
(attrs '())
(annotations '()))
(if
(null? src) ; Recursion finished
(if (null? annotations)
(cons '@ (reverse attrs))
`(@ ,@(reverse attrs) (@ ,@annotations)))
(let iter-annot-attrs ((annot-attrs (cdar src))
(attrs attrs)
(annotations annotations))
(if
(null? annot-attrs) ; proceed with the outer loop
(iter-lst (cdr src) attrs annotations)
(let ((curr (car annot-attrs)))
(cond
((and (pair? curr)
(not (null? curr))
(eq? (car curr) '@))
; an annotation node
(iter-annot-attrs (cdr annot-attrs)
attrs
(append annotations (cdr curr))))
((sxml:assert-proper-attribute curr)
(if
(assq (car curr) attrs) ; duplicate attribute detected
(sxml:modification-error
"duplicate attribute - " (car curr))
(iter-annot-attrs (cdr annot-attrs)
(cons curr attrs)
annotations)))
(else ; improper attribute
#f)))))))))
;-------------------------------------------------
; The core function of document transformation into a new document
; Recursive SXML tree transformation
; curr-node - the node to be transformed
; targets-alist ::= (listof (cons node-chain update-target))
; node-chain ::= (listof node)
; node-chain - the chain of nodes, starting from the `curr-node' and proceeding
; with its decsednants until the update target
; Returns the transformed node
(define (sxml:tree-trans curr-node targets-alist)
(call-with-values
(lambda () (sxml:separate-list
(lambda (pair) (null? (car pair)))
targets-alist))
(lambda (matched ; handlers which match this node
targets-alist ; the rest
)
(and-let*
((after-subnodes ; curr-node after its subnodes are processed
(if
(or (not (pair? curr-node)) ; leaf node
(null? targets-alist) ; no more handlers
)
curr-node
(let process-attrs ((targets-alist targets-alist)
(src-attrs (sxml:attr-list curr-node))
(res-attrs '()))
(if
(null? src-attrs) ; all attributes processed
; Go to proceed child elements
(if
(null? targets-alist) ; children don't need to be processed
(cons ; Constructing the result node
(car curr-node) ; node name
((lambda (kids)
(if (null? res-attrs) ; no attributes
kids
(cons (cons '@ (reverse res-attrs))
kids)))
((if (and (not (null? (cdr curr-node)))
(pair? (cadr curr-node))
(eq? (caadr curr-node) '@))
cddr cdr)
curr-node)))
(let process-kids ((targets-alist targets-alist)
(src-kids (cdr curr-node))
(res-kids '()))
(cond
((null? src-kids) ; all kids processed
(call-with-values
(lambda () (sxml:separate-list
(lambda (obj)
(and (pair? obj) (eq? (car obj) '@)))
res-kids))
(lambda (more-attrs kids)
(if
(and (null? res-attrs) (null? more-attrs))
(cons ; Constructing the result node
(car curr-node) ; node name
kids)
(and-let*
((overall-attrs
(apply
sxml:unite-annot-attributes-lists
(cons
(cons '@ (reverse res-attrs))
more-attrs))))
(cons (car curr-node) ; node name
(cons overall-attrs kids)))))))
((and (pair? (car src-kids))
(eq? (caar src-kids) '@))
; attribute node - already processed
(process-kids
targets-alist (cdr src-kids) res-kids))
(else
(let ((kid-templates
(filter
(lambda (pair)
(eq? (caar pair) (car src-kids)))
targets-alist)))
(if
(null? kid-templates)
; this child node remains as is
(process-kids
targets-alist
(cdr src-kids)
(append res-kids (list (car src-kids))))
(and-let*
((new-kid
(sxml:tree-trans
(car src-kids)
(map
(lambda (pair)
(cons (cdar pair) (cdr pair)))
kid-templates))))
(process-kids
(filter
(lambda (pair)
(not (eq? (caar pair) (car src-kids))))
targets-alist)
(cdr src-kids)
(append
res-kids
(if (nodeset? new-kid)
new-kid
(list new-kid)))))))))))
(let* ((curr-attr (car src-attrs))
(attr-templates
(filter
(lambda (pair)
(eq? (caar pair) curr-attr))
targets-alist)))
(if
(null? attr-templates)
; this attribute remains as is
(process-attrs targets-alist
(cdr src-attrs)
(cons curr-attr res-attrs))
(let ((new-attr ; cannot produce error for attrs
(sxml:tree-trans
curr-attr
(map
(lambda (pair)
(cons (cdar pair) (cdr pair)))
attr-templates))))
(process-attrs
(filter
(lambda (pair)
(not (eq? (caar pair) curr-attr)))
targets-alist)
(cdr src-attrs)
(if (nodeset? new-attr)
(append (reverse new-attr) res-attrs)
(cons new-attr res-attrs)))))))))))
(let process-this ((new-curr-node after-subnodes)
(curr-handlers (map cdr matched)))
(if
(null? curr-handlers)
(if ; all handlers processed
(not (pair? new-curr-node))
new-curr-node ; atomic node
(call-with-values ; otherwise - unite attr lists
(lambda () (sxml:separate-list
(lambda (obj) (and (pair? obj) (eq? (car obj) '@)))
(cdr new-curr-node)))
(lambda (attrs kids)
(if (null? attrs)
new-curr-node ; node remains unchanged
(and-let*
((overall-attrs
(apply sxml:unite-annot-attributes-lists attrs)))
(cons
(car new-curr-node) ; node name
(cons overall-attrs kids)))))))
(process-this
((cadar curr-handlers) ; lambda
new-curr-node
(caar curr-handlers) ; context
(caddar curr-handlers) ; base-node
)
(cdr curr-handlers))))))))
; doc - a source SXML document
; update-targets ::= (listof update-target)
; update-target ::= (list context handler base-node)
; context - context of the node selected by the location path
; handler ::= (lambda (node context base-node) ...)
; handler - specifies the required transformation over the node selected
; base-node - the node with respect to which the location path was evaluated
;
; Returns the new document. In case of a transformation that results to a
; non-well-formed document, returns #f and the error message is displayed to
; stderr as a side effect
(define (sxml:transform-document doc update-targets)
(let ((targets-alist
(map-union
(lambda (triple)
(let ((node-path (reverse (sxml:context->content (car triple)))))
(if
(eq? (car node-path) doc)
(list (cons (cdr node-path) triple))
'())))
update-targets)))
(if (null? targets-alist) ; nothing to do
doc
(sxml:tree-trans doc targets-alist))))
;==========================================================================
; Processing update-specifiers
; Evaluates lambda-upd-specifiers for the SXML document doc
; Returns:
; update-targets ::= (listof update-target)
; update-target ::= (list context handler base-node)
; context - context of the node selected by the location path
; handler ::= (lambda (node context base-node) ...)
; handler - specifies the required transformation over the node selected
; base-node - the node with respect to which the location path was evaluated
(define (sxml:lambdas-upd-specifiers->targets doc lambdas-upd-specifiers)
(let ((doc-list (list doc)))
(letrec
((construct-targets
; base-cntxtset - base context set for the current upd-specifier
; lambdas-upd-specifiers - is assumed to be non-null?
(lambda (base-cntxtset lambdas-upd-specifiers)
(let ((triple (car lambdas-upd-specifiers)))
; Iterates members of the base context-set
; new-base ::= (listof context-set)
; Each context-set is obtained by applying the txpath-lambda
; to the each member of base-cntxtset
(let iter-base ((base-cntxtset base-cntxtset)
(res '())
(new-base '()))
(if
(null? base-cntxtset) ; finished scanning base context-set
(if
(null? (cdr lambdas-upd-specifiers)) ; no more members
res
(append
res
(construct-targets
(if
(cadadr lambdas-upd-specifiers) ; following is relative
(apply ddo:unite-multiple-context-sets new-base)
doc-list)
(cdr lambdas-upd-specifiers))))
(let* ((curr-base-context (car base-cntxtset))
(context-set ((car triple)
(list curr-base-context)
(cons 1 1)
'() ; dummy var-binding
)))
(iter-base
(cdr base-cntxtset)
(append res
(map
(lambda (context)
(list context
(caddr triple) ; handler
(sxml:context->node curr-base-context)))
context-set))
(cons context-set new-base)))))))))
(if
(null? lambdas-upd-specifiers) ; no transformation rules
'()
(construct-targets doc-list lambdas-upd-specifiers)))))
; "Precompiles" each of update-specifiers, by transforming location paths and
; update actions into lambdas.
; Returns:
; lambdas-upd-specifiers ::= (listof lambdas-upd-specifier)
; lambdas-upd-specifier ::= (list txpath-lambda relative? handler)
; txpath-lambda ::= (lambda (nodeset position+size var-binding) ...)
; txpath-lambda - full-argument implementation of a location path
; relative? - whether the txpath lambda is to be evaluated relatively to the
; node selected by the previous lambdas-upd-specifier, or with respect to
; the root of the document. For relative?=#t the base-node is the node
; selected by the previous lambdas-upd-specifier, otherwise the base node is
; the root of the document being transformed
; handler ::= (lambda (node context base-node) ...)
(define (sxml:update-specifiers->lambdas update-specifiers)
(let iter ((src update-specifiers)
(res '()))
(if
(null? src) ; every specifier processed
(reverse res)
(let ((curr (car src)))
(if
(or (not (list? curr))
(null? (cdr curr)))
(sxml:modification-error "improper update-specifier: " curr)
(and-let*
; Convert Location path to XPath AST
((ast (txp:xpath->ast (car curr))))
(call-with-values
(lambda ()
(if
(eq? (car ast) 'absolute-location-path)
(values
(ddo:ast-relative-location-path
(cons 'relative-location-path (cdr ast))
#f ; keep all ancestors
#t ; on a single level, since a single node
0 ; zero predicate nesting
'(0) ; initial var-mapping
)
#f)
(values
(ddo:ast-relative-location-path ast #f #t 0 '(0))
(not (null? res)) ; absolute for the first rule
)))
(lambda (txpath-pair relative?)
(if
(not txpath-pair) ; semantic error
txpath-pair ; propagate the error
(let ((txpath-lambda (car txpath-pair))
(action (cadr curr)))
(if
(procedure? action) ; user-supplied handler
(iter (cdr src)
(cons
(list txpath-lambda relative? action)
res))
(case action
((delete delete-undeep)
(iter (cdr src)
(cons
(list
txpath-lambda
relative?
(cdr
(assq action
`((delete . ,modif:delete)
(delete-undeep . ,modif:delete-undeep)))))
res)))
((insert-into insert-following insert-preceding)
(let ((params (cddr curr)))
(iter (cdr src)
(cons
(list
txpath-lambda
relative?
((cdr
(assq
action
`((insert-into . ,modif:insert-into)
(insert-following . ,modif:insert-following)
(insert-preceding . ,modif:insert-preceding))))
(lambda (context base-node) params)))
res))))
((replace)
(let ((params (cddr curr)))
(iter (cdr src)
(cons
(list txpath-lambda relative?
(lambda (node context base-node) params))
res))))
((rename)
(if
(or (null? (cddr curr)) ; no parameter supplied
(not (symbol? (caddr curr))))
(sxml:modification-error
"improper new name for the node to be renamed: "
curr)
(iter
(cdr src)
(cons
(let ((new-name (caddr curr)))
(list txpath-lambda relative? (modif:rename new-name)))
res))))
((move-into move-following move-preceding)
(if
(or (null? (cddr curr)) ; no lpath supplied
(not (string? (caddr curr))))
(sxml:modification-error
"improper destination location path for move action: "
curr)
(and-let*
((ast (txp:xpath->ast (caddr curr)))
(txpath-pair (ddo:ast-location-path ast #f #t 0 '(0))))
(iter (cdr src)
(cons
(list
(car txpath-pair)
#t
((cdr
(assq
action
`((move-into . ,modif:insert-into)
(move-following . ,modif:insert-following)
(move-preceding . ,modif:insert-preceding))))
(lambda (context base-node) base-node)))
(cons
(list txpath-lambda relative? modif:delete)
res))))))
(else
(sxml:modification-error "unknown action: " curr))))))))))))))
;==========================================================================
; Several popular handlers
; Node insertion
; node-specifier ::= (lambda (context base-node) ...)
; The lambda specifies the node to be inserted
(define (modif:insert-following node-specifier)
(lambda (node context base-node)
((if (nodeset? node) append cons)
node
(as-nodeset (node-specifier context base-node)))))
(define (modif:insert-preceding node-specifier)
(lambda (node context base-node)
(let ((new (node-specifier context base-node)))
((if (nodeset? new) append cons)
new
(as-nodeset node)))))
(define (modif:insert-into node-specifier)
(lambda (node context base-node)
(let* ((to-insert (as-nodeset (node-specifier context base-node)))
(insert-into-single ; inserts into single node
(lambda (node)
(if (not (pair? node)) ; can't insert into
node
(append node to-insert)))))
(if (nodeset? node)
(map insert-into-single node)
(insert-into-single node)))))
; Rename
(define (modif:rename new-name)
(let ((rename-single ; renames a single node
(lambda (node)
(if (pair? node) ; named node
(cons new-name (cdr node))
node))))
(lambda (node context base-node)
(if (nodeset? node)
(map rename-single node)
(rename-single node)))))
; Delete
(define modif:delete
(lambda (node context base-node) '()))
(define modif:delete-undeep
(let ((delete-undeep-single
(lambda (node)
(if (pair? node) (cdr node) '()))))
(lambda (node context base-node)
(if (nodeset? node)
(map delete-undeep-single node)
(delete-undeep-single node)))))
;==========================================================================
; Highest-level API function
; update-specifiers ::= (listof update-specifier)
; update-specifier ::= (list xpath-location-path action [action-parametes])
; xpath-location-path - addresses the node(s) to be transformed, in the form of
; XPath location path. If the location path is absolute, it addresses the
; node(s) with respect to the root of the document being transformed. If the
; location path is relative, it addresses the node(s) with respect to the
; node selected by the previous update-specifier. The location path in the
; first update-specifier always addresses the node(s) with respect to the
; root of the document. We'll further refer to the node with respect of which
; the location path is evaluated as to the base-node for this location path.
; action - specifies the modification to be made over each of the node(s)
; addressed by the location path. Possible actions are described below.
; action-parameters - additional parameters supplied for the action. The number
; of parameters and their semantics depend on the definite action.
;
; action ::= 'delete | 'delete-undeep |
; 'insert-into | 'insert-following | 'insert-preceding |
; 'replace |
; 'move-into | 'move-following | 'move-preceding |
; handler
; 'delete - deletes the node. Expects no action-parameters
; 'delete-undeep - deletes the node, but keeps all its content (which thus
; moves to one level upwards in the document tree). Expects no
; action-parameters
; 'insert-into - inserts the new node(s) as the last children of the given
; node. The new node(s) are specified in SXML as action-parameters
; 'insert-following, 'insert-preceding - inserts the new node(s) after (before)
; the given node. Action-parameters are the same as for 'insert-into
; 'replace - replaces the given node with the new node(s). Action-parameters
; are the same as for 'insert-into
; 'rename - renames the given node. The node to be renamed must be a pair (i.e.
; not a text node). A single action-parameter is expected, which is to be
; a Scheme symbol to specify the new name of the given node
; 'move-into - moves the given node to a new location. The single
; action-parameter is the location path, which addresses the new location
; with respect to the given node as the base node. The given node becomes
; the last child of the node selected by the parameter location path.
; 'move-following, 'move-preceding - the given node is moved to the location
; respectively after (before) the node selected by the parameter location
; path
; handler ::= (lambda (node context base-node) ...)
; handler - specifies the required transformation. It is an arbitrary lambda
; that consumes the node and its context (the latter can be used for addressing
; the other node of the source document relative to the given node). The hander
; can return one of the following 2 things: a node or a nodeset.
; 1. If a node is returned, than it replaces the source node in the result
; document
; 2. If a nodeset is returned, than the source node is replaced by (multiple)
; nodes from this nodeset, in the same order in which they appear in the
; nodeset. In particular, if the empty nodeset is returned by the handler, the
; source node is removed from the result document and nothing is inserted
; instead.
;
; Returns either (lambda (doc) ...) or #f
; The latter signals of an error, an the error message is printed into stderr
; as a side effect. In the former case, the lambda can be applied to an SXML
; document and produces the new SXML document being the result of the
; modification specified.
(define (sxml:modify . update-specifiers)
(and-let*
((lambdas-upd-specifiers
(sxml:update-specifiers->lambdas update-specifiers)))
(lambda (doc)
(sxml:transform-document
doc
(sxml:lambdas-upd-specifiers->targets doc lambdas-upd-specifiers)))))
;==========================================================================
; Destructive modifications
;-------------------------------------------------
; Helper cloning facilities
; These are required to avoid circular structures and such as the result of
; destructive modifications
; Clones the given SXML node
(define (sxml:clone node)
(letrec
((clone-nodeset ; clones nodeset
(lambda (nset)
(if (null? nset)
nset
(cons (sxml:clone (car nset)) (cdr nset))))))
(cond
((pair? node)
(cons (car node) (clone-nodeset (cdr node))))
; Atomic node
((string? node)
(string-copy node))
((number? node)
(string->number (number->string node)))
(else ; unknown node type - do not clone it
node))))
; Clones all members of the `nodeset', except for the `node', which is not
; cloned
(define (sxml:clone-nset-except nodeset node)
(letrec
((iter-nset
; encountered? - a boolean value: whether `node' already encountered
; in the head of the nodeset being processed
(lambda (nset encountered?)
(cond
((null? nset) nset)
((eq? (car nset) node)
(cons
(if encountered? ; already encountered before
(sxml:clone (car nset)) ; is to be now cloned
(car nset))
(iter-nset (cdr nset) #t)))
(else
(cons (sxml:clone (car nset))
(iter-nset (cdr nset) encountered?)))))))
(iter-nset nodeset #f)))
;-------------------------------------------------
; Facilities for mutation
; Destructively replaces the next list member for `prev' with the new `lst'
(define (sxml:replace-next-with-lst! prev lst)
(let ((next (cddr prev)))
(if
(null? lst) ; the member is to be just removed
(set-cdr! prev next)
(begin
(set-cdr! prev lst)
(let loop ((lst lst)) ; the lst is non-null
(if
(null? (cdr lst))
(set-cdr! lst next)
(loop (cdr lst))))))))
; Destructively updates the SXML document
; Returns the modified doc
; mutation-lst ::= (listof (cons context new-value)),
; new-value - a nodeset: the new value to be set to the node
(define (sxml:mutate-doc! doc mutation-lst)
(letrec
((tree-walk
(lambda (curr-node targets-alist)
(if
(not (pair? curr-node)) ; an atom
#t ; nothing to do
; Otherwise, the `curr-node' is a pair
(let loop ((lst curr-node)
(targets targets-alist))
(if
(null? targets)
#t ; nothing more to do
(begin
(if ((ntype?? '@) (car lst)) ; attribute node
(tree-walk (car lst) targets-alist)
#t ; dummy else-branch
)
(if
(null? (cdr lst)) ; this is the last member
#t ; nothing more to be done
(let ((next (cadr lst)))
(call-with-values
(lambda ()
(sxml:separate-list
(lambda (pair) (eq? (caar pair) next))
targets))
(lambda (matched ; handlers which match `next'
targets ; the rest
)
(if
(null? matched) ; nothing matched the next node
(loop (cdr lst) targets)
(let ((matched
(map
(lambda (pair) (cons (cdar pair) (cdr pair)))
matched)))
(cond
((assv '() matched) ; the `next' is to be mutated
=> (lambda (pair)
(let ((k (length (cdr pair))))
(sxml:replace-next-with-lst! lst (cdr pair))
(loop (list-tail lst k) targets))))
(else
(tree-walk next matched)
(loop (cdr lst) targets))))))))))))))))
(let ((targets-alist
(map-union
(lambda (pair)
(let ((node-path (reverse (sxml:context->content (car pair)))))
(if
(eq? (car node-path) doc)
(list (cons (cdr node-path) (cdr pair)))
'())))
mutation-lst)))
(cond
((null? targets-alist) ; nothing to do
#t)
((assv '() targets-alist) ; assv is specified for empty lists
; The root of the document itself is to be modified
=> (lambda (pair)
(set! doc (cadr pair))))
(else
(tree-walk doc targets-alist)))
doc)))
;-------------------------------------------------
; Selects the nodes to be mutated (by a subsequent destructive modification)
; This function is the close analog of `sxml:transform-document'
;
; Returns:
; mutation-lst ::= (listof (cons context new-value)),
; new-value - a nodeset: the new value to be set to the node;
; or #f in case of semantic error during tree processing (e.g. not a
; well-formed document after modification)
;
; doc - a source SXML document
; update-targets ::= (listof update-target)
; update-target ::= (list context handler base-node)
; context - context of the node selected by the location path
; handler ::= (lambda (node context base-node) ...)
; handler - specifies the required transformation over the node selected
; base-node - the node with respect to which the location path was evaluated
(define (sxml:nodes-to-mutate doc update-targets)
(letrec
(; targets-alist ::= (listof (cons node-chain update-target))
; node-chain - the chain of nodes, starting from the current node
; anc-upd? - whether an ancestor of the current node us updated
(tree-walk
(lambda (curr-node targets-alist)
(call-with-values
(lambda () (sxml:separate-list
(lambda (pair) (null? (car pair)))
targets-alist))
(lambda (matched ; handlers which match this node
targets ; the rest
)
(if
; No updates both on this level and on ancestor's level
(null? matched)
(let loop ((targets targets-alist)
(subnodes (append (sxml:attr-list curr-node)
((sxml:child sxml:node?) curr-node)))
(res '()))
(if
(or (null? targets) (null? subnodes))
res
(call-with-values
(lambda ()
(sxml:separate-list
(lambda (pair) (eq? (caar pair) (car subnodes)))
targets))
(lambda (matched targets)
(loop targets
(cdr subnodes)
(if
(null? matched)
res
(append res
(tree-walk
(car subnodes)
(map
(lambda (pair) (cons (cdar pair) (cdr pair)))
matched)))))))))
(list
(cons (cadar matched) ; context
(sxml:clone-nset-except
(as-nodeset
(sxml:tree-trans curr-node targets-alist))
curr-node)))))))))
(let ((targets-alist
(map-union
(lambda (triple)
(let ((node-path (reverse (sxml:context->content (car triple)))))
(if
(eq? (car node-path) doc)
(list (cons (cdr node-path) triple))
'())))
update-targets)))
(if (null? targets-alist) ; nothing to do
'()
(tree-walk doc targets-alist)))))
; A highest-level function
(define (sxml:modify! . update-specifiers)
(and-let*
((lambdas-upd-specifiers
(sxml:update-specifiers->lambdas update-specifiers)))
(lambda (doc)
(sxml:mutate-doc!
doc
(sxml:nodes-to-mutate
doc
(sxml:lambdas-upd-specifiers->targets doc lambdas-upd-specifiers))))))