]> git.donarmstrong.com Git - lilypond.git/blob - scm/to-xml.scm
afe8084170a7d6a68a635eabbc1678a010b48294
[lilypond.git] / scm / to-xml.scm
1
2 (use-modules (ice-9 regex)
3              (srfi srfi-1)
4              (oop goops))
5
6 "
7 Todo: this is a quick hack; it makes more sense to define a GOOPS
8 class of a documentnode (similar to how
9 ; the documentation is generated.)
10
11 That is much cleaner: building the document, and dumping it to output
12 is then separated.
13
14
15    foo = \\score { ... }
16
17    #(as-xml foo)
18
19    <score>
20      <music></music>
21      <paperoutput>
22      </paperoutput>
23    </score>
24 "
25
26 (define-class <xml-node> ()
27   (name #:init-value "" #:accessor node-name #:init-keyword #:name)
28   (value #:init-value "" #:accessor node-value #:init-keyword #:value)
29   (attributes #:init-value '()
30               #:accessor node-attributes
31               #:init-keyword #:attributes)
32   (children #:init-value '()
33             #:accessor node-children
34             #:init-keyword #:children))
35
36 (define node-names
37   '((NoteEvent . note)
38     (SequentialMusic . measure)
39
40     ;;ugh
41     (pitch . pitch)
42     (duration . duration)
43     (octave . octave)
44     (step . step)
45     ))
46
47 (define (assoc-get-default key alist default)
48   "Return value if KEY in ALIST, else DEFAULT."
49   (let ((entry (assoc key alist)))
50     (if entry (cdr entry) default)))
51
52 (define (musicxml-node->string node)
53   (let ((xml-name (assoc-get-default (node-name node) node-names #f)))
54   (string-append
55    (if xml-name (open-tag xml-name '() '()) "")
56    (if (equal? (node-value node) "")
57        (string-append
58         (if xml-name "\n" "")
59         (apply string-append (map musicxml-node->string (node-children node))))
60        (node-value node))
61    (if xml-name (close-tag xml-name) "")
62    (if xml-name "\n" ""))))
63
64 (define (xml-node->string node)
65   (string-append
66    "\n"
67    (open-tag (node-name node) (node-attributes node) '())
68    (if (equal? (node-value node) "")
69        (string-append
70         (apply string-append (map xml-node->string (node-children node))))
71        (node-value node))
72    "\n"
73    (close-tag (node-name node))))
74
75 (define (musicxml-duration->xml-node d)
76   (make <xml-node>
77     #:name 'duration
78     #:value (number->string (ash 1 (ly:duration-log d)))))
79
80 (define (duration->xml-node d)
81   (make <xml-node>
82     #:name 'duration
83     ;; #:value (number->string (ash 1 (ly:duration-log d)))))
84     #:attributes `((log . ,(ly:duration-log d))
85                    (dots . ,(ly:duration-dot-count d))
86                    (numer . ,(car (ly:duration-factor d)))
87                    (denom . ,(cdr (ly:duration-factor d))))))
88
89 (define (musicxml-pitch->xml-node p)
90   (make <xml-node>
91     #:name 'pitch
92     #:children
93     (list
94      (make <xml-node>
95        #:name 'step
96        #:value (list-ref  '("C" "D" "E" "F" "G" "A" "B")
97                           (ly:pitch-notename p)))
98      (make <xml-node>
99        #:name 'octave
100        #:value (number->string (ly:pitch-octave p))))))
101
102 (define (pitch->xml-node p)
103   (make <xml-node>
104     #:name 'pitch
105     #:attributes `((octave . ,(ly:pitch-octave p))
106                    (notename . ,(ly:pitch-notename p))
107                    (alteration . ,(ly:pitch-alteration p)))))
108                                
109 (define (music->xml-node music)
110   (let* ((name (ly:music-property music 'name))
111          (e (ly:music-property music 'element))
112          (es (ly:music-property music 'elements))
113          (mprops (ly:get-mutable-properties music))
114          (d (ly:music-property music 'duration))
115          (p (ly:music-property music 'pitch))
116          (ignore-props '(origin elements duration pitch element)))
117     
118     (make <xml-node>
119       #:name name
120       #:children
121       (apply
122        append
123        (if (ly:pitch? p) (list (pitch->xml-node p)) '())
124        (if (ly:duration? d) (list (duration->xml-node d)) '())
125        (if (pair? es) (map music->xml-node es) '())
126        (if (ly:music? e) (list (music->xml-node e)) '())
127        '()))))
128
129 (define (dtd-header)
130   (string-append
131    "<?xml version=\"1.0\"?>
132 <!DOCTYPE MUSIC ["
133    preliminary-dtd
134    "
135 ]>
136
137 "))
138   
139  
140 ;; as computed from input/trip.ly, by
141 ;; http://www.pault.com/pault/dtdgenerator/
142
143 ;; must recompute with larger, more serious piece, and probably
144 ;; manually add stuff
145 (define preliminary-dtd
146   "
147 <!ELEMENT duration EMPTY >
148 <!ATTLIST duration denom ( 1 | 3 | 5 ) #REQUIRED >
149 <!ATTLIST duration dots ( 0 | 1 ) #REQUIRED >
150 <!ATTLIST duration log ( 0 | 1 | 2 | 3 | 4 ) #REQUIRED >
151 <!ATTLIST duration numer ( 1 | 4 ) #REQUIRED >
152
153 <!ELEMENT music ( duration | music | pitch )* >
154 <!ATTLIST music articulation-type ( lheel | ltoe | marcato | rheel | rtoe | staccato | tenuto ) #IMPLIED >
155 <!ATTLIST music change-to-id NMTOKEN #IMPLIED >
156 <!ATTLIST music change-to-type NMTOKEN #IMPLIED >
157 <!ATTLIST music context-id CDATA #IMPLIED >
158 <!ATTLIST music context-type ( PianoStaff | Score | Staff | Timing | Voice ) #IMPLIED >
159 <!ATTLIST music denominator NMTOKEN #IMPLIED >
160 <!ATTLIST music direction ( 0 | 1 ) #IMPLIED >
161 <!ATTLIST music force-accidental CDATA #IMPLIED >
162 <!ATTLIST music grob-property NMTOKEN #IMPLIED >
163 <!ATTLIST music grob-value CDATA #IMPLIED >
164 <!ATTLIST music iterator-ctor CDATA #IMPLIED >
165 <!ATTLIST music label NMTOKEN #IMPLIED >
166 <!ATTLIST music last-pitch CDATA #IMPLIED >
167 <!ATTLIST music numerator NMTOKEN #IMPLIED >
168 <!ATTLIST music penalty NMTOKEN #IMPLIED >
169 <!ATTLIST music pitch-alist CDATA #IMPLIED >
170 <!ATTLIST music pop-first CDATA #IMPLIED >
171 <!ATTLIST music repeat-count NMTOKEN #IMPLIED >
172 <!ATTLIST music span-direction ( -1 | 1 ) #IMPLIED >
173 <!ATTLIST music span-type NMTOKEN #IMPLIED >
174 <!ATTLIST music symbol NMTOKEN #IMPLIED >
175 <!ATTLIST music text NMTOKEN #IMPLIED >
176 <!ATTLIST music text-type NMTOKEN #IMPLIED >
177 <!ATTLIST music type NMTOKEN #REQUIRED >
178 <!ATTLIST music value CDATA #IMPLIED >
179
180 <!ELEMENT pitch EMPTY >
181 <!ATTLIST pitch alteration ( 0 | 1 ) #REQUIRED >
182 <!ATTLIST pitch notename ( 0 | 1 | 2 | 3 | 4 | 5 | 6 ) #REQUIRED >
183 <!ATTLIST pitch octave ( -1 | -2 | 0 | 1 ) #REQUIRED >")
184
185
186 ;; should use macro
187 (define (assert x)
188   (if x
189       #t
190       (error "assertion failed")))
191
192 (define (re-sub re to string)
193   (regexp-substitute/global #f re string 'pre to 'post))
194
195 (define (re-sub-alist string alist)
196   (if (null? alist)
197       string
198       (re-sub (caar alist) (cdar alist)
199               (re-sub-alist string (cdr alist)))))
200
201 (define xml-entities-alist
202   '(("\"" . "&quot;")
203     ("<" . "&lt;")
204     (">" . "&gt;")
205     ("'" . "&apos;")
206     ("&" . "&amp;")))
207
208 (define (open-tag tag attrs exceptions)
209   (define (candidate? x)
210     (not (memq (car x) exceptions)))
211   
212   (define (dump-attr sym-val)
213     (let*
214         (
215         (sym (car sym-val))
216         (val (cdr sym-val))
217         )
218       
219     (string-append
220      "\n   "
221     (symbol->string sym)
222     "=\""
223     (let ((s (call-with-output-string (lambda (port) (display val port)))))
224       (re-sub-alist s xml-entities-alist))
225     "\""
226     )))
227
228   (string-append
229    "<" (symbol->string tag)
230    (apply string-append (map dump-attr (filter candidate? attrs)))
231    ">"))
232
233 (define (close-tag name)
234   (string-append "</" (symbol->string name) ">"))
235
236 (define-public (music-to-xml music port)
237   "Dump XML-ish stuff to PORT."
238
239   ;; dtd contains # -- This confuses tex during make web.
240   ;;
241   ;;  (display (dtd-header) port)
242   
243   (display (open-tag 'music '((type . score)) '()) port)
244   (display (xml-node->string (music->xml-node music)) port)
245   (display (close-tag 'music) port))
246
247 (define-public (music-to-musicxml music port)
248   "Dump MusicXML-ish stuff to PORT."
249
250   ;; dtd contains # -- This confuses tex during make web.
251   ;;
252   ;;  (display (dtd-header) port)
253
254   (define pitch->xml-node musicxml-pitch->xml-node)
255   (define duration->xml-node musicxml-duration->xml-node)
256   
257   (display (open-tag 'music '((type . score)) '()) port)
258   (display (musicxml-node->string (music->xml-node music)) port)
259   (display (close-tag 'music) port))
260