# -*- coding: utf-8 -*-
import new
import string
from rational import *
import re
import sys
import copy
import lilylib as ly

_ = ly._

def error (str):
    ly.stderr_write ((_ ("error: %s") % str) + "\n")


def escape_ly_output_string (input_string):
    return_string = input_string
    needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string);
    if needs_quotes:
        return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
    return return_string


class Xml_node:
    def __init__ (self):
	self._children = []
	self._data = None
	self._original = None
	self._name = 'xml_node'
	self._parent = None
        self._attribute_dict = {}
        
    def get_parent (self):
        return self._parent
    
    def is_first (self):
	return self._parent.get_typed_children (self.__class__)[0] == self

    def original (self):
	return self._original 
    def get_name (self):
	return self._name

    def get_text (self):
	if self._data:
	    return self._data

	if not self._children:
	    return ''

	return ''.join ([c.get_text () for c in self._children])

    def message (self, msg):
        ly.stderr_write (msg+'\n')

        p = self
        while p:
            sys.stderr.write ('  In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items()])))
            p = p.get_parent ()
        
    def get_typed_children (self, klass):
        if not klass:
            return []
        else:
            return [c for c in self._children if isinstance(c, klass)]

    def get_named_children (self, nm):
	return self.get_typed_children (get_class (nm))

    def get_named_child (self, nm):
	return self.get_maybe_exist_named_child (nm)

    def get_children (self, predicate):
	return [c for c in self._children if predicate(c)]

    def get_all_children (self):
	return self._children

    def get_maybe_exist_named_child (self, name):
	return self.get_maybe_exist_typed_child (get_class (name))

    def get_maybe_exist_typed_child (self, klass):
	cn = self.get_typed_children (klass)
	if len (cn)==0:
	    return None
	elif len (cn) == 1:
	    return cn[0]
	else:
	    raise "More than 1 child", klass

    def get_unique_typed_child (self, klass):
	cn = self.get_typed_children(klass)
	if len (cn) <> 1:
	    sys.stderr.write (self.__dict__ + '\n')
	    raise 'Child is not unique for', (klass, 'found', cn)

	return cn[0]

    def get_named_child_value_number (self, name, default):
        n = self.get_maybe_exist_named_child (name)
        if n:
            return string.atoi (n.get_text())
        else:
            return default


class Music_xml_node (Xml_node):
    def __init__ (self):
	Xml_node.__init__ (self)
	self.duration = Rational (0)
	self.start = Rational (0)

class Work (Xml_node):
    def get_work_information (self, tag):
        wt = self.get_maybe_exist_named_child (tag)
        if wt:
            return wt.get_text ()
        else:
            return ''
      
    def get_work_title (self):
        return self.get_work_information ('work-title')
    def get_work_number (self):
        return self.get_work_information ('work-number')
    def get_opus (self):
        return self.get_work_information ('opus')

class Identification (Xml_node):
    def get_rights (self):
        rights = self.get_maybe_exist_named_child ('rights')
        if rights:
            return rights.get_text ()
        else:
            return ''

    def get_creator (self, type):
        creators = self.get_named_children ('creator')
        # return the first creator tag that has the particular type
        for i in creators:
            if hasattr (i, 'type') and i.type == type:
                return i.get_text ()
        return None

    def get_composer (self):
        c = self.get_creator ('composer')
        if c:
            return c
        creators = self.get_named_children ('creator')
        # return the first creator tag that has no type at all
        for i in creators:
            if not hasattr (i, 'type'):
                return i.get_text ()
        return None
    def get_arranger (self):
        return self.get_creator ('arranger')
    def get_editor (self):
        return self.get_creator ('editor')
    def get_poet (self):
        v = self.get_creator ('lyricist')
        if v:
            return v
        v = self.get_creator ('poet')
        return v
    
    def get_encoding_information (self, type):
        enc = self.get_named_children ('encoding')
        if enc:
            children = enc[0].get_named_children (type)
            if children:
                return children[0].get_text ()
        else:
            return None
      
    def get_encoding_software (self):
        return self.get_encoding_information ('software')
    def get_encoding_date (self):
        return self.get_encoding_information ('encoding-date')
    def get_encoding_person (self):
        return self.get_encoding_information ('encoder')
    def get_encoding_description (self):
        return self.get_encoding_information ('encoding-description')
    
    def get_encoding_software_list (self):
        enc = self.get_named_children ('encoding')
        software = []
        for e in enc:
            softwares = e.get_named_children ('software')
            for s in softwares:
                software.append (s.get_text ())
        return software



class Duration (Music_xml_node):
    def get_length (self):
	dur = int (self.get_text ()) * Rational (1,4)
	return dur

class Hash_comment (Music_xml_node):
    pass
class Hash_text (Music_xml_node):
    pass

class Pitch (Music_xml_node):
    def get_step (self):
	ch = self.get_unique_typed_child (get_class (u'step'))
	step = ch.get_text ().strip ()
	return step
    def get_octave (self):
	ch = self.get_unique_typed_child (get_class (u'octave'))

	step = ch.get_text ().strip ()
	return int (step)

    def get_alteration (self):
	ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
	alter = 0
	if ch:
	    alter = int (ch.get_text ().strip ())
	return alter

class Unpitched (Music_xml_node):
    def get_step (self):
	ch = self.get_unique_typed_child (get_class (u'display-step'))
	step = ch.get_text ().strip ()
	return step

    def get_octave (self):
	ch = self.get_unique_typed_child (get_class (u'display-octave'))

	if ch:
	    octave = ch.get_text ().strip ()
	    return int (octave)
	else:
	    return None

class Measure_element (Music_xml_node):
    def get_voice_id (self):
	voice_id = self.get_maybe_exist_named_child ('voice')
	if voice_id:
	    return voice_id.get_text ()
	else:
	    return None

    def is_first (self):
	cn = self._parent.get_typed_children (self.__class__)
	cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()]
	return cn[0] == self

