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