# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2008 - 2015 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.
"""
Uses xml.etree to create a Music XML document.
Example::
    musxml = create_musicxml.CreateMusicXML()
    musxml.create_part()
    musxml.create_measure(divs=1)
    musxml.new_note('C', 4, 'whole', 4)
    xml = musxml.musicxml()
    xml.write(filename)
"""
from __future__ import unicode_literals
from __future__ import division
import sys
try:
    import xml.etree.cElementTree as etree
except ImportError:
    import xml.etree.ElementTree as etree
import ly.pkginfo
from .midi_sound_map import midi_sound_map
[docs]
class CreateMusicXML():
    """ Creates the XML nodes according to the Music XML standard."""
    def __init__(self):
        """Creates the basic structure of the XML without any music."""
        self.root = etree.Element("score-partwise", version="3.0")
        self.tree = etree.ElementTree(self.root)
        self.score_info = etree.SubElement(self.root, "identification")
        encoding = etree.SubElement(self.score_info, "encoding")
        software = etree.SubElement(encoding, "software")
        software.text = "python-ly " + ly.pkginfo.version
        encoding_date = etree.SubElement(encoding, "encoding-date")
        import datetime
        encoding_date.text = str(datetime.date.today())
        self.partlist = etree.SubElement(self.root, "part-list")
        self.part_count = 1
    ##
    # Building the basic Elements
    ##
[docs]
    def create_title(self, title):
        """Create score title."""
        mov_title = etree.Element("movement-title")
        mov_title.text = title
        self.root.insert(0, mov_title) 
[docs]
    def create_score_info(self, tag, info, attr={}):
        """Create score info."""
        info_node = etree.SubElement(self.score_info, tag, attr)
        info_node.text = info 
[docs]
    def create_partgroup(self, gr_type, num, name=None, abbr=None, symbol=None):
        """Create a new part group."""
        attr_dict = {"type": gr_type, "number": str(num)}
        partgroup = etree.SubElement(self.partlist, "part-group", attr_dict)
        if name:
            group_name = etree.SubElement(partgroup, "group-name")
            group_name.text = name
        if abbr:
            group_abbr = etree.SubElement(partgroup, "group-abbreviation")
            group_abbr.text = abbr
        if symbol:
            group_symbol = etree.SubElement(partgroup, "group-symbol")
            group_symbol.text = symbol 
[docs]
    def create_part(self, name="unnamed", abbr=False, midi=False):
        """Create a new part """
        strnr = str(self.part_count)
        part = etree.SubElement(self.partlist, "score-part", id="P"+strnr)
        partname = etree.SubElement(part, "part-name")
        partname.text = name
        if abbr:
            partabbr = etree.SubElement(part, "part-abbreviation")
            partabbr.text = abbr
        if midi:
            scoreinstr = etree.SubElement(part, "score-instrument", id="P"+strnr+"-I"+strnr)
            instrname = etree.SubElement(scoreinstr, "instrument-name")
            instrname.text = midi
            if midi in midi_sound_map and midi_sound_map[midi]:
                instrsound = etree.SubElement(scoreinstr, "instrument-sound")
                instrsound.text = midi_sound_map[midi]
            midiinstr = etree.SubElement(part, "midi-instrument", id="P"+strnr+"-I"+strnr)
            midich = etree.SubElement(midiinstr, "midi-channel")
            midich.text = strnr
            midiname = etree.SubElement(midiinstr, "midi-name")
            midiname.text = midi
        self.current_part = etree.SubElement(self.root, "part", id="P"+strnr)
        self.part_count += 1
        self.bar_nr = 1 
[docs]
    def create_measure(self, pickup = False, **bar_attrs):
        """Create new measure """
        if pickup and self.bar_nr == 1:
            self.bar_nr = 0
        self.current_bar = etree.SubElement(self.current_part, "measure", number=str(self.bar_nr))
        self.bar_nr +=1
        if bar_attrs:
            self.new_bar_attr(**bar_attrs) 
    ##
    # High-level node creation
    ##