class Attributes (Measure_element):
    def __init__ (self):
	Measure_element.__init__ (self)
	self._dict = {}

    def set_attributes_from_previous (self, dict):
	self._dict.update (dict)
        
    def read_self (self):
	for c in self.get_all_children ():
	    self._dict[c.get_name()] = c

    def get_named_attribute (self, name):
	return self._dict.get (name)

    def get_measure_length (self):
        (n,d) = self.get_time_signature ()
        return Rational (n,d)
        
    def get_time_signature (self):
        "return time sig as a (beat, beat-type) tuple"

        try:
            mxl = self.get_named_attribute ('time')
            if mxl:
                beats = mxl.get_maybe_exist_named_child ('beats')
                type = mxl.get_maybe_exist_named_child ('beat-type')
                return (int (beats.get_text ()),
                        int (type.get_text ()))
            else:
                return (4, 4)
        except KeyError:
            error (_ ("requested time signature, but time sig is unknown"))
            return (4, 4)

    # returns clef information in the form ("cleftype", position, octave-shift)
    def get_clef_information (self):
        clefinfo = ['G', 2, 0]
        mxl = self.get_named_attribute ('clef')
        if not mxl:
            return clefinfo
        sign = mxl.get_maybe_exist_named_child ('sign')
        if sign:
            clefinfo[0] = sign.get_text()
        line = mxl.get_maybe_exist_named_child ('line')
        if line:
            clefinfo[1] = string.atoi (line.get_text ())
        octave = mxl.get_maybe_exist_named_child ('clef-octave-change')
        if octave:
            clefinfo[2] = string.atoi (octave.get_text ())
        return clefinfo

    def get_key_signature (self):
        "return (fifths, mode) tuple"

        key = self.get_named_attribute ('key')
        mode_node = key.get_maybe_exist_named_child ('mode')
        mode = 'major'
        if mode_node:
            mode = mode_node.get_text ()

        fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
        return (fifths, mode)

