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