[docs]
    def new_note(self, step, octave, durtype, divdur, alter=0,
                 acc_token=0, voice=1, dot=0, chord=0, grace=(0, 0), stem_dir=0):
        """Create all nodes needed for a normal note. """
        self.create_note()
        if grace[0]:
            self.add_grace(grace[1])
        if chord:
            self.add_chord()
        self.add_pitch(step, alter, octave)
        if not grace[0]:
            self.add_div_duration(divdur)
        self.add_voice(voice)
        self.add_duration_type(durtype)
        if dot:
            for i in range(dot):
                self.add_dot()
        if alter or acc_token:
            if acc_token == '!': # cautionary
                self.add_accidental(alter, caut=True)
            elif acc_token == '?': # parentheses
                self.add_accidental(alter, parenth=True)
            else:
                self.add_accidental(alter)
        if stem_dir:
            self.set_stem_dir(stem_dir) 
[docs]
    def new_unpitched_note(self, step, octave, durtype, divdur, voice=1,
                            dot=0, chord=0, grace=(0, 0)):
        """Create all nodes needed for an unpitched note. """
        self.create_note()
        if grace[0]:
            self.add_grace(grace[1])
        if chord:
            self.add_chord()
        self.add_unpitched(step, octave)
        if not grace[0]:
            self.add_div_duration(divdur)
        self.add_duration_type(durtype)
        self.add_voice(voice)
        if dot:
            for i in range(dot):
                self.add_dot() 
[docs]
    def tuplet_note(self, fraction, bs, ttype, nr, divs, atyp='', ntyp=''):
        """Convert current note to tuplet """
        base = self.mult * bs[0]
        scaling = bs[1]
        a = divs*4*fraction[1]
        b = (1/base)*fraction[0]
        duration = (a/b)*scaling
        self.change_div_duration(duration)
        from fractions import Fraction
        self.mult = Fraction(fraction[1], fraction[0])
        timemod_node = self.get_time_modify()
        if timemod_node:
            self.adjust_time_modify(timemod_node, fraction)
        else:
            self.add_time_modify(fraction)
        if ttype:
            self.add_notations()
            if atyp and ttype != "stop":
                self.add_tuplet_type(nr, ttype, fraction[0], atyp, fraction[1], ntyp)
            else:
                self.add_tuplet_type(nr, ttype) 
[docs]
    def tie_note(self, tie_type):
        self.add_tie(tie_type)
        self.add_notations()
        self.add_tied(tie_type) 
[docs]
    def new_rest(self, duration, durtype, pos, dot, voice):
        """Create all nodes needed for a rest. """
        self.create_note()
        if pos:
            self.add_rest_w_pos(pos[0], pos[1])
        else:
            self.add_rest()
        self.add_div_duration(duration)
        self.add_voice(voice)
        if durtype:
            self.add_duration_type(durtype)
        if dot:
            for i in range(dot):
                self.add_dot() 
[docs]
    def new_articulation(self, artic):
        """Add specified articulation. """
        self.add_notations()
        self.add_articulations()
        self.add_named_artic(artic) 
[docs]
    def new_simple_ornament(self, ornament):
        """Add specified ornament. """
        self.add_notations()
        self.add_ornaments()
        func_call = getattr(self, 'add_'+ornament)
        func_call() 
[docs]
    def new_adv_ornament(self, ornament, args):
        """Add more complex ornament."""
        self.add_notations()
        self.add_ornaments()
        if ornament == "wavy-line":
            self.add_wavyline(args['type']) 
[docs]
    def new_bar_attr(self, clef=0, mustime=0, key=None, mode=0, divs=0, multirest=0):
        """Create all bar attributes set. """
        self.create_bar_attr()
        if divs:
            self.add_divisions(divs)
        if key is not None:
            self.add_key(key, mode)
        if mustime:
            self.add_time(mustime)
        if clef:
            sign, line, octch = clef
            self.add_clef(sign, line, oct_ch=octch)
        if multirest:
            self.add_bar_style(multirest) 
[docs]
    def create_tempo(self, words, metronome, sound, dots):
        self.add_direction()
        if words:
            self.add_dirwords(words)
        if metronome:
            self.add_metron_dir(metronome[0], metronome[1], dots)
            self.add_sound_dir(sound) 
[docs]
    def create_new_node(self, parentnode, nodename, txt):
        """ The Music XML language is extensive.
        This function can be used to create
        a non basic node not covered elsewhere in this script.
        TODO: add attributes
        """
        new_node = etree.SubElement(parentnode, nodename)
        new_node.text = str(txt) 
    ##
    # Low-level node creation
    ##
[docs]
    def add_creator(self, creator, name):
        """Add creator to score info."""
        attr = {'type': creator }
        self.create_score_info("creator", name, attr) 