class Barline (Measure_element):
    pass
class BarStyle (Music_xml_node):
    pass
class Partial (Measure_element):
    def __init__ (self, partial):
        Measure_element.__init__ (self)
        self.partial = partial

class Note (Measure_element):
    def __init__ (self):
        Measure_element.__init__ (self)
        self.instrument_name = ''
        
    def get_duration_log (self):
        ch = self.get_maybe_exist_named_child (u'type')

        if ch:
            log = ch.get_text ().strip()
            return {'256th': 8,
                    '128th': 7,
                    '64th': 6,
                    '32nd': 5,
                    '16th': 4,
                    'eighth': 3,
                    'quarter': 2,
                    'half': 1,
                    'whole': 0,
                    'breve': -1,
                    'longa': -2}.get (log, 0)
	elif self.get_maybe_exist_named_child (u'grace'):
	    # FIXME: is it ok to default to eight note for grace notes?
	    return 3
        else:
            self.message (_ ("Encountered note at %s with %s duration (no <type> element):") % (self.start, self.duration) )
            return 0

    def get_factor (self):
        return 1

    def get_pitches (self):
        return self.get_typed_children (get_class (u'pitch'))

class Part_list (Music_xml_node):
    def __init__ (self):
        Music_xml_node.__init__ (self)
        self._id_instrument_name_dict = {}
        
    def generate_id_instrument_dict (self):

        ## not empty to make sure this happens only once.
        mapping = {1: 1}
        for score_part in self.get_named_children ('score-part'):
            for instr in score_part.get_named_children ('score-instrument'):
                id = instr.id
                name = instr.get_named_child ("instrument-name")
                mapping[id] = name.get_text ()

        self._id_instrument_name_dict = mapping

    def get_instrument (self, id):
        if not self._id_instrument_name_dict:
            self.generate_id_instrument_dict()

        instrument_name = self._id_instrument_name_dict.get (id)
        if instrument_name:
            return instrument_name
        else:
            ly.stderr_write (_ ("Unable to find find instrument for ID=%s\n") % id)
            return "Grand Piano"

class Part_group (Music_xml_node):
    pass
class Score_part (Music_xml_node):
    pass
        
class Measure (Music_xml_node):
    def __init__ (self):
        Music_xml_node.__init__ (self)
        self.partial = 0
    def is_implicit (self):
        return hasattr (self, 'implicit') and self.implicit == 'yes'
    def get_notes (self):
	return self.get_typed_children (get_class (u'note'))

class Syllabic (Music_xml_node):
    def continued (self):
        text = self.get_text()
        return (text == "begin") or (text == "middle")
class Text (Music_xml_node):
    pass

class Lyric (Music_xml_node):
    def get_number (self):
        if hasattr (self, 'number'):
            return self.number
        else:
            return -1

    def lyric_to_text (self):
        continued = False
        syllabic = self.get_maybe_exist_typed_child (Syllabic)
        if syllabic:
            continued = syllabic.continued ()
        text = self.get_maybe_exist_typed_child (Text)
        
        if text:
            text = text.get_text()
            # We need to convert soft hyphens to -, otherwise the ascii codec as well
            # as lilypond will barf on that character
            text = string.replace( text, u'\xad', '-' )
        
        if text == "-" and continued:
            return "--"
        elif text == "_" and continued:
            return "__"
        elif continued and text:
            return escape_ly_output_string (text) + " --"
        elif continued:
            return "--"
        elif text:
            return escape_ly_output_string (text)
        else:
            return ""

