Update \sacredHarpHeads and \aikenHeads to use new sol head
[lilypond/mpolesky.git] / scm / to-xml.scm
bloba552c1f8465304f645cf1158af784bec9255f4e5
1 ;;;; This file is part of LilyPond, the GNU music typesetter.
2 ;;;;
3 ;;;; Copyright (C) 2003--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
4 ;;;;                 Jan Nieuwenhuizen <janneke@gnu.org>
5 ;;;;
6 ;;;; LilyPond is free software: you can redistribute it and/or modify
7 ;;;; it under the terms of the GNU General Public License as published by
8 ;;;; the Free Software Foundation, either version 3 of the License, or
9 ;;;; (at your option) any later version.
10 ;;;;
11 ;;;; LilyPond is distributed in the hope that it will be useful,
12 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ;;;; GNU General Public License for more details.
15 ;;;;
16 ;;;; You should have received a copy of the GNU General Public License
17 ;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 (define-module (scm to-xml))
21 (use-modules (ice-9 regex)
22              (srfi srfi-1)
23              (lily)
24              (oop goops))
27 Todo: this is a quick hack; it makes more sense to define a GOOPS
28 class of a documentnode (similar to how
29 ; the documentation is generated.)
31 That is much cleaner: building the document, and dumping it to output
32 is then separated.
35    foo = \\score { ... }
37    #(as-xml foo)
39    <score>
40      <music></music>
41      <layoutoutput>
42      </layoutoutput>
43    </score>
46 (define-class <xml-node> ()
47   (name #:init-value "" #:accessor node-name #:init-keyword #:name)
48   (value #:init-value "" #:accessor node-value #:init-keyword #:value)
49   (attributes #:init-value '()
50               #:accessor node-attributes
51               #:init-keyword #:attributes)
52   (children #:init-value '()
53             #:accessor node-children
54             #:init-keyword #:children))
56 (define node-names
57   '((NoteEvent . note)
58     (SequentialMusic . measure)
60     ;;ugh
61     (pitch . pitch)
62     (duration . duration)
63     (octave . octave)
64     (step . step)))
66 (define (musicxml-node->string node)
67   (let ((xml-name (assoc-get (node-name node) node-names #f)))
68     (string-append
69      (if xml-name (open-tag xml-name '() '()) "")
70      (if (equal? (node-value node) "")
71          (string-append
72           (if xml-name "\n" "")
73           (apply string-append (map musicxml-node->string (node-children node))))
74          (node-value node))
75      (if xml-name (close-tag xml-name) "")
76      (if xml-name "\n" ""))))
78 (define (xml-node->string node)
79   (string-append
80    "\n"
81    (open-tag (node-name node) (node-attributes node) '())
82    (if (equal? (node-value node) "")
83        (string-append
84         (apply string-append (map xml-node->string (node-children node))))
85        (node-value node))
86    "\n"
87    (close-tag (node-name node))))
89 (define (musicxml-duration->xml-node d)
90   (make <xml-node>
91     #:name 'duration
92     #:value (number->string (ash 1 (ly:duration-log d)))))
94 (define (duration->xml-node d)
95   (make <xml-node>
96     #:name 'duration
97     ;; #:value (number->string (ash 1 (ly:duration-log d)))))
98     #:attributes `((log . ,(ly:duration-log d))
99                    (dots . ,(ly:duration-dot-count d))
100                    (numer . ,(car (ly:duration-factor d)))
101                    (denom . ,(cdr (ly:duration-factor d))))))
103 (define (pitch->xml-node p)
104   (make <xml-node>
105     #:name 'pitch
106     #:attributes `((octave . ,(ly:pitch-octave p))
107                    (notename . ,(ly:pitch-notename p))
108                    (alteration . ,(ly:pitch-alteration p)))))
110 (define (music->xml-node music)
111   (let* ((name (ly:music-property music 'name))
112          (e (ly:music-property music 'element))
113          (es (ly:music-property music 'elements))
114          (mprops (ly:music-mutable-properties music))
115          (d (ly:music-property music 'duration))
116          (p (ly:music-property music 'pitch))
117          (ignore-props '(origin elements duration pitch element)))
118     
119     (make <xml-node>
120       #:name name
121       #:children
122       (apply
123        append
124        (if (ly:pitch? p) (list (pitch->xml-node p)) '())
125        (if (ly:duration? d) (list (duration->xml-node d)) '())
126        (if (pair? es) (map music->xml-node es) '())
127        (if (ly:music? e) (list (music->xml-node e)) '())
128        '()))))
130 (define (dtd-header)
131   (string-append
132    "<?xml version=\"1.0\"?>
133 <!DOCTYPE MUSIC ["
134    preliminary-dtd
135    "
141 ;; as computed from input/trip.ly, by
142 ;; http://www.pault.com/pault/dtdgenerator/
144 ;; must recompute with larger, more serious piece, and probably
145 ;; manually add stuff
146 (define preliminary-dtd
147   "
148 <!ELEMENT duration EMPTY >
149 <!ATTLIST duration denom ( 1 | 3 | 5 ) #REQUIRED >
150 <!ATTLIST duration dots ( 0 | 1 ) #REQUIRED >
151 <!ATTLIST duration log ( 0 | 1 | 2 | 3 | 4 ) #REQUIRED >
152 <!ATTLIST duration numer ( 1 | 4 ) #REQUIRED >
154 <!ELEMENT music ( duration | music | pitch )* >
155 <!ATTLIST music articulation-type ( lheel | ltoe | marcato | rheel | rtoe | staccato | tenuto ) #IMPLIED >
156 <!ATTLIST music change-to-id NMTOKEN #IMPLIED >
157 <!ATTLIST music change-to-type NMTOKEN #IMPLIED >
158 <!ATTLIST music context-id CDATA #IMPLIED >
159 <!ATTLIST music context-type ( PianoStaff | Score | Staff | Timing | Voice ) #IMPLIED >
160 <!ATTLIST music denominator NMTOKEN #IMPLIED >
161 <!ATTLIST music direction ( 0 | 1 ) #IMPLIED >
162 <!ATTLIST music force-accidental CDATA #IMPLIED >
163 <!ATTLIST music grob-property NMTOKEN #IMPLIED >
164 <!ATTLIST music grob-value CDATA #IMPLIED >
165 <!ATTLIST music iterator-ctor CDATA #IMPLIED >
166 <!ATTLIST music label NMTOKEN #IMPLIED >
167 <!ATTLIST music last-pitch CDATA #IMPLIED >
168 <!ATTLIST music numerator NMTOKEN #IMPLIED >
169 <!ATTLIST music penalty NMTOKEN #IMPLIED >
170 <!ATTLIST music pitch-alist CDATA #IMPLIED >
171 <!ATTLIST music pop-first CDATA #IMPLIED >
172 <!ATTLIST music repeat-count NMTOKEN #IMPLIED >
173 <!ATTLIST music span-direction ( -1 | 1 ) #IMPLIED >
174 <!ATTLIST music span-type NMTOKEN #IMPLIED >
175 <!ATTLIST music symbol NMTOKEN #IMPLIED >
176 <!ATTLIST music text NMTOKEN #IMPLIED >
177 <!ATTLIST music text-type NMTOKEN #IMPLIED >
178 <!ATTLIST music type NMTOKEN #REQUIRED >
179 <!ATTLIST music value CDATA #IMPLIED >
181 <!ELEMENT pitch EMPTY >
182 <!ATTLIST pitch alteration ( 0 | 1 ) #REQUIRED >
183 <!ATTLIST pitch notename ( 0 | 1 | 2 | 3 | 4 | 5 | 6 ) #REQUIRED >
184 <!ATTLIST pitch octave ( -1 | -2 | 0 | 1 ) #REQUIRED >")
187 ;; should use macro
188 (define (assert x)
189   (if x
190       #t
191       (ly:error (_ "assertion failed: ~S") x)))
193 (define (re-sub re to string)
194   (regexp-substitute/global #f re string 'pre to 'post))
196 (define (re-sub-alist string alist)
197   (if (null? alist)
198       string
199       (re-sub (caar alist) (cdar alist)
200               (re-sub-alist string (cdr alist)))))
202 (define xml-entities-alist
203   '(("\"" . "&quot;")
204     ("<" . "&lt;")
205     (">" . "&gt;")
206     ("'" . "&apos;")
207     ("&" . "&amp;")))
209 (define (open-tag tag attrs exceptions)
210   (define (candidate? x)
211     (not (memq (car x) exceptions)))
212   
213   (define (dump-attr sym-val)
214     (let* ((sym (car sym-val))
215            (val (cdr sym-val)))
216       
217       (string-append
218        "\n   "
219        (symbol->string sym)
220        "=\""
221        (let ((s (call-with-output-string (lambda (port) (display val port)))))
222          (re-sub-alist s xml-entities-alist))
223        "\"")))
225   (string-append
226    "<" (symbol->string tag)
227    (apply string-append (map dump-attr (filter candidate? attrs)))
228    ">"))
230 (define (close-tag name)
231   (string-append "</" (symbol->string name) ">"))
233 (define-public (music-to-xml music port)
234   "Dump XML-ish stuff to PORT."
236   ;; dtd contains # -- This confuses tex during make doc.
237   ;;
238   ;;  (display (dtd-header) port)
239   
240   (display (open-tag 'music '((type . score)) '()) port)
241   (display (xml-node->string (music->xml-node music)) port)
242   (display (close-tag 'music) port))
244 (define-public (music-to-musicxml music port)
245   "Dump MusicXML-ish stuff to PORT."
247   ;; dtd contains # -- This confuses tex during make doc.
248   ;;
249   ;;  (display (dtd-header) port)
251   (define duration->xml-node musicxml-duration->xml-node)
252   
253   (display (open-tag 'music '((type . score)) '()) port)
254   (display (musicxml-node->string (music->xml-node music)) port)
255   (display (close-tag 'music) port))