[docs]
    def add_rights(self, rights, type=None):
        """Add rights to score info."""
        attr = {'type': type} if type else {}
        self.create_score_info("rights", rights, attr) 
[docs]
    def create_note(self):
        """Create new note."""
        self.current_note = etree.SubElement(self.current_bar, "note")
        self.current_notation = None
        self.current_artic = None
        self.current_ornament = None
        self.current_tech = None 
[docs]
    def add_pitch(self, step, alter, octave):
        """Create new pitch."""
        pitch = etree.SubElement(self.current_note, "pitch")
        stepnode = etree.SubElement(pitch, "step")
        stepnode.text = str(step)
        if alter:
            altnode = etree.SubElement(pitch, "alter")
            altnode.text = str(alter)
        octnode = etree.SubElement(pitch, "octave")
        octnode.text = str(octave) 
[docs]
    def add_unpitched(self, step, octave):
        """Create unpitched."""
        unpitched = etree.SubElement(self.current_note, "unpitched")
        stepnode = etree.SubElement(unpitched, "display-step")
        stepnode.text = str(step)
        octnode = etree.SubElement(unpitched, "display-octave")
        octnode.text = str(octave) 
[docs]
    def add_accidental(self, alter, caut=False, parenth=False):
        """Create accidental."""
        attrib = {}
        if caut:
            attrib['cautionary'] = 'yes'
        if parenth:
            attrib['parentheses'] = 'yes'
        acc = etree.SubElement(self.current_note, "accidental", attrib)
        acc_dict = {
        0: 'natural',
        1: 'sharp', -1: 'flat',
        2: 'sharp-sharp', -2: 'flat-flat',
        0.5: 'natural-up', -0.5: 'natural-down',
        1.5: 'sharp-up', -1.5: 'flat-down'
        }
        acc.text = acc_dict[alter] 
[docs]
    def set_stem_dir(self, dir):
        stem_dir = etree.SubElement(self.current_note, "stem")
        stem_dir.text = dir 
[docs]
    def add_rest(self):
        """Create rest."""
        etree.SubElement(self.current_note, "rest") 
[docs]
    def add_rest_w_pos(self, step, octave):
        """Create rest with display position."""
        restnode = etree.SubElement(self.current_note, "rest")
        stepnode = etree.SubElement(restnode, "display-step")
        octnode = etree.SubElement(restnode, "display-octave")
        stepnode.text = str(step)
        octnode.text = str(octave) 
[docs]
    def add_skip(self, duration, forward=True):
        if forward:
            skip = etree.SubElement(self.current_bar, "forward")
        else:
            skip = etree.SubElement(self.current_bar, "backward")
        dura_node = etree.SubElement(skip, "duration")
        dura_node.text = str(duration) 
[docs]
    def add_div_duration(self, divdur):
        """Create new duration """
        self.duration = etree.SubElement(self.current_note, "duration")
        self.duration.text = str(divdur)
        self.mult = 1 
[docs]
    def change_div_duration(self, newdura):
        """Set new duration when tuplet """
        self.duration.text = str(newdura) 
[docs]
    def add_duration_type(self, durtype):
        """Create new type """
        typenode = etree.SubElement(self.current_note, "type")
        typenode.text = str(durtype) 
[docs]
    def add_dot(self):
        """Create a dot """
        etree.SubElement(self.current_note, "dot") 
[docs]
    def add_beam(self, nr, beam_type):
        """Add beam. """
        beam_node = etree.SubElement(self.current_notation, "beam", number=str(nr))
        beam_node.text = beam_type 
[docs]
    def add_tie(self, tie_type):
        """Create node tie (used for sound of tie) """
        # A tie must be directly after a duration
        insert_at = get_tag_index(self.current_note, "duration") + 1
        tie_element = etree.Element("tie", type=tie_type)
        self.current_note.insert(insert_at, tie_element) 
[docs]
    def add_grace(self, slash):
        """Create grace node """
        if slash:
            etree.SubElement(self.current_note, "grace", slash="yes")
        else:
            etree.SubElement(self.current_note, "grace") 
[docs]
    def add_notations(self):
        if not self.current_notation:
            self.current_notation = etree.SubElement(self.current_note, "notations") 
[docs]
    def add_tied(self, tie_type):
        """Create node tied (used for notation of tie) """
        etree.SubElement(self.current_notation, "tied", type=tie_type) 