class Musicxml_voice:
    def __init__ (self):
	self._elements = []
	self._staves = {}
	self._start_staff = None
        self._lyrics = []
        self._has_lyrics = False

    def add_element (self, e):
	self._elements.append (e)
	if (isinstance (e, Note)
	    and e.get_maybe_exist_typed_child (Staff)):
	    name = e.get_maybe_exist_typed_child (Staff).get_text ()

	    if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
		self._start_staff = name
	    self._staves[name] = True

        lyrics = e.get_typed_children (Lyric)
        if not self._has_lyrics:
          self.has_lyrics = len (lyrics) > 0

        for l in lyrics:
            nr = l.get_number()
            if (nr > 0) and not (nr in self._lyrics):
                self._lyrics.append (nr)

    def insert (self, idx, e):
	self._elements.insert (idx, e)

    def get_lyrics_numbers (self):
        if (len (self._lyrics) == 0) and self._has_lyrics:
            #only happens if none of the <lyric> tags has a number attribute
            return [1]
        else:
            return self._lyrics


class Part (Music_xml_node):
    def __init__ (self):
        Music_xml_node.__init__ (self)
	self._voices = {}
        self._staff_attributes_dict = {}

    def get_part_list (self):
        n = self
        while n and n.get_name() != 'score-partwise':
            n = n._parent

        return n.get_named_child ('part-list')
        
    def interpret (self):
	"""Set durations and starting points."""
        """The starting point of the very first note is 0!"""
        
        part_list = self.get_part_list ()
        
	now = Rational (0)
	factor = Rational (1)
	attributes_dict = {}
        attributes_object = None
	measures = self.get_typed_children (Measure)
        last_moment = Rational (-1)
        last_measure_position = Rational (-1)
        measure_position = Rational (0)
        measure_start_moment = now
        is_first_measure = True
        previous_measure = None
	for m in measures:
            # implicit measures are used for artificial measures, e.g. when
            # a repeat bar line splits a bar into two halves. In this case,
            # don't reset the measure position to 0. They are also used for
            # upbeats (initial value of 0 fits these, too).
            # Also, don't reset the measure position at the end of the loop,
            # but rather when starting the next measure (since only then do we
            # know if the next measure is implicit and continues that measure)
            if not m.is_implicit ():
                # Warn about possibly overfull measures and reset the position
                if attributes_object and previous_measure and previous_measure.partial == 0:
                    length = attributes_object.get_measure_length ()
                    new_now = measure_start_moment + length
                    if now <> new_now:
                        problem = 'incomplete'
                        if now > new_now:
                            problem = 'overfull'
                        ## only for verbose operation.
                        if problem <> 'incomplete' and previous_measure:
                            previous_measure.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
                    now = new_now
                measure_start_moment = now
                measure_position = Rational (0)

            for n in m.get_all_children ():
                if isinstance (n, Hash_text):
                    continue
		dur = Rational (0)

                if n.__class__ == Attributes:
		    n.set_attributes_from_previous (attributes_dict)
		    n.read_self ()
		    attributes_dict = n._dict.copy ()
                    attributes_object = n
                    
		    factor = Rational (1,
				       int (attributes_dict.get ('divisions').get_text ()))

                
		if (n.get_maybe_exist_typed_child (Duration)):
		    mxl_dur = n.get_maybe_exist_typed_child (Duration)
		    dur = mxl_dur.get_length () * factor
                    
		    if n.get_name() == 'backup':
			dur = - dur
		    if n.get_maybe_exist_typed_child (Grace):
			dur = Rational (0)

                    rest = n.get_maybe_exist_typed_child (Rest)
		    if (rest
                        and attributes_object
                        and attributes_object.get_measure_length () == dur):

                        rest._is_whole_measure = True

                if (dur > Rational (0) 
                    and n.get_maybe_exist_typed_child (Chord)):
                    now = last_moment
                    measure_position = last_measure_position

                n._when = now
                n._measure_position = measure_position
                n._duration = dur
                if dur > Rational (0):
                    last_moment = now
                    last_measure_position = measure_position
                    now += dur
                    measure_position += dur
                elif dur < Rational (0):
                    # backup element, reset measure position
                    now += dur
                    measure_position += dur
                    if measure_position < 0:
                        # backup went beyond the measure start => reset to 0
                        now -= measure_position
                        measure_position = 0
                    last_moment = now
                    last_measure_position = measure_position
                if n._name == 'note':
                    instrument = n.get_maybe_exist_named_child ('instrument')
                    if instrument:
                        n.instrument_name = part_list.get_instrument (instrument.id)

            # Incomplete first measures are not padded, but registered as partial
            if is_first_measure:
                is_first_measure = False
                # upbeats are marked as implicit measures
                if attributes_object and m.is_implicit ():
                    length = attributes_object.get_measure_length ()
                    measure_end = measure_start_moment + length
                    if measure_end <> now:
                        m.partial = now
            previous_measure = m

    # modify attributes so that only those applying to the given staff remain
    def extract_attributes_for_staff (part, attr, staff):
        attributes = copy.copy (attr)
        attributes._children = copy.copy (attr._children)
        attributes._dict = attr._dict.copy ()
        for c in attributes._children:
            if hasattr (c, 'number') and c.number != staff:
                attributes._children.remove (c)
        return attributes

    def extract_voices (part):
	voices = {}
	measures = part.get_typed_children (Measure)
	elements = []
	for m in measures:
            if m.partial > 0:
                elements.append (Partial (m.partial))
	    elements.extend (m.get_all_children ())
        # make sure we know all voices already so that dynamics, clefs, etc.
        # can be assigned to the correct voices
        voice_to_staff_dict = {}
        for n in elements:
            voice_id = n.get_maybe_exist_named_child (u'voice')
            vid = None
            if voice_id:
                vid = voice_id.get_text ()

            staff_id = n.get_maybe_exist_named_child (u'staff')
            sid = None
            if staff_id:
                sid = staff_id.get_text ()
            else:
                sid = "None"
            if vid and not voices.has_key (vid):
                voices[vid] = Musicxml_voice()
            if vid and sid and not n.get_maybe_exist_typed_child (Grace):
                if not voice_to_staff_dict.has_key (vid):
                    voice_to_staff_dict[vid] = sid
        # invert the voice_to_staff_dict into a staff_to_voice_dict (since we
        # need to assign staff-assigned objects like clefs, times, etc. to
        # all the correct voices. This will never work entirely correct due
        # to staff-switches, but that's the best we can do!
        staff_to_voice_dict = {}
        for (v,s) in voice_to_staff_dict.items ():
            if not staff_to_voice_dict.has_key (s):
                staff_to_voice_dict[s] = [v]
            else:
                staff_to_voice_dict[s].append (v)


	start_attr = None
        assign_to_next_note = []
        id = None
	for n in elements:
	    voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))

	    if not (voice_id or isinstance (n, Attributes) or
                    isinstance (n, Direction) or isinstance (n, Partial) or
                    isinstance (n, Barline) or isinstance (n, Harmony) ):
		continue

	    if isinstance (n, Attributes) and not start_attr:
		start_attr = n
		continue

            if isinstance (n, Attributes):
                # assign these only to the voices they really belongs to!
                for (s, vids) in staff_to_voice_dict.items ():
                    staff_attributes = part.extract_attributes_for_staff (n, s)
                    for v in vids:
                        voices[v].add_element (staff_attributes)
                continue

            if isinstance (n, Partial) or isinstance (n, Barline):
                for v in voices.keys ():
                    voices[v].add_element (n)
                continue

            if isinstance (n, Direction):
                staff_id = n.get_maybe_exist_named_child (u'staff')
                if staff_id:
                    staff_id = staff_id.get_text ()
                if staff_id:
                    dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ())
                else:
                    dir_voices = voices.keys ()
                for v in dir_voices:
                    voices[v].add_element (n)
                continue

            if isinstance (n, Harmony):
                # store the harmony element until we encounter the next note
                # and assign it only to that one voice.
                assign_to_next_note.append (n)
                continue

	    id = voice_id.get_text ()
            if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
                #Skip this note. 
                pass
            else:
                for i in assign_to_next_note:
                    voices[id].add_element (i)
                assign_to_next_note = []
                voices[id].add_element (n)

        # Assign all remaining elements from assign_to_next_note to the voice
        # of the previous note:
        for i in assign_to_next_note:
            voices[id].add_element (i)
        assign_to_next_note = []

	if start_attr:
            for (s, vids) in staff_to_voice_dict.items ():
                staff_attributes = part.extract_attributes_for_staff (start_attr, s)
                staff_attributes.read_self ()
                part._staff_attributes_dict[s] = staff_attributes
                for v in vids:
                    voices[v].insert (0, staff_attributes)
                    voices[v]._elements[0].read_self()

        part._voices = voices

    def get_voices (self):
	return self._voices
    def get_staff_attributes (self):
        return self._staff_attributes_dict

