]> git.donarmstrong.com Git - lilypond.git/blob - python/musicxml.py
MusicXML: convert <miscellaneous-field name='description'> tag to texidoc header
[lilypond.git] / python / musicxml.py
1 # -*- coding: utf-8 -*-
2 import new
3 import string
4 from rational import *
5 import re
6 import sys
7 import copy
8 import lilylib as ly
9
10 _ = ly._
11
12 def error (str):
13     ly.stderr_write ((_ ("error: %s") % str) + "\n")
14
15
16 def escape_ly_output_string (input_string):
17     return_string = input_string
18     needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string);
19     if needs_quotes:
20         return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
21     return return_string
22
23
24 def musicxml_duration_to_log (dur):
25     return  {'256th': 8,
26              '128th': 7,
27              '64th': 6,
28              '32nd': 5,
29              '16th': 4,
30              'eighth': 3,
31              'quarter': 2,
32              'half': 1,
33              'whole': 0,
34              'breve': -1,
35              'longa': -2,
36              'long': -2}.get (dur, 0)
37
38
39
40 class Xml_node:
41     def __init__ (self):
42         self._children = []
43         self._data = None
44         self._original = None
45         self._name = 'xml_node'
46         self._parent = None
47         self._attribute_dict = {}
48         
49     def get_parent (self):
50         return self._parent
51     
52     def is_first (self):
53         return self._parent.get_typed_children (self.__class__)[0] == self
54
55     def original (self):
56         return self._original 
57     def get_name (self):
58         return self._name
59
60     def get_text (self):
61         if self._data:
62             return self._data
63
64         if not self._children:
65             return ''
66
67         return ''.join ([c.get_text () for c in self._children])
68
69     def message (self, msg):
70         ly.stderr_write (msg+'\n')
71
72         p = self
73         while p:
74             sys.stderr.write ('  In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items ()])))
75             p = p.get_parent ()
76         
77     def get_typed_children (self, klass):
78         if not klass:
79             return []
80         else:
81             return [c for c in self._children if isinstance(c, klass)]
82
83     def get_named_children (self, nm):
84         return self.get_typed_children (get_class (nm))
85
86     def get_named_child (self, nm):
87         return self.get_maybe_exist_named_child (nm)
88
89     def get_children (self, predicate):
90         return [c for c in self._children if predicate(c)]
91
92     def get_all_children (self):
93         return self._children
94
95     def get_maybe_exist_named_child (self, name):
96         return self.get_maybe_exist_typed_child (get_class (name))
97
98     def get_maybe_exist_typed_child (self, klass):
99         cn = self.get_typed_children (klass)
100         if len (cn)==0:
101             return None
102         elif len (cn) == 1:
103             return cn[0]
104         else:
105             raise "More than 1 child", klass
106
107     def get_unique_typed_child (self, klass):
108         cn = self.get_typed_children(klass)
109         if len (cn) <> 1:
110             sys.stderr.write (self.__dict__ + '\n')
111             raise 'Child is not unique for', (klass, 'found', cn)
112
113         return cn[0]
114
115     def get_named_child_value_number (self, name, default):
116         n = self.get_maybe_exist_named_child (name)
117         if n:
118             return string.atoi (n.get_text())
119         else:
120             return default
121
122
123 class Music_xml_node (Xml_node):
124     def __init__ (self):
125         Xml_node.__init__ (self)
126         self.duration = Rational (0)
127         self.start = Rational (0)
128
129 class Work (Xml_node):
130     def get_work_information (self, tag):
131         wt = self.get_maybe_exist_named_child (tag)
132         if wt:
133             return wt.get_text ()
134         else:
135             return ''
136       
137     def get_work_title (self):
138         return self.get_work_information ('work-title')
139     def get_work_number (self):
140         return self.get_work_information ('work-number')
141     def get_opus (self):
142         return self.get_work_information ('opus')
143
144 class Identification (Xml_node):
145     def get_rights (self):
146         rights = self.get_maybe_exist_named_child ('rights')
147         if rights:
148             return rights.get_text ()
149         else:
150             return ''
151
152     def get_creator (self, type):
153         creators = self.get_named_children ('creator')
154         # return the first creator tag that has the particular type
155         for i in creators:
156             if hasattr (i, 'type') and i.type == type:
157                 return i.get_text ()
158         return None
159
160     def get_composer (self):
161         c = self.get_creator ('composer')
162         if c:
163             return c
164         creators = self.get_named_children ('creator')
165         # return the first creator tag that has no type at all
166         for i in creators:
167             if not hasattr (i, 'type'):
168                 return i.get_text ()
169         return None
170     def get_arranger (self):
171         return self.get_creator ('arranger')
172     def get_editor (self):
173         return self.get_creator ('editor')
174     def get_poet (self):
175         v = self.get_creator ('lyricist')
176         if v:
177             return v
178         v = self.get_creator ('poet')
179         return v
180     
181     def get_encoding_information (self, type):
182         enc = self.get_named_children ('encoding')
183         if enc:
184             children = enc[0].get_named_children (type)
185             if children:
186                 return children[0].get_text ()
187         else:
188             return None
189       
190     def get_encoding_software (self):
191         return self.get_encoding_information ('software')
192     def get_encoding_date (self):
193         return self.get_encoding_information ('encoding-date')
194     def get_encoding_person (self):
195         return self.get_encoding_information ('encoder')
196     def get_encoding_description (self):
197         return self.get_encoding_information ('encoding-description')
198     
199     def get_encoding_software_list (self):
200         enc = self.get_named_children ('encoding')
201         software = []
202         for e in enc:
203             softwares = e.get_named_children ('software')
204             for s in softwares:
205                 software.append (s.get_text ())
206         return software
207
208     def get_file_description (self):
209         misc = self.get_named_children ('miscellaneous')
210         for m in misc:
211             misc_fields = m.get_named_children ('miscellaneous-field')
212             for mf in misc_fields:
213                 if hasattr (mf, 'name') and mf.name == 'description':
214                     return mf.get_text () 
215         return None
216
217
218
219 class Duration (Music_xml_node):
220     def get_length (self):
221         dur = int (self.get_text ()) * Rational (1,4)
222         return dur
223
224 class Hash_comment (Music_xml_node):
225     pass
226 class Hash_text (Music_xml_node):
227     pass
228
229 class Pitch (Music_xml_node):
230     def get_step (self):
231         ch = self.get_unique_typed_child (get_class (u'step'))
232         step = ch.get_text ().strip ()
233         return step
234     def get_octave (self):
235         ch = self.get_unique_typed_child (get_class (u'octave'))
236
237         step = ch.get_text ().strip ()
238         return int (step)
239
240     def get_alteration (self):
241         ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
242         alter = 0
243         if ch:
244             alter = int (ch.get_text ().strip ())
245         return alter
246
247 class Unpitched (Music_xml_node):
248     def get_step (self):
249         ch = self.get_unique_typed_child (get_class (u'display-step'))
250         step = ch.get_text ().strip ()
251         return step
252
253     def get_octave (self):
254         ch = self.get_unique_typed_child (get_class (u'display-octave'))
255
256         if ch:
257             octave = ch.get_text ().strip ()
258             return int (octave)
259         else:
260             return None
261
262 class Measure_element (Music_xml_node):
263     def get_voice_id (self):
264         voice_id = self.get_maybe_exist_named_child ('voice')
265         if voice_id:
266             return voice_id.get_text ()
267         else:
268             return None
269
270     def is_first (self):
271         # Look at all measure elements (previously we had self.__class__, which
272         # only looked at objects of the same type!
273         cn = self._parent.get_typed_children (Measure_element)
274         # But only look at the correct voice; But include Attributes, too, which
275         # are not tied to any particular voice
276         cn = [c for c in cn if (c.get_voice_id () == self.get_voice_id ()) or isinstance (c, Attributes)]
277         return cn[0] == self
278
279 class Attributes (Measure_element):
280     def __init__ (self):
281         Measure_element.__init__ (self)
282         self._dict = {}
283         self._original_tag = None
284
285     def is_first (self):
286         cn = self._parent.get_typed_children (self.__class__)
287         if self._original_tag:
288             return cn[0] == self._original_tag
289         else:
290             return cn[0] == self
291     
292     def set_attributes_from_previous (self, dict):
293         self._dict.update (dict)
294         
295     def read_self (self):
296         for c in self.get_all_children ():
297             self._dict[c.get_name()] = c
298
299     def get_named_attribute (self, name):
300         return self._dict.get (name)
301
302     def get_measure_length (self):
303         (n,d) = self.get_time_signature ()
304         return Rational (n,d)
305         
306     def get_time_signature (self):
307         "return time sig as a (beat, beat-type) tuple"
308
309         try:
310             mxl = self.get_named_attribute ('time')
311             if mxl:
312                 beats = mxl.get_maybe_exist_named_child ('beats')
313                 type = mxl.get_maybe_exist_named_child ('beat-type')
314                 return (int (beats.get_text ()),
315                         int (type.get_text ()))
316             else:
317                 return (4, 4)
318         except KeyError:
319             error (_ ("requested time signature, but time sig is unknown"))
320             return (4, 4)
321
322     # returns clef information in the form ("cleftype", position, octave-shift)
323     def get_clef_information (self):
324         clefinfo = ['G', 2, 0]
325         mxl = self.get_named_attribute ('clef')
326         if not mxl:
327             return clefinfo
328         sign = mxl.get_maybe_exist_named_child ('sign')
329         if sign:
330             clefinfo[0] = sign.get_text()
331         line = mxl.get_maybe_exist_named_child ('line')
332         if line:
333             clefinfo[1] = string.atoi (line.get_text ())
334         octave = mxl.get_maybe_exist_named_child ('clef-octave-change')
335         if octave:
336             clefinfo[2] = string.atoi (octave.get_text ())
337         return clefinfo
338
339     def get_key_signature (self):
340         "return (fifths, mode) tuple"
341
342         key = self.get_named_attribute ('key')
343         mode_node = key.get_maybe_exist_named_child ('mode')
344         mode = 'major'
345         if mode_node:
346             mode = mode_node.get_text ()
347
348         fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
349         return (fifths, mode)
350
351 class Barline (Measure_element):
352     pass
353 class BarStyle (Music_xml_node):
354     pass
355 class Partial (Measure_element):
356     def __init__ (self, partial):
357         Measure_element.__init__ (self)
358         self.partial = partial
359
360 class Note (Measure_element):
361     def __init__ (self):
362         Measure_element.__init__ (self)
363         self.instrument_name = ''
364         self._after_grace = False
365     def is_grace (self):
366         return self.get_maybe_exist_named_child (u'grace')
367     def is_after_grace (self):
368         if not self.is_grace():
369             return False;
370         gr = self.get_maybe_exist_typed_child (Grace)
371         return self._after_grace or hasattr (gr, 'steal-time-previous');
372
373     def get_duration_log (self):
374         ch = self.get_maybe_exist_named_child (u'type')
375
376         if ch:
377             log = ch.get_text ().strip()
378             return musicxml_duration_to_log (log)
379         elif self.get_maybe_exist_named_child (u'grace'):
380             # FIXME: is it ok to default to eight note for grace notes?
381             return 3
382         else:
383             self.message (_ ("Encountered note at %s with %s duration (no <type> element):") % (self.start, self.duration) )
384             return 0
385
386     def get_factor (self):
387         return 1
388
389     def get_pitches (self):
390         return self.get_typed_children (get_class (u'pitch'))
391
392 class Part_list (Music_xml_node):
393     def __init__ (self):
394         Music_xml_node.__init__ (self)
395         self._id_instrument_name_dict = {}
396         
397     def generate_id_instrument_dict (self):
398
399         ## not empty to make sure this happens only once.
400         mapping = {1: 1}
401         for score_part in self.get_named_children ('score-part'):
402             for instr in score_part.get_named_children ('score-instrument'):
403                 id = instr.id
404                 name = instr.get_named_child ("instrument-name")
405                 mapping[id] = name.get_text ()
406
407         self._id_instrument_name_dict = mapping
408
409     def get_instrument (self, id):
410         if not self._id_instrument_name_dict:
411             self.generate_id_instrument_dict()
412
413         instrument_name = self._id_instrument_name_dict.get (id)
414         if instrument_name:
415             return instrument_name
416         else:
417             ly.stderr_write (_ ("Unable to find instrument for ID=%s\n") % id)
418             return "Grand Piano"
419
420 class Part_group (Music_xml_node):
421     pass
422 class Score_part (Music_xml_node):
423     pass
424         
425 class Measure (Music_xml_node):
426     def __init__ (self):
427         Music_xml_node.__init__ (self)
428         self.partial = 0
429     def is_implicit (self):
430         return hasattr (self, 'implicit') and self.implicit == 'yes'
431     def get_notes (self):
432         return self.get_typed_children (get_class (u'note'))
433
434 class Syllabic (Music_xml_node):
435     def continued (self):
436         text = self.get_text()
437         return (text == "begin") or (text == "middle")
438 class Text (Music_xml_node):
439     pass
440
441 class Lyric (Music_xml_node):
442     def get_number (self):
443         if hasattr (self, 'number'):
444             return self.number
445         else:
446             return -1
447
448     def lyric_to_text (self):
449         continued = False
450         syllabic = self.get_maybe_exist_typed_child (Syllabic)
451         if syllabic:
452             continued = syllabic.continued ()
453         text = self.get_maybe_exist_typed_child (Text)
454         
455         if text:
456             text = text.get_text()
457             # We need to convert soft hyphens to -, otherwise the ascii codec as well
458             # as lilypond will barf on that character
459             text = string.replace( text, u'\xad', '-' )
460         
461         if text == "-" and continued:
462             return "--"
463         elif text == "_" and continued:
464             return "__"
465         elif continued and text:
466             return escape_ly_output_string (text) + " --"
467         elif continued:
468             return "--"
469         elif text:
470             return escape_ly_output_string (text)
471         else:
472             return ""
473
474 class Musicxml_voice:
475     def __init__ (self):
476         self._elements = []
477         self._staves = {}
478         self._start_staff = None
479         self._lyrics = []
480         self._has_lyrics = False
481
482     def add_element (self, e):
483         self._elements.append (e)
484         if (isinstance (e, Note)
485             and e.get_maybe_exist_typed_child (Staff)):
486             name = e.get_maybe_exist_typed_child (Staff).get_text ()
487
488             if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
489                 self._start_staff = name
490             self._staves[name] = True
491
492         lyrics = e.get_typed_children (Lyric)
493         if not self._has_lyrics:
494           self.has_lyrics = len (lyrics) > 0
495
496         for l in lyrics:
497             nr = l.get_number()
498             if (nr > 0) and not (nr in self._lyrics):
499                 self._lyrics.append (nr)
500
501     def insert (self, idx, e):
502         self._elements.insert (idx, e)
503
504     def get_lyrics_numbers (self):
505         if (len (self._lyrics) == 0) and self._has_lyrics:
506             #only happens if none of the <lyric> tags has a number attribute
507             return [1]
508         else:
509             return self._lyrics
510
511
512 class Part (Music_xml_node):
513     def __init__ (self):
514         Music_xml_node.__init__ (self)
515         self._voices = {}
516         self._staff_attributes_dict = {}
517
518     def get_part_list (self):
519         n = self
520         while n and n.get_name() != 'score-partwise':
521             n = n._parent
522
523         return n.get_named_child ('part-list')
524         
525     def interpret (self):
526         """Set durations and starting points."""
527         """The starting point of the very first note is 0!"""
528         
529         part_list = self.get_part_list ()
530         
531         now = Rational (0)
532         factor = Rational (1)
533         attributes_dict = {}
534         attributes_object = None
535         measures = self.get_typed_children (Measure)
536         last_moment = Rational (-1)
537         last_measure_position = Rational (-1)
538         measure_position = Rational (0)
539         measure_start_moment = now
540         is_first_measure = True
541         previous_measure = None
542         # Graces at the end of a measure need to have their position set to the
543         # previous number!
544         pending_graces = []
545         for m in measures:
546             # implicit measures are used for artificial measures, e.g. when
547             # a repeat bar line splits a bar into two halves. In this case,
548             # don't reset the measure position to 0. They are also used for
549             # upbeats (initial value of 0 fits these, too).
550             # Also, don't reset the measure position at the end of the loop,
551             # but rather when starting the next measure (since only then do we
552             # know if the next measure is implicit and continues that measure)
553             if not m.is_implicit ():
554                 # Warn about possibly overfull measures and reset the position
555                 if attributes_object and previous_measure and previous_measure.partial == 0:
556                     length = attributes_object.get_measure_length ()
557                     new_now = measure_start_moment + length
558                     if now <> new_now:
559                         problem = 'incomplete'
560                         if now > new_now:
561                             problem = 'overfull'
562                         ## only for verbose operation.
563                         if problem <> 'incomplete' and previous_measure:
564                             previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
565                     now = new_now
566                 measure_start_moment = now
567                 measure_position = Rational (0)
568
569             for n in m.get_all_children ():
570                 # figured bass has a duration, but applies to the next note
571                 # and should not change the current measure position!
572                 if isinstance (n, FiguredBass):
573                     n._divisions = factor.denominator ()
574                     n._when = now
575                     n._measure_position = measure_position
576                     continue
577
578                 if isinstance (n, Hash_text):
579                     continue
580                 dur = Rational (0)
581
582                 if n.__class__ == Attributes:
583                     n.set_attributes_from_previous (attributes_dict)
584                     n.read_self ()
585                     attributes_dict = n._dict.copy ()
586                     attributes_object = n
587                     
588                     factor = Rational (1,
589                                        int (attributes_dict.get ('divisions').get_text ()))
590
591                 
592                 if (n.get_maybe_exist_typed_child (Duration)):
593                     mxl_dur = n.get_maybe_exist_typed_child (Duration)
594                     dur = mxl_dur.get_length () * factor
595                     
596                     if n.get_name() == 'backup':
597                         dur = - dur
598                         # reset all graces before the backup to after-graces:
599                         for n in pending_graces:
600                             n._when = n._prev_when
601                             n._measure_position = n._prev_measure_position
602                             n._after_grace = True
603                         pending_graces = []
604                     if n.get_maybe_exist_typed_child (Grace):
605                         dur = Rational (0)
606
607                     rest = n.get_maybe_exist_typed_child (Rest)
608                     if (rest
609                         and attributes_object
610                         and attributes_object.get_measure_length () == dur):
611
612                         rest._is_whole_measure = True
613
614                 if (dur > Rational (0) 
615                     and n.get_maybe_exist_typed_child (Chord)):
616                     now = last_moment
617                     measure_position = last_measure_position
618
619                 n._when = now
620                 n._measure_position = measure_position
621
622                 # For all grace notes, store the previous note,  in case need
623                 # to turn the grace note into an after-grace later on!
624                 if isinstance(n, Note) and n.is_grace ():
625                     n._prev_when = last_moment
626                     n._prev_measure_position = last_measure_position
627                 # After-graces are placed at the same position as the previous note
628                 if isinstance(n, Note) and  n.is_after_grace ():
629                     # TODO: We should do the same for grace notes at the end of 
630                     # a measure with no following note!!!
631                     n._when = last_moment
632                     n._measure_position = last_measure_position
633                 elif isinstance(n, Note) and n.is_grace ():
634                     pending_graces.append (n)
635                 elif (dur > Rational (0)):
636                     pending_graces = [];
637
638                 n._duration = dur
639                 if dur > Rational (0):
640                     last_moment = now
641                     last_measure_position = measure_position
642                     now += dur
643                     measure_position += dur
644                 elif dur < Rational (0):
645                     # backup element, reset measure position
646                     now += dur
647                     measure_position += dur
648                     if measure_position < 0:
649                         # backup went beyond the measure start => reset to 0
650                         now -= measure_position
651                         measure_position = 0
652                     last_moment = now
653                     last_measure_position = measure_position
654                 if n._name == 'note':
655                     instrument = n.get_maybe_exist_named_child ('instrument')
656                     if instrument:
657                         n.instrument_name = part_list.get_instrument (instrument.id)
658
659             # reset all graces at the end of the measure to after-graces:
660             for n in pending_graces:
661                 n._when = n._prev_when
662                 n._measure_position = n._prev_measure_position
663                 n._after_grace = True
664             pending_graces = []
665             # Incomplete first measures are not padded, but registered as partial
666             if is_first_measure:
667                 is_first_measure = False
668                 # upbeats are marked as implicit measures
669                 if attributes_object and m.is_implicit ():
670                     length = attributes_object.get_measure_length ()
671                     measure_end = measure_start_moment + length
672                     if measure_end <> now:
673                         m.partial = now
674             previous_measure = m
675
676     # modify attributes so that only those applying to the given staff remain
677     def extract_attributes_for_staff (part, attr, staff):
678         attributes = copy.copy (attr)
679         attributes._children = [];
680         attributes._dict = attr._dict.copy ()
681         attributes._original_tag = attr
682         # copy only the relevant children over for the given staff
683         for c in attr._children:
684             if (not (hasattr (c, 'number') and (c.number != staff)) and
685                 not (isinstance (c, Hash_text))):
686                 attributes._children.append (c)
687         if not attributes._children:
688             return None
689         else:
690             return attributes
691
692     def extract_voices (part):
693         voices = {}
694         measures = part.get_typed_children (Measure)
695         elements = []
696         for m in measures:
697             if m.partial > 0:
698                 elements.append (Partial (m.partial))
699             elements.extend (m.get_all_children ())
700         # make sure we know all voices already so that dynamics, clefs, etc.
701         # can be assigned to the correct voices
702         voice_to_staff_dict = {}
703         for n in elements:
704             voice_id = n.get_maybe_exist_named_child (u'voice')
705             vid = None
706             if voice_id:
707                 vid = voice_id.get_text ()
708
709             staff_id = n.get_maybe_exist_named_child (u'staff')
710             sid = None
711             if staff_id:
712                 sid = staff_id.get_text ()
713             else:
714                 sid = "None"
715             if vid and not voices.has_key (vid):
716                 voices[vid] = Musicxml_voice()
717             if vid and sid and not n.get_maybe_exist_typed_child (Grace):
718                 if not voice_to_staff_dict.has_key (vid):
719                     voice_to_staff_dict[vid] = sid
720         # invert the voice_to_staff_dict into a staff_to_voice_dict (since we
721         # need to assign staff-assigned objects like clefs, times, etc. to
722         # all the correct voices. This will never work entirely correct due
723         # to staff-switches, but that's the best we can do!
724         staff_to_voice_dict = {}
725         for (v,s) in voice_to_staff_dict.items ():
726             if not staff_to_voice_dict.has_key (s):
727                 staff_to_voice_dict[s] = [v]
728             else:
729                 staff_to_voice_dict[s].append (v)
730
731
732         start_attr = None
733         assign_to_next_note = []
734         id = None
735         for n in elements:
736             voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
737
738             if not (voice_id or isinstance (n, Attributes) or
739                     isinstance (n, Direction) or isinstance (n, Partial) or
740                     isinstance (n, Barline) or isinstance (n, Harmony) or
741                     isinstance (n, FiguredBass) ):
742                 continue
743
744             if isinstance (n, Attributes) and not start_attr:
745                 start_attr = n
746                 continue
747
748             if isinstance (n, Attributes):
749                 # assign these only to the voices they really belongs to!
750                 for (s, vids) in staff_to_voice_dict.items ():
751                     staff_attributes = part.extract_attributes_for_staff (n, s)
752                     if staff_attributes:
753                         for v in vids:
754                             voices[v].add_element (staff_attributes)
755                 continue
756
757             if isinstance (n, Partial) or isinstance (n, Barline):
758                 for v in voices.keys ():
759                     voices[v].add_element (n)
760                 continue
761
762             if isinstance (n, Direction):
763                 staff_id = n.get_maybe_exist_named_child (u'staff')
764                 if staff_id:
765                     staff_id = staff_id.get_text ()
766                 if staff_id:
767                     dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ())
768                 else:
769                     dir_voices = voices.keys ()
770                 for v in dir_voices:
771                     voices[v].add_element (n)
772                 continue
773
774             if isinstance (n, Harmony) or isinstance (n, FiguredBass):
775                 # store the harmony or figured bass element until we encounter 
776                 # the next note and assign it only to that one voice.
777                 assign_to_next_note.append (n)
778                 continue
779
780             id = voice_id.get_text ()
781             if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
782                 #Skip this note. 
783                 pass
784             else:
785                 for i in assign_to_next_note:
786                     voices[id].add_element (i)
787                 assign_to_next_note = []
788                 voices[id].add_element (n)
789
790         # Assign all remaining elements from assign_to_next_note to the voice
791         # of the previous note:
792         for i in assign_to_next_note:
793             voices[id].add_element (i)
794         assign_to_next_note = []
795
796         if start_attr:
797             for (s, vids) in staff_to_voice_dict.items ():
798                 staff_attributes = part.extract_attributes_for_staff (start_attr, s)
799                 staff_attributes.read_self ()
800                 part._staff_attributes_dict[s] = staff_attributes
801                 for v in vids:
802                     voices[v].insert (0, staff_attributes)
803                     voices[v]._elements[0].read_self()
804
805         part._voices = voices
806
807     def get_voices (self):
808         return self._voices
809     def get_staff_attributes (self):
810         return self._staff_attributes_dict
811
812 class Notations (Music_xml_node):
813     def get_tie (self):
814         ts = self.get_named_children ('tied')
815         starts = [t for t in ts if t.type == 'start']
816         if starts:
817             return starts[0]
818         else:
819             return None
820
821     def get_tuplets (self):
822         return self.get_typed_children (Tuplet)
823
824 class Time_modification(Music_xml_node):
825     def get_fraction (self):
826         b = self.get_maybe_exist_named_child ('actual-notes')
827         a = self.get_maybe_exist_named_child ('normal-notes')
828         return (int(a.get_text ()), int (b.get_text ()))
829
830 class Accidental (Music_xml_node):
831     def __init__ (self):
832         Music_xml_node.__init__ (self)
833         self.editorial = False
834         self.cautionary = False
835
836 class Music_xml_spanner (Music_xml_node):
837     def get_type (self):
838         if hasattr (self, 'type'):
839             return self.type
840         else:
841             return 0
842     def get_size (self):
843         if hasattr (self, 'size'):
844             return string.atoi (self.size)
845         else:
846             return 0
847
848 class Wedge (Music_xml_spanner):
849     pass
850
851 class Tuplet (Music_xml_spanner):
852     pass
853
854 class Bracket (Music_xml_spanner):
855     pass
856
857 class Dashes (Music_xml_spanner):
858     pass
859
860 class Slur (Music_xml_spanner):
861     def get_type (self):
862         return self.type
863
864 class Beam (Music_xml_spanner):
865     def get_type (self):
866         return self.get_text ()
867     def is_primary (self):
868         return self.number == "1"
869
870 class Wavy_line (Music_xml_spanner):
871     pass
872     
873 class Pedal (Music_xml_spanner):
874     pass
875
876 class Glissando (Music_xml_spanner):
877     pass
878
879 class Slide (Music_xml_spanner):
880     pass
881
882 class Octave_shift (Music_xml_spanner):
883     # default is 8 for the octave-shift!
884     def get_size (self):
885         if hasattr (self, 'size'):
886             return string.atoi (self.size)
887         else:
888             return 8
889
890 class Chord (Music_xml_node):
891     pass
892
893 class Dot (Music_xml_node):
894     pass
895
896 # Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
897 # for the inner <rest> element, not the whole rest block.
898 class Rest (Music_xml_node):
899     def __init__ (self):
900         Music_xml_node.__init__ (self)
901         self._is_whole_measure = False
902     def is_whole_measure (self):
903         return self._is_whole_measure
904     def get_step (self):
905         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
906         if ch:
907             step = ch.get_text ().strip ()
908             return step
909         else:
910             return None
911     def get_octave (self):
912         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
913         if ch:
914             step = ch.get_text ().strip ()
915             return int (step)
916         else:
917             return None
918
919 class Type (Music_xml_node):
920     pass
921 class Grace (Music_xml_node):
922     pass
923 class Staff (Music_xml_node):
924     pass
925
926 class Direction (Music_xml_node):
927     pass
928 class DirType (Music_xml_node):
929     pass
930
931 class Bend (Music_xml_node):
932     def bend_alter (self):
933         alter = self.get_maybe_exist_named_child ('bend-alter')
934         if alter:
935             return alter.get_text()
936         else:
937             return 0
938
939 class Words (Music_xml_node):
940     pass
941
942 class Harmony (Music_xml_node):
943     pass
944
945 class ChordPitch (Music_xml_node):
946     def step_class_name (self):
947         return u'root-step'
948     def alter_class_name (self):
949         return u'root-alter'
950     def get_step (self):
951         ch = self.get_unique_typed_child (get_class (self.step_class_name ()))
952         return ch.get_text ().strip ()
953     def get_alteration (self):
954         ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ()))
955         alter = 0
956         if ch:
957             alter = int (ch.get_text ().strip ())
958         return alter
959
960 class Root (ChordPitch):
961     pass
962
963 class Bass (ChordPitch):
964     def step_class_name (self):
965         return u'bass-step'
966     def alter_class_name (self):
967         return u'bass-alter'
968
969 class ChordModification (Music_xml_node):
970     def get_type (self):
971         ch = self.get_maybe_exist_typed_child (get_class (u'degree-type'))
972         return {'add': 1, 'alter': 1, 'subtract': -1}.get (ch.get_text ().strip (), 0)
973     def get_value (self):
974         ch = self.get_maybe_exist_typed_child (get_class (u'degree-value'))
975         value = 0
976         if ch:
977             value = int (ch.get_text ().strip ())
978         return value
979     def get_alter (self):
980         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
981         value = 0
982         if ch:
983             value = int (ch.get_text ().strip ())
984         return value
985
986
987 class Frame (Music_xml_node):
988     def get_frets (self):
989         return self.get_named_child_value_number ('frame-frets', 4)
990     def get_strings (self):
991         return self.get_named_child_value_number ('frame-strings', 6)
992     def get_first_fret (self):
993         return self.get_named_child_value_number ('first-fret', 1)
994
995 class Frame_Note (Music_xml_node):
996     def get_string (self):
997         return self.get_named_child_value_number ('string', 1)
998     def get_fret (self):
999         return self.get_named_child_value_number ('fret', 0)
1000     def get_fingering (self):
1001         return self.get_named_child_value_number ('fingering', -1)
1002     def get_barre (self):
1003         n = self.get_maybe_exist_named_child ('barre')
1004         if n:
1005             return getattr (n, 'type', '')
1006         else:
1007             return ''
1008
1009 class FiguredBass (Music_xml_node):
1010     pass
1011
1012 class BeatUnit (Music_xml_node):
1013     pass
1014
1015 class BeatUnitDot (Music_xml_node):
1016     pass
1017
1018 class PerMinute (Music_xml_node):
1019     pass
1020
1021
1022
1023 ## need this, not all classes are instantiated
1024 ## for every input file. Only add those classes, that are either directly
1025 ## used by class name or extend Music_xml_node in some way!
1026 class_dict = {
1027         '#comment': Hash_comment,
1028         '#text': Hash_text,
1029         'accidental': Accidental,
1030         'attributes': Attributes,
1031         'barline': Barline,
1032         'bar-style': BarStyle,
1033         'bass': Bass,
1034         'beam' : Beam,
1035         'beat-unit': BeatUnit,
1036         'beat-unit-dot': BeatUnitDot,
1037         'bend' : Bend,
1038         'bracket' : Bracket,
1039         'chord': Chord,
1040         'dashes' : Dashes,
1041         'degree' : ChordModification,
1042         'dot': Dot,
1043         'direction': Direction,
1044         'direction-type': DirType,
1045         'duration': Duration,
1046         'frame': Frame,
1047         'frame-note': Frame_Note,
1048         'figured-bass': FiguredBass,
1049         'glissando': Glissando,
1050         'grace': Grace,
1051         'harmony': Harmony,
1052         'identification': Identification,
1053         'lyric': Lyric,
1054         'measure': Measure,
1055         'notations': Notations,
1056         'note': Note,
1057         'octave-shift': Octave_shift,
1058         'part': Part,
1059     'part-group': Part_group,
1060         'part-list': Part_list,
1061         'pedal': Pedal,
1062         'per-minute': PerMinute,
1063         'pitch': Pitch,
1064         'rest': Rest,
1065         'root': Root,
1066         'score-part': Score_part,
1067         'slide': Slide,
1068         'slur': Slur,
1069         'staff': Staff,
1070         'syllabic': Syllabic,
1071         'text': Text,
1072         'time-modification': Time_modification,
1073         'tuplet': Tuplet,
1074         'type': Type,
1075         'unpitched': Unpitched,
1076         'wavy-line': Wavy_line,
1077         'wedge': Wedge,
1078         'words': Words,
1079         'work': Work,
1080 }
1081
1082 def name2class_name (name):
1083     name = name.replace ('-', '_')
1084     name = name.replace ('#', 'hash_')
1085     name = name[0].upper() + name[1:].lower()
1086
1087     return str (name)
1088
1089 def get_class (name):
1090     classname = class_dict.get (name)
1091     if classname:
1092         return classname
1093     else:
1094         class_name = name2class_name (name)
1095         klass = new.classobj (class_name, (Music_xml_node,) , {})
1096         class_dict[name] = klass
1097         return klass
1098         
1099 def lxml_demarshal_node (node):
1100     name = node.tag
1101
1102     # Ignore comment nodes, which are also returned by the etree parser!
1103     if name is None or node.__class__.__name__ == "_Comment":
1104         return None
1105     klass = get_class (name)
1106     py_node = klass()
1107     
1108     py_node._original = node
1109     py_node._name = name
1110     py_node._data = node.text
1111     py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
1112     py_node._children = filter (lambda x: x, py_node._children)
1113     
1114     for c in py_node._children:
1115         c._parent = py_node
1116
1117     for (k, v) in node.items ():
1118         py_node.__dict__[k] = v
1119         py_node._attribute_dict[k] = v
1120
1121     return py_node
1122
1123 def minidom_demarshal_node (node):
1124     name = node.nodeName
1125
1126     klass = get_class (name)
1127     py_node = klass ()
1128     py_node._name = name
1129     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
1130     for c in py_node._children:
1131         c._parent = py_node
1132
1133     if node.attributes:
1134         for (nm, value) in node.attributes.items ():
1135             py_node.__dict__[nm] = value
1136             py_node._attribute_dict[nm] = value
1137             
1138     py_node._data = None
1139     if node.nodeType == node.TEXT_NODE and node.data:
1140         py_node._data = node.data 
1141
1142     py_node._original = node
1143     return py_node
1144
1145
1146 if __name__  == '__main__':
1147     import lxml.etree
1148         
1149     tree = lxml.etree.parse ('beethoven.xml')
1150     mxl_tree = lxml_demarshal_node (tree.getroot ())
1151     ks = class_dict.keys ()
1152     ks.sort ()
1153     print '\n'.join (ks)