[docs]
    def add_time_modify(self, fraction):
        """Create time modification """
        index = get_tag_index(self.current_note, "accidental")
        if index == -1:
            index = get_tag_index(self.current_note, "dot")
        if index == -1:
            index = get_tag_index(self.current_note, "type")
        timemod_node = etree.Element("time-modification")
        actual_notes = etree.SubElement(timemod_node, "actual-notes")
        actual_notes.text = str(fraction[0])
        norm_notes = etree.SubElement(timemod_node, "normal-notes")
        norm_notes.text = str(fraction[1])
        self.current_note.insert(index + 1, timemod_node) 
[docs]
    def get_time_modify(self):
        """Check if time-modification node already exists."""
        return self.current_note.find("time-modification") 
[docs]
    def adjust_time_modify(self, timemod_node, fraction):
        """Adjust existing time-modification node."""
        actual_notes = timemod_node.find("actual-notes")
        an = int(actual_notes.text) * fraction[0]
        actual_notes.text = str(an)
        norm_notes = timemod_node.find("normal-notes")
        nn = int(norm_notes.text) * fraction[1]
        norm_notes.text = str(nn) 
[docs]
    def add_tuplet_type(self, nr, ttype, actnr=0, acttype='', normnr=0, normtype=''):
        """Create tuplet with type attribute """
        tuplnode = etree.SubElement(self.current_notation, "tuplet",
                                    {'number': str(nr), 'type': ttype })
        if actnr:
            actnode = etree.SubElement(tuplnode, "tuplet-actual")
            atn = etree.SubElement(actnode, "tuplet-number")
            atn.text = str(actnr)
            att = etree.SubElement(actnode, "tuplet-type")
            if not acttype:
                acttype = self.current_note.find("type").text
            att.text = acttype
        if normnr:
            normnode = etree.SubElement(tuplnode, "tuplet-normal")
            ntn = etree.SubElement(normnode, "tuplet-number")
            ntn.text = str(normnr)
            ntt = etree.SubElement(normnode, "tuplet-type")
            if not normtype:
                normtype = self.current_note.find("type").text
            ntt.text = normtype 
[docs]
    def add_slur(self, nr, sl_type):
        """Add slur. """
        self.add_notations()
        etree.SubElement(self.current_notation, "slur", {'number': str(nr), 'type': sl_type }) 
[docs]
    def add_named_notation(self, notate):
        """Fermata, etc. """
        self.add_notations()
        etree.SubElement(self.current_notation, notate) 
[docs]
    def add_articulations(self):
        """Common function for creating all types of articulations. """
        if not self.current_artic:
            self.current_artic = etree.SubElement(self.current_notation, "articulations") 
[docs]
    def add_named_artic(self, artic):
        """Add articulation with specified name. """
        etree.SubElement(self.current_artic, artic) 
[docs]
    def add_ornaments(self):
        if not self.current_ornament:
            self.add_notations()
            self.current_ornament = etree.SubElement(self.current_notation, "ornaments") 
[docs]
    def add_tremolo(self, trem_type, lines):
        self.add_ornaments()
        trem_node = etree.SubElement(self.current_ornament, "tremolo", type=trem_type)
        trem_node.text = str(lines) 
[docs]
    def add_trill(self):
        etree.SubElement(self.current_ornament, "trill-mark") 
[docs]
    def add_turn(self):
        etree.SubElement(self.current_ornament, "turn") 
[docs]
    def add_mordent(self):
        etree.SubElement(self.current_ornament, "mordent") 
[docs]
    def add_prall(self):
        etree.SubElement(self.current_ornament, "inverted-mordent") 
[docs]
    def add_wavyline(self, end_type):
        self.add_ornaments
        etree.SubElement(self.current_ornament, "wavy-line", type=end_type) 
[docs]
    def add_gliss(self, linetype, endtype, nr):
        nodedict = { "line-type": linetype, "number": str(nr), "type": endtype }
        self.add_notations()
        etree.SubElement(self.current_notation, "glissando", nodedict) 
[docs]
    def add_technical(self):
        if not self.current_tech:
            self.add_notations()
            self.current_tech = etree.SubElement(self.current_notation, "technical") 
[docs]
    def add_fingering(self, finger_nr):
        self.add_technical()
        fing_node = etree.SubElement(self.current_tech, "fingering")
        fing_node.text = str(finger_nr) 