class Notations (Music_xml_node):
    def get_tie (self):
	ts = self.get_named_children ('tied')
	starts = [t for t in ts if t.type == 'start']
	if starts:
	    return starts[0]
	else:
	    return None

    def get_tuplets (self):
	return self.get_typed_children (Tuplet)

class Time_modification(Music_xml_node):
    def get_fraction (self):
	b = self.get_maybe_exist_named_child ('actual-notes')
	a = self.get_maybe_exist_named_child ('normal-notes')
	return (int(a.get_text ()), int (b.get_text ()))

class Accidental (Music_xml_node):
    def __init__ (self):
	Music_xml_node.__init__ (self)
	self.editorial = False
	self.cautionary = False

class Music_xml_spanner (Music_xml_node):
    def get_type (self):
        if hasattr (self, 'type'):
            return self.type
        else:
            return 0
    def get_size (self):
        if hasattr (self, 'size'):
            return string.atoi (self.size)
        else:
            return 0

class Wedge (Music_xml_spanner):
    pass

class Tuplet (Music_xml_spanner):
    pass

class Bracket (Music_xml_spanner):
    pass

class Dashes (Music_xml_spanner):
    pass

class Slur (Music_xml_spanner):
    def get_type (self):
	return self.type

class Beam (Music_xml_spanner):
    def get_type (self):
	return self.get_text ()
    def is_primary (self):
        return self.number == "1"

