def test_name_value_copy_constructor(self): a = MeiAttribute("pname", "c") b = MeiAttribute(a) b.value = "d" self.assertEqual(b.name, a.name) self.assertNotEqual(b.value, a.value)
def update_neume_head_shape(self, id, shape, ulx, uly, lrx, lry): """ Update the head shape of the given punctum. Update bounding box, if it has changed. Update neume name, if the new head shape changes the name. """ neume = self.mei.getElementById(id) nc = neume.getChildrenByName("nc")[0] if shape == "punctum": neume_name = "punctum" nc.setAttributes([]) elif shape == "punctum_inclinatum": neume_name = "punctum" attrs = [MeiAttribute("inclinatum", "true")] nc.setAttributes(attrs) elif shape == "punctum_inclinatum_parvum": neume_name = "punctum" attrs = [ MeiAttribute("inclinatum", "true"), MeiAttribute("deminutus", "true") ] nc.setAttributes(attrs) elif shape == "quilisma": neume_name = "punctum" attrs = [MeiAttribute("quilisma", "true")] nc.setAttributes(attrs) elif shape == "virga": neume_name = "virga" nc.setAttributes([]) elif shape == "cavum": neume_name = "cavum" nc.setAttributes([]) elif shape == "tractulus": neume_name = "tractulus" nc.setAttributes([]) elif shape == "gravis": neume_name = "gravis" nc.setAttributes([]) elif shape == "oriscus": neume_name = "oriscus" nc.setAttributes([]) elif shape == "stropha": neume_name = "stropha" nc.setAttributes([]) neume.addAttribute("name", neume_name) self.update_or_add_zone(neume, ulx, uly, lrx, lry)
def getMEIContent(self, dumpVisualization=False): """Extract zones and neumes from the source file""" neumeElements = [] zones = [] for elem in etree.parse( self.xmlFile).xpath('/gamera-database/glyphs/glyph'): # Get the relevant attributes from the glyph element startX = int(elem.get('ulx')) endX = startX + int(elem.get('ncols')) startY = int(elem.get('uly')) endY = startY + int(elem.get('nrows')) curNeumeName = elem.xpath('string(./ids/id/@name)') # Create the MEI neume element newNeumeElement = MeiElement('neume') neumeElements.append(newNeumeElement) newNeumeElement.id = generate_MEI_ID() splitName = curNeumeName[curNeumeName.find(".") + 1:] if (splitName in self.neumeNames): newNeumeElement.addAttribute( MeiAttribute('name', self.neumeNames[splitName])) elif len(splitName) < 3: newNeumeElement.addAttribute( MeiAttribute('name', "Letter " + splitName.upper())) else: newNeumeElement.addAttribute(MeiAttribute('name', splitName)) zoneID = generate_MEI_ID() newNeumeElement.addAttribute(MeiAttribute('facs', zoneID)) zones.append(Zone(zoneID, startX, startY, endX, endY)) zoneElements = [] for zone in self._sortZones(zones, dumpVisualization=dumpVisualization): newZoneElement = MeiElement('zone') zoneElements.append(newZoneElement) newZoneElement.id = zone.id newZoneElement.addAttribute(MeiAttribute('ulx', str(zone.startX))) newZoneElement.addAttribute(MeiAttribute('uly', str(zone.startY))) newZoneElement.addAttribute(MeiAttribute('lrx', str(zone.endX))) newZoneElement.addAttribute(MeiAttribute('lry', str(zone.endY))) return zoneElements, neumeElements
def switch_tie(meifile, tstamp=False, keep_id=False): ''' Changes all ties expressed with attributes into elements. If tstamp is set, it will attempt to generate tstamps instead of startid/endid pairs. @TODO At some point search() will support passing in args will narrow down the search by only retrieving objects with that attribute. Make sure to update this function when that happens ''' meifile_flat = meifile.getFlattenedTree() for n in meifile_flat: if n.hasAttribute('tie'): if n.getAttribute("tie").value == 'i' or n.getAttribute( "tie").value == 'm': #one tie element for each tie! #get ancestor measure measure = n.getAncestor('measure') #create a tie element tie = MeiElement("tie") #determine attributes according to args # atts = {'xml:id': generate_mei_id()} atts = MeiAttributeList() if tstamp: atts.append( MeiAttribute("tstamp", str(time.id_to_tstamp(n)))) # atts['tstamp'] = time.id_to_tstamp(n) if keep_id or (not keep_id and not tstamp): atts.append(MeiAttribute("startid", n.id)) # atts['startid'] = n.id #add attributes to tie tie.attributes = atts #add tie to measure measure.addChild(tie) #remove tie attribute n.removeAttribute('tie')
def test_pushtoattributes(self): m = MeiElement('mei') m.addAttribute('foo', '1') m.addAttribute('bar', '2') m.addAttribute('baz', '3') attrs = m.getAttributes() new = MeiAttribute('hi', 'there') attrs.push(new) self.assertEqual('there', attrs[0].value) self.assertEqual(4, len(attrs))
def test_newattrobj(self): el = MeiElement("mei") at = MeiAttribute("meiversion", "2012") el.addAttribute(at) self.assertTrue(el.hasAttribute("meiversion"))
def processContigRange(self, ema_range): """ Process a contigous range of measures give an MEI doc and an EmaExpression.EmaMeasureRange object """ # get all the spanners for total extension of meaure selection # (including gap measures, if present) # NB: Doing this for every range may be inefficient for larger files spanners = self.getMultiMeasureSpanners(ema_range.measures[0].idx - 1, ema_range.measures[-1].idx - 1) # Let's start with measures for i, ema_m in enumerate(ema_range.measures): is_first_m = i == 0 is_last_m = i == len(ema_range.measures) - 1 # Get requested measure measure = self.measures[ema_m.idx - 1] events = measure.getChildren() # determine current measure beat info meter = None for change in self.timeChanges: if int(change) + 1 <= ema_m.idx: meter = self.beatsInfo[change] # Get list of staff numbers in current measure sds = [ int(sd.getAttribute("n").getValue()) for sd in measure.getClosestStaffDefs() ] # Set aside selected staff numbers s_nos = [] # Proceed to locate requested staves for ema_s in ema_m.staves: if ema_s.number not in sds: # CAREFUL: there may be issues with "silent" staves # that may be defined by missing from current measure. # TODO: Write test, fix. raise BadApiRequest("Requested staff is not defined") s_nos.append(ema_s.number) for s_i, staff in enumerate(measure.getChildrenByName("staff")): s_no = s_i if staff.hasAttribute("n"): s_no = int(staff.getAttribute("n").getValue()) if s_no in s_nos: ema_s = ema_m.staves[s_nos.index(s_no)] # Get other elements affecting the staff, e.g. slurs around = [] for el in list(events): if self._isInStaff(el, s_no): around.append(el) # Create sets of elements marked for selection, removal and # cutting. Elements are not removed or cut immediately to make # sure that beat calcualtions are accurate. marked_as_selected = MeiElementSet() marked_as_space = MeiElementSet() marked_for_removal = MeiElementSet() marked_for_cutting = MeiElementSet() # Now locate the requested beat ranges within the staff for b_i, ema_beat_range in enumerate(ema_s.beat_ranges): is_first_b = i == 0 is_last_b = i == len(ema_s.beat_ranges) - 1 def _remove(el): """ Determine whether a removed element needs to be converted to a space or removed altogether""" if is_last_b and is_last_m: marked_for_removal.add(el) else: marked_as_space.add(el) # shorten them names tstamp_first = ema_beat_range.tstamp_first tstamp_final = ema_beat_range.tstamp_final co = self.ema_exp.completenessOptions # check that the requested beats actually fit in the meter if tstamp_first > int(meter["count"])+1 or \ tstamp_final > int(meter["count"])+1: raise BadApiRequest( "Request beat is out of measure bounds") # Find all descendants with att.duration.musical (@dur) for layer in staff.getDescendantsByName("layer"): cur_beat = 1.0 is_first_match = True for el in layer.getDescendants(): if el.hasAttribute( "dur" ) and not el.hasAttribute("grace"): dur = self._calculateDur(el, meter) # TODO still problems with non-consecutive beat ranges # e.g. @1@3 # exclude descendants at and in between tstamps if cur_beat >= tstamp_first: # We round to 4 decimal places to avoid issues caused by # tuplet-related calculations, which are admittedly not # well expressed in floating numbers. if round(cur_beat, 4) <= round( tstamp_final, 4): marked_as_selected.add(el) if is_first_match and "cut" in co: marked_for_cutting.add(el) is_first_match = False # discard from removal set if it had # been placed there from other beat # range marked_as_space.discard(el) # Cut the duration of the last element # if completeness = cut needs_cut = cur_beat + dur > tstamp_final + 1 if needs_cut and "cut" in co: marked_for_cutting.add(el) is_first_match = False elif not marked_as_selected.get(el): _remove(el) elif not marked_as_selected.get(el): marked_as_space.add(el) # continue cur_beat += dur # select elements affecting the staff occurring # within beat range for event in around: if not marked_as_selected.get(event): if event.hasAttribute("tstamp"): ts = float( event.getAttribute( "tstamp").getValue()) if ts < 1 and "cut" not in self.ema_exp.completenessOptions: ts = 1 ts2_att = None if event.hasAttribute("tstamp2"): ts2_att = event.getAttribute("tstamp2") if ts > tstamp_final or ( not ts2_att and ts < tstamp_first): marked_for_removal.add(event) elif ts2_att: ts2 = ts2_att.getValue() if "+" not in ts2: if ts2 < tstamp_first: marked_for_removal.add(event) elif ts2 == tstamp_final: marked_as_selected.add(event) marked_for_removal.discard( event) if ts < tstamp_first and ts2 >= tstamp_final: marked_as_selected.add(event) marked_for_removal.discard( event) else: marked_for_removal.add(event) else: marked_as_selected.add(event) else: marked_as_selected.add(event) elif event.hasAttribute("startid"): startid = (event.getAttribute( "startid").getValue().replace("#", "")) target = self.doc.getElementById(startid) if not target: msg = """Unsupported Encoding: attribute startid on element {0} does not point to any element in the document.""".format( event.getName()) raise UnsupportedEncoding( re.sub(r'\s+', ' ', msg.strip())) # Make sure the target event is in the same measure event_m = event.getAncestor( "measure").getId() target_m = target.getAncestor( "measure").getId() if not event_m == target_m: msg = """Unsupported Encoding: attribute startid on element {0} does not point to an element in the same measure.""".format( event.getName()) raise UnsupportedEncoding( re.sub(r'\s+', ' ', msg.strip())) else: if marked_as_selected.get(target): marked_as_selected.add(event) marked_for_removal.discard(event) elif not event.hasAttribute("endid"): marked_for_removal.add(event) else: # Skip if event starts after latest # selected element with duration pos = target.getPositionInDocument( ) is_ahead = False for i in reversed( marked_as_selected. getElements()): if i.hasAttribute("dur"): if pos > i.getPositionInDocument( ): marked_for_removal.add( event) is_ahead = True break if not is_ahead: # last chance to keep it: # must start before and end after # latest selected element with duration endid = ( event.getAttribute("endid") .getValue().replace( "#", "")) target2 = self.doc.getElementById( endid) if marked_as_selected.get( target2): marked_as_selected.add( event) marked_for_removal.discard( event) else: pos2 = target2.getPositionInDocument( ) for i in reversed( marked_as_selected. getElements()): if i.hasAttribute( "dur"): if pos2 > i.getPositionInDocument( ): marked_as_selected.add( event) marked_for_removal.discard( event) else: marked_for_removal.add( event) break # Remove elements marked for removal if "highlight" not in self.ema_exp.completenessOptions: for el in marked_for_removal: el.getParent().removeChild(el) # Replace elements marked as spaces with actual spaces, # unless completion = nospace, then remove the elements. for el in marked_as_space: parent = el.getParent() if "nospace" not in self.ema_exp.completenessOptions: space = MeiElement("space") space.setId(el.id) space.addAttribute(el.getAttribute("dur")) if el.getAttribute("dots"): space.addAttribute(el.getAttribute("dots")) elif el.getChildrenByName("dot"): dots = str(len( el.getChildrenByName("dot"))) space.addAttribute( MeiAttribute("dots", dots)) parent.addChildBefore(el, space) el.getParent().removeChild(el) else: for el in marked_as_selected: # add to list if self.highlight_el: cur_plist = self.highlight_el.getAttribute( "plist").getValue() space = "" if len(cur_plist) > 0: space = " " val = cur_plist + space + "#" + el.getId() self.highlight_el.addAttribute("plist", val) else: if "highlight" not in self.ema_exp.completenessOptions: # Remove this staff and its attached events staff.getParent().removeChild(staff) for el in list(events): if self._isInStaff(el, s_no): el.getParent().removeChild(el) # At the first measure, also add relevant multi-measure spanners # for each selected staff if is_first_m: if "highlight" not in self.ema_exp.completenessOptions: for evs in spanners.values(): for event_id in evs: ev = self.doc.getElementById(event_id) # Spanners starting outside of beat ranges # may be already gone if ev: # Determine staff of event for id changes for ema_s in ema_m.staves: staff_no = self._isInStaff( ev, ema_s.number) if staff_no and staff_no in s_nos: # If the event is attached to more than one staff, just # consider it attached to the its first one staff_no = staff_no[0] staff = None for staff_candidate in measure.getDescendantsByName( "staff"): if staff_candidate.hasAttribute("n"): n = int( staff_candidate.getAttribute( "n").getValue()) if n == staff_no: staff = staff_candidate # Truncate event to start at the beginning of the beat range if ev.hasAttribute("startid"): # Set startid to the first event still on staff, # at the first available layer try: layer = (staff.getChildrenByName( "layer")) first_id = layer[0].getChildren( )[0].getId() ev.getAttribute( "startid").setValue("#" + first_id) except IndexError: msg = """ Unsupported encoding. Omas attempted to adjust the starting point of a selected multi-measure element that starts before the selection, but the staff or layer could not be located. """ msg = re.sub( r'\s+', ' ', msg.strip()) raise UnsupportedEncoding(msg) if ev.hasAttribute("tstamp"): # Set tstamp to first in beat selection tstamp_first = 0 for e_s in ema_m.staves: if e_s.number == staff_no: tstamp_first = e_s.beat_ranges[ 0].tstamp_first ev.getAttribute("tstamp").setValue( str(tstamp_first)) # Truncate to end of range if completeness = cut # (actual beat cutting will happen when beat ranges are procesed) if "cut" in self.ema_exp.completenessOptions: if ev.hasAttribute("tstamp2"): att = ev.getAttribute("tstamp2") t2 = att.getValue() p = re.compile(r"([1-9]+)(?=m\+)") multimeasure = p.match(t2) if multimeasure: new_val = len(mm) - 1 att.setValue( p.sub(str(new_val), t2)) # Otherwise adjust tspan2 value to correct distance. # E.g. given 4 measures with a spanner originating # in 1 and ending in 4 and a selection of measures 2 and 3, # change @tspan2 from 3m+X to 2m+X else: if ev.hasAttribute("tstamp2"): att = ev.getAttribute("tstamp2") t2 = att.getValue() p = re.compile(r"([1-9]+)(?=m\+)") multimeasure = p.match(t2) if multimeasure: dis = evs[event_id]["distance"] new_val = int( multimeasure.group( 1)) - dis att.setValue( p.sub(str(new_val), t2)) # move element to first measure and add it to selected # events "around" the staff. ev.moveTo(measure) else: # Add spanners to annotated selection for evs in spanners.values(): for event_id in evs: if self.highlight_el: cur_plist = self.highlight_el.getAttribute( "plist").getValue() space = "" if len(cur_plist) > 0: space = " " val = cur_plist + space + "#" + event_id self.highlight_el.addAttribute("plist", val) return self.doc
def neumify(self, ids, type_id, head_shapes, ulx, uly, lrx, lry): ''' Neumify a group of neumes (with provided ids) and give it the given neume name. Also update bounding box information. ''' # get neume name and variant from type id type_split = type_id.split(".") if type_split[-1].isdigit(): type_split.pop() if len(type_split) == 1: attrs = [MeiAttribute("name", type_split[0])] else: variant = " ".join(type_split[1:]) attrs = [ MeiAttribute("name", type_split[0]), MeiAttribute("variant", variant) ] new_neume = MeiElement("neume") new_neume.setAttributes(attrs) ncs = [] cur_nc = None iNote = 0 for id in ids: ref_neume = self.mei.getElementById(str(id)) if ref_neume: # get underlying notes notes = ref_neume.getDescendantsByName("note") for n in notes: head = str(head_shapes[iNote]) # check if a new nc must be opened if head == 'punctum' and cur_nc != 'punctum': ncs.append(MeiElement("nc")) cur_nc = head elif head == 'punctum_inclinatum' and cur_nc != 'punctum_inclinatum': new_nc = MeiElement("nc") new_nc.addAttribute("inclinatum", "true") ncs.append(new_nc) cur_nc = head elif head == 'punctum_inclinatum_parvum' and cur_nc != 'punctum_inclinatum_parvum': new_nc = MeiElement("nc") new_nc.addAttribute("inclinatum", "true") new_nc.addAttribute("deminutus", "true") ncs.append(new_nc) cur_nc = head elif head == 'quilisma' and cur_nc != 'quilisma': new_nc = MeiElement("nc") new_nc.addAttribute("quilisma", "true") ncs.append(new_nc) cur_nc = head elif cur_nc is None: ncs.append(MeiElement("nc")) cur_nc = 'punctum' ncs[-1].addChild(n) iNote += 1 new_neume.setChildren(ncs) # insert the new neume before = self.mei.getElementById(ids[0]) parent = before.getParent() if before and parent: parent.addChildBefore(before, new_neume) # remove the old neumes from the mei document for id in ids: neume = self.mei.getElementById(str(id)) if neume: # remove facs data facs = neume.getAttribute("facs") if facs: facsid = facs.value # Remove the zone if it exists zone = self.mei.getElementById(str(facsid)) if zone and zone.name == "zone": zone.parent.removeChild(zone) # now remove the neume neume.parent.removeChild(neume) # update bounding box data self.update_or_add_zone(new_neume, ulx, uly, lrx, lry) result = {"id": new_neume.getId()} return result
def test_name_value_constructor(self): a = MeiAttribute("pname", "c") self.assertEqual("pname", a.name) self.assertEqual("c", a.value)
def _initMEI(self): """Initialize a new MEI document Sets the attributes meiDoc, surface, and initLayer """ self.meiDoc = MeiDocument() root = MeiElement("mei") root.id = generate_MEI_ID() self.meiDoc.root = root #needs meiHead here meiHead = MeiElement('meiHead') fileDesc = MeiElement('fileDesc') titleStmt = MeiElement('titleStmt') title = MeiElement('title') pubStmt = MeiElement('pubStmt') date = MeiElement('date') encodingDesc = MeiElement('encodingDesc') projectDesc = MeiElement('projectDesc') p = MeiElement('p') music = MeiElement('music') facsimile = MeiElement('facsimile') self.surface = MeiElement('surface') # Label the surface with the name of the input file, which could help # identify the original image label = os.path.basename(os.path.splitext(self.xmlFile)[0]) self.surface.addAttribute(MeiAttribute('label', label)) #systems get added to page #neumes get added to systems body = MeiElement('body') mdiv = MeiElement('mdiv') pages = MeiElement('pages') page = MeiElement('page') page.id = generate_MEI_ID() initSystem = MeiElement('system') initSystem.id = generate_MEI_ID() initStaff = MeiElement('staff') initStaff.id = generate_MEI_ID() self.initLayer = MeiElement('layer') self.initLayer.id = generate_MEI_ID() root.addChild(meiHead) meiHead.addChild(fileDesc) fileDesc.addChild(titleStmt) titleStmt.addChild(title) fileDesc.addChild(pubStmt) pubStmt.addChild(date) meiHead.addChild(encodingDesc) encodingDesc.addChild(projectDesc) projectDesc.addChild(p) root.addChild(music) music.addChild(facsimile) facsimile.addChild(self.surface) music.addChild(body) body.addChild(mdiv) mdiv.addChild(pages) pages.addChild(page) page.addChild(initSystem) initSystem.addChild(initStaff) initStaff.addChild(self.initLayer)