[docs]
    def create_bar_attr(self):
        """Create node attributes """
        self.bar_attr = etree.SubElement(self.current_bar, "attributes") 
[docs]
    def add_divisions(self, div):
        division = etree.SubElement(self.bar_attr, "divisions")
        division.text = str(div) 
[docs]
    def add_key(self, key, mode):
        keynode = etree.SubElement(self.bar_attr, "key")
        fifths = etree.SubElement(keynode, "fifths")
        fifths.text = str(key)
        modenode = etree.SubElement(keynode, "mode")
        modenode.text = str(mode) 
[docs]
    def add_time(self, timesign):
        if len(timesign)==3:
            timenode = etree.SubElement(self.bar_attr, "time", symbol=timesign[2])
        else:
            timenode = etree.SubElement(self.bar_attr, "time")
        beatnode = etree.SubElement(timenode, "beats")
        beatnode.text = str(timesign[0])
        typenode = etree.SubElement(timenode, "beat-type")
        typenode.text = str(timesign[1]) 
[docs]
    def add_clef(self, sign, line, nr=0, oct_ch=0):
        if nr:
            clefnode = etree.SubElement(self.bar_attr, "clef", number=str(nr))
        else:
            clefnode = etree.SubElement(self.bar_attr, "clef")
        signnode = etree.SubElement(clefnode, "sign")
        signnode.text = str(sign)
        if line:
            linenode = etree.SubElement(clefnode, "line")
            linenode.text = str(line)
        if oct_ch:
            octchnode = etree.SubElement(clefnode, "clef-octave-change")
            octchnode.text = str(oct_ch) 
[docs]
    def add_bar_style(self, multirest=None):
        bar_style = etree.SubElement(self.bar_attr, "measure-style")
        if multirest:
            multirestnode = etree.SubElement(bar_style, "multiple-rest")
            multirestnode.text = str(multirest) 
[docs]
    def new_system(self, force_break):
        etree.SubElement(self.current_bar, "print", {'new-system':force_break}) 
[docs]
    def add_barline(self, bl_type, repeat=None):
        barnode = etree.SubElement(self.current_bar, "barline", location="right")
        barstyle = etree.SubElement(barnode, "bar-style")
        barstyle.text = bl_type
        if repeat:
            repeatnode = etree.SubElement(barnode, "repeat", direction=repeat) 
[docs]
    def add_backup(self, duration):
        if duration <= 0:
            return
        backupnode = etree.SubElement(self.current_bar, "backup")
        durnode = etree.SubElement(backupnode, "duration")
        durnode.text = str(duration) 
[docs]
    def add_voice(self, voice):
        voicenode = etree.SubElement(self.current_note, "voice")
        voicenode.text = str(voice) 
[docs]
    def add_staff(self, staff):
        staffnode = etree.SubElement(self.current_note, "staff")
        staffnode.text = str(staff) 
[docs]
    def add_staves(self, staves):
        index = get_tag_index(self.bar_attr, "time")
        stavesnode = etree.Element("staves")
        stavesnode.text = str(staves)
        self.bar_attr.insert(index + 1, stavesnode) 
[docs]
    def add_chord(self):
        etree.SubElement(self.current_note, "chord") 
[docs]
    def add_direction(self, pos="above"):
        self.direction = etree.SubElement(self.current_bar, "direction", placement=pos) 
[docs]
    def add_dynamic_mark(self, dyn):
        """Add specified dynamic mark."""
        direction = etree.SubElement(self.current_bar, "direction", placement='below')
        dirtypenode = etree.SubElement(direction, "direction-type")
        dyn_node = etree.SubElement(dirtypenode, "dynamics")
        dynexpr_node = etree.SubElement(dyn_node, dyn) 
[docs]
    def add_dynamic_wedge(self, wedge_type):
        """Add dynamic wedge/hairpin."""
        direction = etree.SubElement(self.current_bar, "direction", placement='below')
        dirtypenode = etree.SubElement(direction, "direction-type")
        dyn_node = etree.SubElement(dirtypenode, "wedge", type=wedge_type) 
[docs]
    def add_dynamic_text(self, text):
        """Add dynamic text."""
        direction = etree.SubElement(self.current_bar, "direction", placement='below')
        dirtypenode = etree.SubElement(direction, "direction-type")
        dyn_node = etree.SubElement(dirtypenode, "words")
        dyn_node.attrib['font-style'] = 'italic'
        dyn_node.text = text 