class Wavy_line (Music_xml_spanner):
    pass
    
class Pedal (Music_xml_spanner):
    pass

class Glissando (Music_xml_spanner):
    pass

class Slide (Music_xml_spanner):
    pass

class Octave_shift (Music_xml_spanner):
    # default is 8 for the octave-shift!
    def get_size (self):
        if hasattr (self, 'size'):
            return string.atoi (self.size)
        else:
            return 8

class Chord (Music_xml_node):
    pass

class Dot (Music_xml_node):
    pass

# Rests in MusicXML are <note> blocks with a <rest> inside. This class is only
# for the inner <rest> element, not the whole rest block.
class Rest (Music_xml_node):
    def __init__ (self):
        Music_xml_node.__init__ (self)
        self._is_whole_measure = False
    def is_whole_measure (self):
        return self._is_whole_measure
    def get_step (self):
        ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
        if ch:
            step = ch.get_text ().strip ()
            return step
        else:
            return None
    def get_octave (self):
        ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
        if ch:
            step = ch.get_text ().strip ()
            return int (step)
        else:
            return None

class Type (Music_xml_node):
    pass
class Grace (Music_xml_node):
    pass
class Staff (Music_xml_node):
    pass

class Direction (Music_xml_node):
    pass
class DirType (Music_xml_node):
    pass

class Bend (Music_xml_node):
    def bend_alter (self):
        alter = self.get_maybe_exist_named_child ('bend-alter')
        if alter:
            return alter.get_text()
        else:
            return 0

class Words (Music_xml_node):
    pass

class Harmony (Music_xml_node):
    pass