[docs]
    def add_dynamic_dashes(self, text):
        """Add dynamics dashes."""
        direction = etree.SubElement(self.current_bar, "direction", placement='below')
        dirtypenode = etree.SubElement(direction, "direction-type")
        dyn_node = etree.SubElement(dirtypenode, "dashes", type=text) 
[docs]
    def add_octave_shift(self, plac, octdir, size):
        """Add octave shift."""
        oct_dict = {"type": octdir, "size": str(size) }
        direction = etree.SubElement(self.current_bar, "direction", placement=plac)
        dirtypenode = etree.SubElement(direction, "direction-type")
        dyn_node = etree.SubElement(dirtypenode, "octave-shift", oct_dict) 
[docs]
    def add_dirwords(self, words):
        """Add words in direction, e. g. a tempo mark."""
        if self.current_bar.find('direction') == None:
            self.add_direction()
        dirtypenode = etree.SubElement(self.direction, "direction-type")
        wordsnode = etree.SubElement(dirtypenode, "words")
        wordsnode.text = words 
[docs]
    def add_mark(self, mark):
        """Add rehearsal mark in direction"""
        if self.current_bar.find('direction') == None:
            self.add_direction()
        dirtypenode = etree.SubElement(self.direction, "direction-type")
        rehearsalnode = etree.SubElement(dirtypenode, "rehearsal")
        rehearsalnode.text = mark 
[docs]
    def add_metron_dir(self, unit, beats, dots):
        dirtypenode = etree.SubElement(self.direction, "direction-type")
        metrnode = etree.SubElement(dirtypenode, "metronome")
        bunode = etree.SubElement(metrnode, "beat-unit")
        bunode.text = unit
        if dots:
            for d in range(dots):
                etree.SubElement(metrnode, "beat-unit-dot")
        pmnode = etree.SubElement(metrnode, "per-minute")
        pmnode.text = str(beats) 
[docs]
    def add_sound_dir(self, midi_tempo):
        # FIXME: remove the int conversion once LilyPond accepts decimal tempo
        soundnode = etree.SubElement(self.direction, "sound", tempo=str(int(midi_tempo))) 
[docs]
    def add_lyric(self, txt, syll, nr, ext=False):
        """ Add lyric element. """
        lyricnode = etree.SubElement(self.current_note, "lyric", number=str(nr))
        syllnode = etree.SubElement(lyricnode, "syllabic")
        syllnode.text = syll
        txtnode = etree.SubElement(lyricnode, "text")
        txtnode.text = txt
        if ext:
            etree.SubElement(lyricnode, "extend") 
    ##
    # Create the XML document
    ##
[docs]
    def musicxml(self, prettyprint=True):
        xml = MusicXML(self.tree)
        if prettyprint:
            xml.indent("  ")
        return xml 
 
[docs]
class MusicXML(object):
    """Represent a generated MusicXML tree."""
    def __init__(self, tree):
        self.tree = tree
        self.root = tree.getroot()
[docs]
    def indent(self, indent="  "):
        """Add indent and linebreaks to the created XML tree."""
        import ly.etreeutil
        ly.etreeutil.indent(self.root, indent) 
[docs]
    def tostring(self, encoding='UTF-8'):
        """Output etree as a XML document."""
        return etree.tostring(self.root, encoding=encoding, method="xml") 
[docs]
    def write(self, file, encoding='UTF-8', doctype=True):
        """Write XML to a file (file obj or filename)."""
        def write(f):
            if doctype:
                f.write((xml_decl_txt + "\n").format(encoding=encoding).encode(encoding))
                f.write((doctype_txt + "\n").encode(encoding))
                self.tree.write(f, encoding=encoding, xml_declaration=False)
            else:
                self.tree.write(f, encoding=encoding, xml_declaration=True, method="xml")
        if hasattr(file, 'write'):
            write(file)
            # do not close if it already was a file object
        else:
            # it is not a file object
            with open(file, 'wb') as f:
                write(f) 
 
[docs]
def get_tag_index(node, tag):
    """Return the (first) index of tag in node.
    If tag is not found, -1 is returned.
    """
    for i, elem in enumerate(list(node)):
        if elem.tag == tag:
            return i
    return -1 
xml_decl_txt = """<?xml version="1.0" encoding="{encoding}"?>"""
doctype_txt = """<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN"
                                "http://www.musicxml.org/dtds/partwise.dtd">"""