class Frame (Music_xml_node):
    def get_frets (self):
        return self.get_named_child_value_number ('frame-frets', 4)
    def get_strings (self):
        return self.get_named_child_value_number ('frame-strings', 6)
    def get_first_fret (self):
        return self.get_named_child_value_number ('first-fret', 1)
class Frame_Note (Music_xml_node):
    def get_string (self):
        return self.get_named_child_value_number ('string', 1)
    def get_fret (self):
        return self.get_named_child_value_number ('fret', 0)
    def get_fingering (self):
        return self.get_named_child_value_number ('fingering', -1)
    def get_barre (self):
        n = self.get_maybe_exist_named_child ('barre')
        if n:
            return getattr (n, 'type', '')
        else:
            return ''


## need this, not all classes are instantiated
## for every input file. Only add those classes, that are either directly
## used by class name or extend Music_xml_node in some way!
class_dict = {
	'#comment': Hash_comment,
        '#text': Hash_text,
	'accidental': Accidental,
	'attributes': Attributes,
        'barline': Barline,
        'bar-style': BarStyle,
	'beam' : Beam,
        'bend' : Bend,
        'bracket' : Bracket,
	'chord': Chord,
        'dashes' : Dashes,
	'dot': Dot,
	'direction': Direction,
        'direction-type': DirType,
	'duration': Duration,
        'frame': Frame,
        'frame-note': Frame_Note,
        'glissando': Glissando,
	'grace': Grace,
        'harmony': Harmony,
        'identification': Identification,
        'lyric': Lyric,
	'measure': Measure,
	'notations': Notations,
	'note': Note,
        'octave-shift': Octave_shift,
	'part': Part,
    'part-group': Part_group,
	'part-list': Part_list,
        'pedal': Pedal,
	'pitch': Pitch,
	'rest': Rest,
    'score-part': Score_part,
        'slide': Slide,
	'slur': Slur,
	'staff': Staff,
        'syllabic': Syllabic,
        'text': Text,
	'time-modification': Time_modification,
        'tuplet': Tuplet,
	'type': Type,
	'unpitched': Unpitched,
        'wavy-line': Wavy_line,
        'wedge': Wedge,
        'words': Words,
        'work': Work,
}

def name2class_name (name):
    name = name.replace ('-', '_')
    name = name.replace ('#', 'hash_')
    name = name[0].upper() + name[1:].lower()

    return str (name)

def get_class (name):
    classname = class_dict.get (name)
    if classname:
        return classname
    else:
	class_name = name2class_name (name)
	klass = new.classobj (class_name, (Music_xml_node,) , {})
	class_dict[name] = klass
        return klass
        
def lxml_demarshal_node (node):
    name = node.tag

    # Ignore comment nodes, which are also returned by the etree parser!
    if name is None or node.__class__.__name__ == "_Comment":
        return None
    klass = get_class (name)
    py_node = klass()
    
    py_node._original = node
    py_node._name = name
    py_node._data = node.text
    py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
    py_node._children = filter (lambda x: x, py_node._children)
    
    for c in py_node._children:
	c._parent = py_node

    for (k,v) in node.items ():
        py_node.__dict__[k] = v
        py_node._attribute_dict[k] = v

    return py_node

def minidom_demarshal_node (node):
    name = node.nodeName

    klass = get_class (name)
    py_node = klass()
    py_node._name = name
    py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
    for c in py_node._children:
	c._parent = py_node

    if node.attributes:
	for (nm, value) in node.attributes.items():
	    py_node.__dict__[nm] = value
            py_node._attribute_dict[nm] = value
            
    py_node._data = None
    if node.nodeType == node.TEXT_NODE and node.data:
	py_node._data = node.data 

    py_node._original = node
    return py_node


if __name__  == '__main__':
        import lxml.etree
        
        tree = lxml.etree.parse ('beethoven.xml')
        mxl_tree = lxml_demarshal_node (tree.getroot ())
        ks = class_dict.keys()
        ks.sort()
        print '\n'.join (ks)
