Repository: cuthbertLab/music21
Pull request #126 (2015-12-17 02:35:40)

MEI Improvements
Before working on cuthbertLab/music21#125, I thought I should submit a pull request for all of this stuff. I don't really remember doing this work, but it seems like I added support for lyrics, ``<space>`` elements, and ``<barLine>`` elements, and rewrote and added tests for score-wide metadata handling.

Of course, suggestions for revision are welcome.


Patch:
diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py
index 1c89d55b8..cdbc2f7b9 100644
--- a/music21/converter/__init__.py
+++ b/music21/converter/__init__.py
@@ -64,13 +64,6 @@ from music21 import environment
 _MOD = 'converter/__init__.py'
 environLocal = environment.Environment(_MOD)
 
-# use the faster library if possible (won't be possible on Jython, PyPy, etc.)
-try:
-    import xml.etree.cElementTree as ETree
-except ImportError:
-    import xml.etree.ElementTree as ETree
-
-
 
 #-------------------------------------------------------------------------------
 class ArchiveManagerException(exceptions21.Music21Exception):
diff --git a/music21/converter/subConverters.py b/music21/converter/subConverters.py
index c64741eab..d7a81b319 100644
--- a/music21/converter/subConverters.py
+++ b/music21/converter/subConverters.py
@@ -1239,16 +1239,10 @@ class Test(unittest.TestCase):
         '''
         When the string starts with "mei:"
         '''
-        try:
-            # this works in Python 3.3+
+        if six.PY3:
             from unittest import mock  # pylint: disable=no-name-in-module
-        except ImportError:
-            try:
-                # system library overrides the built-in
-                import mock
-            except ImportError:
-                # last resort
-                from music21.ext import mock
+        else:
+            from music21.ext import mock
         with mock.patch('music21.mei.MeiToM21Converter') as mockConv:
             testConverter = ConverterMEI()
             testConverter.parseData('mei: <?xml><mei><note/></mei>')
@@ -1258,16 +1252,10 @@ class Test(unittest.TestCase):
         '''
         When the string doesn't start with "mei:"
         '''
-        try:
-            # this works in Python 3.3+
+        if six.PY3:
             from unittest import mock  # pylint: disable=no-name-in-module
-        except ImportError:
-            try:
-                # system library overrides the built-in
-                import mock
-            except ImportError:
-                # last resort
-                from music21.ext import mock
+        else:
+            from music21.ext import mock
         with mock.patch('music21.mei.MeiToM21Converter') as mockConv:
             testConverter = ConverterMEI()
             testConverter.parseData('<?xml><mei><note/></mei>')
diff --git a/music21/mei/base.py b/music21/mei/base.py
index b18bcc8b3..aad9bc1fd 100644
--- a/music21/mei/base.py
+++ b/music21/mei/base.py
@@ -125,6 +125,9 @@ MEI in any other context.
   does not have the @numbase attribute.
 * Retain consistent @n values for the same layer, staff, and instrument throughout the score.
 * Always indicate the duration of <mRest> and <mSpace> elements.
+* Avoid using the <barLine> element if you require well-formatted output from music21, since (as of
+  January 2015) the music21-to-something converters will only output a :class:`Barline` that is
+  part of a :class:`Measure`.
 
 **List of Supported Elements**
 
@@ -132,6 +135,7 @@ Alphabetical list of the elements currently supported by this module:
 
 * :func:`accidFromElement`
 * :func:`articFromElement`
+* :func:`barLineFromElement`
 * :func:`beamFromElement`
 * :func:`chordFromElement`
 * :func:`clefFromElement`
@@ -140,21 +144,36 @@ Alphabetical list of the elements currently supported by this module:
 * :func:`layerFromElement`
 * :func:`measureFromElement`
 * :func:`noteFromElement`
-* :func:`spaceFromElement`
-* :func:`mSpaceFromElement`
 * :func:`restFromElement`
 * :func:`mRestFromElement`
+* :func:`spaceFromElement`
+* :func:`mSpaceFromElement`
 * :func:`scoreFromElement`
-* :func:`sectionFromElement`
 * :func:`scoreDefFromElement`
+* :func:`sectionFromElement`
 * :func:`staffFromElement`
 * :func:`staffDefFromElement`
 * :func:`staffGrpFromElement`
+* :func:`sylFromElement`
 * :func:`tupletFromElement`
+* :func:`verseFromElement`
 
 To know which MEI attributes are known to import correctly, read the documentation for the relevant
 element. For example, to know whether the @color attribute on a <note> element is supported, read
 the "Attributes/Elements Implemented" section of the :func:`noteFromElement` documentation.
+
+**List of Ignored Elements**
+
+The following elements are (silently) ignored by the MEI-to-music21 converter because they primarily
+affect the layout and typesetting of a musical score. We may choose to implement these elements in
+the future, but they are a lower priority because music21 is not primarily a layout or typesetting
+tool.
+
+* <multiRest>: a multi-measure rest (these will be "converted" to single-measure rests)
+* <pb>: a page break
+* <lb>: a line break
+* <sb>: a system break
+
 '''
 
 # Determine which ElementTree implementation to use.
@@ -231,9 +250,11 @@ class MeiElementError(exceptions21.Music21Exception):
 
 # Text Strings for Error Conditions
 #------------------------------------------------------------------------------
+# NOTE: these are all collected handily at the top for two reasons: help you find the easier, and
+#       help you translate them easier
 _TEST_FAILS = 'MEI module had {} failures and {} errors; run music21/mei/base.py to find out more.'
 _INVALID_XML_DOC = 'MEI document is not valid XML.'
-_WRONG_ROOT_ELEMENT = 'Root element should be <mei>, not <{}>.'
+_WRONG_ROOT_ELEMENT = 'Root element should be <mei> in the MEI namespace, not <{}>.'
 _UNKNOWN_TAG = 'Found unexpected tag while parsing MEI: <{}>.'
 _UNEXPECTED_ATTR_VALUE = 'Unexpected value for "{}" attribute: {}'
 _SEEMINGLY_NO_PARTS = 'There appear to be no <staffDef> tags in this score.'
@@ -242,6 +263,8 @@ _CANNOT_FIND_XMLID = 'Could not find the @{} so we could not create the {}.'
 _MISSING_TUPLET_DATA = 'Both @num and @numbase attributes are required on <tuplet> tags.'
 _UNIMPLEMENTED_IMPORT = 'Importing {} without {} is not yet supported.'
 _UNPROCESSED_SUBELEMENT = 'Found an unprocessed <{}> element in a <{}>.'
+_MISSED_DATE = 'Unable to decipher the composition date "{}"'
+_BAD_VERSE_NUMBER = 'Verse number must be an int (got "{}")'
 
 
 # Module-level Functions
@@ -382,7 +405,7 @@ def makeDuration(base=0.0, dots=0):
     0.5
     '''
     returnDuration = duration.Duration(base)
-    returnDuration.dots = dots
+    returnDuration.dots = dots  # pylint: disable=assigning-non-slot
     return returnDuration
 
 
@@ -473,7 +496,7 @@ _ARTIC_ATTR_DICT = {'acc': articulations.Accent, 'stacc': articulations.Staccato
 # for _barlineFromAttr()
 # TODO: make new music21 Barline styles for 'dbldashed' and 'dbldotted'
 _BAR_ATTR_DICT = {'dashed': 'dashed', 'dotted': 'dotted', 'dbl': 'double', 'end': 'final',
-                  'invis': 'none', 'single': 'none'}
+                  'invis': 'none', 'single': 'regular'}
 
 
 # One-to-One Translator Functions
@@ -837,7 +860,6 @@ def _ppTuplets(theConverter):
             # Ideally (for us) <tupletSpan> elements will have a @plist that enumerates the
             # @xml:id of every affected element. In this case, tupletSpanFromElement() can use the
             # @plist to add our custom @m21TupletNum and @m21TupletNumbase attributes.
-            # TODO: use @startid and @endid, if present, to set the duration.tuplets "type"
             for eachXmlid in eachTuplet.get('plist', '').split(' '):
                 eachXmlid = removeOctothorpe(eachXmlid)
                 if 0 < len(eachXmlid):
@@ -1188,70 +1210,109 @@ def removeOctothorpe(xmlid):
         return xmlid
 
 
-def makeMetadata(fromThis):
-    # TODO: tests
-    # TODO: break into sub-functions
-    # TODO/NOTE: only returns a single Metadata objects atm
+def makeMetadata(documentRoot):
     '''
     Produce metadata objects for all the metadata stored in the MEI header.
 
-    :param fromThis: The MEI file's root tag.
-    :type fromThis: :class:`~xml.etree.ElementTree.Element`
-    :returns: Metadata objects that hold the metadata stored in the MEI header.
-    :rtype: sequence of :class:`music21.metadata.Metadata` and 
-       :class:`~music21.metadata.RichMetadata`.
+    :param documentRoot: The MEI document's root element.
+    :type documentRoot: :class:`~xml.etree.ElementTree.Element`
+    :returns: A :class:`Metadata` object with some of the metadata stored in the MEI document.
+    :rtype: :class:`music21.metadata.Metadata`
     '''
-    fromThis = fromThis.find('.//{}work'.format(_MEINS))
-    if fromThis is None:
-        return []
-
     meta = metadata.Metadata()
-    #richMeta = metadata.RichMetadata()
-
-    for eachTag in fromThis.iterfind('*'):
-        if eachTag.tag == '{}titleStmt'.format(_MEINS):
-            for subTag in eachTag.iterfind('*'):
-                if subTag.tag == '{}title'.format(_MEINS):
-                    if subTag.get('type', '') == 'subtitle':
-                        # TODO: this is meaningless because m21's 
-                        #    Metadata doesn't do anything with it
-                        meta.subtitle = subTag.text
-                    elif meta.title is None:
-                        meta.title = subTag.text
-                elif subTag.tag == '{}respStmt'.format(_MEINS):
-                    for subSubTag in subTag.iterfind('*'):
-                        if subSubTag.tag == '{}persName'.format(_MEINS):
-                            if subSubTag.get('role') == 'composer':
-                                meta.composer = subSubTag.text
-
-        elif eachTag.tag == '{}history'.format(_MEINS):
-            for subTag in eachTag.iterfind('*'):
-                if subTag.tag == '{}creation'.format(_MEINS):
-                    for subSubTag in subTag.iterfind('*'):
-                        if subSubTag.tag == '{}date'.format(_MEINS):
-                            if subSubTag.text is None:
-                                dateStart, dateEnd = None, None
-                                if subSubTag.get('isodate') is not None:
-                                    meta.date = subSubTag.get('isodate')
-                                elif subSubTag.get('notbefore') is not None:
-                                    dateStart = subSubTag.get('notbefore')
-                                elif subSubTag.get('startdate') is not None:
-                                    dateStart = subSubTag.get('startdate')
-
-                                if subSubTag.get('notafter') is not None:
-                                    dateEnd = subSubTag.get('notafter')
-                                elif subSubTag.get('enddate') is not None:
-                                    dateEnd = subSubTag.get('enddate')
-
-                                if dateStart is not None and dateEnd is not None:
-                                    meta.date = metadata.DateBetween((dateStart, dateEnd))
-                            else:
-                                meta.date = subSubTag.text
-
-        elif eachTag.tag == '{}tempo'.format(_MEINS):
-            # NB: this has to be done after a proper, movement-specific title would have been set
-            if meta.movementName is None:
-                meta.movementName = eachTag.text
+    work = documentRoot.find('.//{}work'.format(_MEINS))
+    if work is not None:
+        # title, subtitle, and movement name
+        meta = metaSetTitle(work, meta)
+        # composer
+        meta = metaSetComposer(work, meta)
+        # date
+        meta = metaSetDate(work, meta)
+
+    return meta
+
+
+def metaSetTitle(work, meta):
+    '''
+    From a <work> element, find the title, subtitle, and movement name (<tempo> element) and store
+    the values in a :class:`Metadata` object.
+
+    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
+    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
+    :return: The ``meta`` argument, having relevant metadata added.
+    '''
+    # title, subtitle, and movement name
+    for title in work.findall('./{mei}titleStmt/{mei}title'.format(mei=_MEINS)):
+        if title.get('type', '') == 'subtitle':
+            meta.subtitle = title.text
+        elif meta.title is None:
+            meta.title = title.text
+
+    if hasattr(meta, 'subtitle'):
+        # Since m21.Metadata doesn't actually have a "subtitle" attribute, we'll put the subtitle
+        # in the title
+        meta.title = '{} ({})'.format(meta.title, meta.subtitle)
+        del meta.subtitle
+
+    tempo = work.find('./{}tempo'.format(_MEINS))
+    if tempo is not None:
+        meta.movementName = tempo.text
+
+    return meta
+
+
+def metaSetComposer(work, meta):
+    '''
+    From a <work> element, find the composer(s) and store the values in a :class:`Metadata` object.
+
+    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
+    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
+    :return: The ``meta`` argument, having relevant metadata added.
+    '''
+    composers = []
+    for persName in work.findall('./{mei}titleStmt/{mei}respStmt/{mei}persName'.format(mei=_MEINS)):
+        if persName.get('role') == 'composer' and persName.text:
+            composers.append(persName.text)
+    for composer in work.findall('./{mei}titleStmt/{mei}composer'.format(mei=_MEINS)):
+        if composer.text:
+            composers.append(composer.text)
+        else:
+            persName = composer.find('./{}persName'.format(_MEINS))
+            if persName.text:
+                composers.append(persName.text)
+    if 1 == len(composers):
+        meta.composer = composers[0]
+    elif 1 < len(composers):
+        meta.composer = composers
+
+    return meta
+
+
+def metaSetDate(work, meta):
+    '''
+    From a <work> element, find the date (range) of composition and store the values in a
+    :class:`Metadata` object.
+
+    :param work: A <work> :class:`~xml.etree.ElementTree.Element` with metadata you want to find.
+    :param meta: The :class:`~music21.metadata.Metadata` object in which to store the metadata.
+    :return: The ``meta`` argument, having relevant metadata added.
+    '''
+    date = work.find('./{mei}history/{mei}creation/{mei}date'.format(mei=_MEINS))
+    if date is not None:  # must use explicit "is not None" for an Element
+        if date.text or date.get('isodate'):
+            dateStr = date.get('isodate') if date.get('isodate') else date.text
+            theDate = metadata.Date()
+            try:
+                theDate.loadStr(dateStr.replace('-', '/'))
+            except ValueError:
+                environLocal.warn(_MISSED_DATE.format(dateStr))
+            else:
+                meta.date = theDate
+        else:
+            dateStart = date.get('notbefore') if date.get('notbefore') else date.get('startdate')
+            dateEnd = date.get('notafter') if date.get('notafter') else date.get('enddate')
+            if dateStart and dateEnd:
+                meta.date = metadata.DateBetween((dateStart, dateEnd))
 
     return meta
 
@@ -1340,7 +1401,7 @@ def scaleToTuplet(objs, elem):
 
 
 def _guessTuplets(theLayer):
-    # TODO: nested tuplets?
+    # TODO: nested tuplets don't work when they're both specified with <tupletSpan>
     # TODO: adjust this to work with cross-measure tuplets (i.e., where only the "start" or "end"
     #       is found in theLayer)
     '''
@@ -1427,7 +1488,13 @@ def scoreDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argume
 
     >>> meiDoc = """<?xml version="1.0" encoding="UTF-8"?>
     ... <scoreDef meter.count="3" meter.unit="4" xmlns="http://www.music-encoding.org/ns/mei">
-    ...     <staffGrp><staffDef n="1" label="Clarinet"/></staffGrp>
+    ...     <staffGrp>
+    ...         <staffDef n="1" label="Clarinet"/>
+    ...         <staffGrp>
+    ...             <staffDef n="2" label="Flute"/>
+    ...             <staffDef n="3" label="Violin"/>
+    ...         </staffGrp>
+    ...     </staffGrp>
     ... </scoreDef>
     ... """
     >>> from music21 import *
@@ -1435,9 +1502,11 @@ def scoreDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argume
     >>> scoreDef = ET.fromstring(meiDoc)
     >>> result = mei.base.scoreDefFromElement(scoreDef)
     >>> len(result)
-    3
+    5
     >>> result['1']
     {'instrument': <music21.instrument.Instrument 1: Clarinet: Clarinet>}
+    >>> result['3']
+    {'instrument': <music21.instrument.Instrument 3: Violin: Violin>}
     >>> result['all-part objects']
     [<music21.meter.TimeSignature 3/4>]
     >>> result['whole-score objects']
@@ -1508,15 +1577,16 @@ def scoreDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argume
     return post
 
 
-def staffGrpFromElement(elem, slurBundle=None):
+def staffGrpFromElement(elem, slurBundle=None, staffDefDict=None):
     '''
     <staffGrp> A group of bracketed or braced staves.
 
     In MEI 2013: pg.448 (462 in PDF) (MEI.shared module)
 
-    For now, this function is merely a container-processor-thing  for <staffDef> elements contained
+    For now, this function is merely a container-processor  for <staffDef> elements contained
     in this <staffGrp> element given as the "elem" argument. That is, the function does not yet
-    create the brackets/braces and labels expected of a staff group.
+    create the brackets/braces and labels expected of a staff group. Note however that all <staffDef>
+    elements will be processed, even if they're contained within several layers of <staffGrp>.
 
     :param elem: The ``<staffGrp>`` element to process.
     :type elem: :class:`~xml.etree.ElementTree.Element`
@@ -1526,6 +1596,7 @@ def staffGrpFromElement(elem, slurBundle=None):
     **Attributes/Elements Implemented:**
 
     - contained <staffDef>
+    - contained <staffGrp>
 
     **Attributes/Elements in Testing:** none
 
@@ -1545,12 +1616,24 @@ def staffGrpFromElement(elem, slurBundle=None):
     **Contained Elements not Implemented:**
 
     - MEI.midi: instrDef
-    - MEI.shared: grpSym label staffGrp
+    - MEI.shared: grpSym label
     '''
+    
     staffDefTag = '{http://www.music-encoding.org/ns/mei}staffDef'
-    return {sd.get('n'): staffDefFromElement(sd, slurBundle) for sd in elem.iterfind(staffDefTag)}
-    # Check that out, eh? Dictionary comprehensions! Sometimes Python does impress me.
+    staffGroupTag = '{http://www.music-encoding.org/ns/mei}staffGrp'
+
+    staffDefDict = staffDefDict if staffDefDict is not None else {}
+
+    for el in elem.findall("*"):
+        # return all staff defs in this staff group
+        if el.tag == staffDefTag:
+            staffDefDict[el.get('n')] = staffDefFromElement(el, slurBundle)
+
+        # recurse if there are more groups, append to the working staffDefDict    
+        elif el.tag == staffGroupTag:
+            staffGrpFromElement(el, slurBundle, staffDefDict)
 
+    return staffDefDict
 
 def staffDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
     '''
@@ -1653,9 +1736,9 @@ def staffDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argume
 
     **Contained Elements not Implemented:**
 
-    - MEI.cmn: meterSig meterSigGrp  TODO: these
+    - MEI.cmn: meterSig meterSigGrp
     - MEI.mensural: mensur proport
-    - MEI.shared: clefGrp keySig label layerDef  TODO: these
+    - MEI.shared: clefGrp keySig label layerDef
     '''
     # mapping from tag name to our converter function
     tagToFunction = {'{http://www.music-encoding.org/ns/mei}clef': clefFromElement}
@@ -1843,6 +1926,113 @@ def accidFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
     return _accidentalFromAttr(elem.get('accid'))
 
 
+def sylFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
+    '''
+    <syl> Individual lyric syllable.
+
+    In MEI 2013: pg.454 (468 in PDF) (MEI.shared module)
+
+    :returns: An appropriately-configured :class:`music21.note.Lyric`.
+
+    **Attributes/Elements Implemented:**
+
+    - @con and @wordpos (from att.syl.log)
+
+    **Attributes/Elements in Testing:** none
+
+    **Attributes not Implemented:**
+
+    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
+    - att.facsimile (@facs)
+    - att.syl.vis (att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight))
+
+        - (att.visualoffset (att.visualoffset.ho (@ho))
+
+            - (att.visualoffset.to (@to))
+            - (att.visualoffset.vo (@vo)))
+
+        - (att.xy (@x, @y))
+        - (att.horizontalalign (@halign))
+
+    - att.syl.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
+
+        -  (att.alignment (@when)))
+
+    **Contained Elements not Implemented:**
+
+    - MEI.edittrans: (all)
+    - MEI.figtable: fig
+    - MEI.namesdates: corpName geogName periodName persName styleName
+    - MEI.ptrref: ptr ref
+    - MEI.shared: address bibl date identifier lb name num rend repository stack title
+    '''
+    wordpos = elem.get('wordpos')
+    wordposDict = {'i': 'begin', 'm': 'middle', 't': 'end', None: None}
+
+    conDict = {'s': ' ', 'd': '-', 't': '~', 'u': '_', None: '-'}
+    if 'i' == wordpos:
+        text = elem.text + conDict[elem.get('con')]
+    elif 'm' == wordpos:
+        text = conDict[elem.get('con')] + elem.text + conDict[elem.get('con')]
+    elif 't' == wordpos:
+        text = conDict[elem.get('con')] + elem.text
+    else:
+        text = elem.text
+
+    syllabic = wordposDict[wordpos]
+
+    if syllabic:
+        return note.Lyric(text=text, syllabic=syllabic, applyRaw=True)
+    else:
+        return note.Lyric(text=text)
+
+
+def verseFromElement(elem, backupN=None, slurBundle=None):  # pylint: disable=unused-argument
+    '''
+    <verse> Lyric verse.
+
+    In MEI 2013: pg.480 (494 in PDF) (MEI.lyrics module)
+
+    :param int backupN: The backup verse number to use if no @n attribute exists on ``elem``.
+    :returns: The appropriately-configured :class:`Lyric` objects.
+    :rtype: list of :class:`music21.note.Lyric`
+
+    **Attributes/Elements Implemented:**
+
+    - @n and <syl>
+
+    **Attributes/Elements in Testing:** none
+
+    **Attributes not Implemented:**
+
+    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
+    - att.facsimile (@facs)
+    - att.lang (@xml:lang)
+    - att.verse.log (@refrain, @rhythm)
+    - att.verse.vis (att.typography (@fontfam, @fontname, @fontsize, @fontstyle, @fontweight))
+
+        - (att.visualoffset.to (@to))
+        - ((att.visualoffset.vo (@vo))
+
+            - (att.xy (@x, @y))
+
+    - att.verse.anl (att.common.anl (@copyof, @corresp, @next, @prev, @sameas, @synch)
+
+        - (att.alignment (@when)))
+
+    **Contained Elements not Implemented:**
+
+    - MEI.shared: dir dynam lb space tempo
+    '''
+    syls = [sylFromElement(s) for s in elem.findall('./{}syl'.format(_MEINS))]
+    for eachSyl in syls:
+        try:
+            eachSyl.number = int(elem.get('n', backupN))
+        except (TypeError, ValueError):
+            environLocal.warn(_BAD_VERSE_NUMBER.format(elem.get('n', backupN)))
+    return syls
+
+
 def noteFromElement(elem, slurBundle=None):
     # NOTE: this function should stay in sync with chordFromElement() where sensible
     '''
@@ -1853,6 +2043,10 @@ def noteFromElement(elem, slurBundle=None):
     .. note:: If set, the @accid.ges attribute is always imported as the music21 :class:`Accidental`
         for this note. We assume it corresponds to the accidental implied by a key signature.
 
+    .. note:: If ``elem`` contains both <syl> and <verse> elements as immediate children, the lyrics
+        indicated with <verse> element(s) will always obliterate those given indicated with <syl>
+        elements.
+
     **Attributes/Elements Implemented:**
 
     - @accid and <accid>
@@ -1867,6 +2061,7 @@ def noteFromElement(elem, slurBundle=None):
     - @slur, (many of "[i|m|t][1-6]")
     - @grace, from att.note.ges.cmn: partial implementation (notes marked as grace, but the
         duration is 0 because we ignore the question of which neighbouring note to borrow time from)
+    - <syl> and <verse>
 
     **Attributes/Elements in Testing:** none
 
@@ -1915,12 +2110,11 @@ def noteFromElement(elem, slurBundle=None):
 
     - MEI.critapp: app
     - MEI.edittrans: (all)
-    - MEI.lyrics: verse
-    - MEI.shared: syl
     '''
     tagToFunction = {'{http://www.music-encoding.org/ns/mei}dot': dotFromElement,
                      '{http://www.music-encoding.org/ns/mei}artic': articFromElement,
-                     '{http://www.music-encoding.org/ns/mei}accid': accidFromElement}
+                     '{http://www.music-encoding.org/ns/mei}accid': accidFromElement,
+                     '{http://www.music-encoding.org/ns/mei}syl': sylFromElement}
 
     # start with a Note with Pitch
     theNote = _accidentalFromAttr(elem.get('accid'))
@@ -1944,6 +2138,8 @@ def noteFromElement(elem, slurBundle=None):
             theNote.articulations.append(subElement)
         elif isinstance(subElement, six.string_types):
             theNote.pitch.accidental = pitch.Accidental(subElement)
+        elif isinstance(subElement, note.Lyric):
+            theNote.lyrics = [subElement]
 
     # adjust for @accid.ges if present
     if elem.get('accid.ges') is not None:
@@ -1982,6 +2178,13 @@ def noteFromElement(elem, slurBundle=None):
     if elem.get('m21TupletNum') is not None:
         theNote = scaleToTuplet(theNote, elem)
 
+    # lyrics indicated with <verse>
+    if elem.find('./{}verse'.format(_MEINS)) is not None:
+        tempLyrics = []
+        for i, eachVerse in enumerate(elem.findall('./{}verse'.format(_MEINS))):
+            tempLyrics.extend(verseFromElement(eachVerse, backupN=i + 1))
+        theNote.lyrics = tempLyrics
+
     return theNote
 
 
@@ -2273,6 +2476,7 @@ def clefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
 
 
 def instrDefFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
+    # TODO: robuster handling of <instrDef>, including <instrGrp> and if held in a <staffGrp>
     '''
     <instrDef> (instrument definition)---MIDI instrument declaration.
 
@@ -2355,7 +2559,7 @@ def beamFromElement(elem, slurBundle=None):
 
     **Attributes/Elements Implemented:**
 
-    - <clef>, <chord>, <note>, <rest>, <space>, <tuplet>, <beam>
+    - <clef>, <chord>, <note>, <rest>, <space>, <tuplet>, <beam>, <barLine>
 
     **Attributes/Elements Ignored:**
 
@@ -2387,7 +2591,7 @@ def beamFromElement(elem, slurBundle=None):
     - MEI.critapp: app
     - MEI.edittrans: (all)
     - MEI.mensural: ligature mensur proport
-    - MEI.shared: barLine clefGrp custos keySig pad
+    - MEI.shared: clefGrp custos keySig pad
     '''
     # NB: The doctest is a sufficient integration test. Since there is no logic, I don't think we
     #     need to bother with unit testing.
@@ -2399,7 +2603,8 @@ def beamFromElement(elem, slurBundle=None):
                      '{http://www.music-encoding.org/ns/mei}rest': restFromElement,
                      '{http://www.music-encoding.org/ns/mei}tuplet': tupletFromElement,
                      '{http://www.music-encoding.org/ns/mei}beam': beamFromElement,
-                     '{http://www.music-encoding.org/ns/mei}space': spaceFromElement}
+                     '{http://www.music-encoding.org/ns/mei}space': spaceFromElement,
+                     '{http://www.music-encoding.org/ns/mei}barLine': barLineFromElement}
 
     beamedStuff = _processEmbeddedElements(elem.findall('*'), tagToFunction, elem.tag, slurBundle)
     beamedStuff = beamTogether(beamedStuff)
@@ -2407,6 +2612,58 @@ def beamFromElement(elem, slurBundle=None):
     return beamedStuff
 
 
+def barLineFromElement(elem, slurBundle=None):  # pylint: disable=unused-argument
+    '''
+    <barLine> Vertical line drawn through one or more staves that divides musical notation into
+    metrical units.
+
+    In MEI 2013: pg.262 (276 in PDF) (MEI.shared module)
+
+    :returns: A :class:`music21.bar.Barline` or :class:`~music21.bar.Repeat`, depending on the
+        value of @rend. If @rend is ``'rptboth'``, a 2-tuplet of :class:`Repeat` objects will be
+        returned, represented an "end" and "start" barline, as specified in the :mod:`music21.bar`
+        documentation.
+
+    .. note:: The music21-to-other converters expect that a :class:`Barline` will be attached to a
+        :class:`Measure`, which it will not be when imported from MEI as a <barLine> element.
+        However, this function does import correctly to a :class:`Barline` that you can access from
+        Python in the :class:`Stream` object as expected.
+
+    **Attributes/Elements Implemented:**
+
+    - @rend from att.barLine.log
+
+    **Attributes/Elements in Testing:** none
+
+    **Attributes not Implemented:**
+
+    - att.common (@label, @n, @xml:base) (att.id (@xml:id))
+    - att.facsimile (@facs)
+    - att.pointing (@xlink:actuate, @xlink:role, @xlink:show, @target, @targettype, @xlink:title)
+    - att.barLine.log
+
+        - (att.meterconformance.bar (@metcon, @control))
+
+    - att.barLine.vis
+
+        - (att.barplacement (@barplace, @taktplace))
+        - (att.color (@color))
+        - (att.measurement (@unit))
+        - (att.width (@width))
+
+    - att.barLine.ges (att.timestamp.musical (@tstamp))
+    - att.barLine.anl
+
+        - (att.common.anl
+
+            - (@copyof, @corresp, @next, @prev, @sameas, @synch)
+            - (att.alignment (@when)))
+
+    **Contained Elements not Implemented:** none
+    '''
+    return _barlineFromAttr(elem.get('rend', 'single'))
+
+
 def tupletFromElement(elem, slurBundle=None):
     '''
     <tuplet> A group of notes with "irregular" (sometimes called "irrational") rhythmic values,
@@ -2421,7 +2678,7 @@ def tupletFromElement(elem, slurBundle=None):
 
     **Attributes/Elements Implemented:**
 
-    - <tuplet>, <beam>, <note>, <rest>, <chord>, <clef>, <space>
+    - <tuplet>, <beam>, <note>, <rest>, <chord>, <clef>, <space>, <barLine>
     - @num and @numbase
 
     **Attributes/Elements in Testing:** none
@@ -2454,7 +2711,7 @@ def tupletFromElement(elem, slurBundle=None):
     - MEI.critapp: app
     - MEI.edittrans: (all)
     - MEI.mensural: ligature mensur proport
-    - MEI.shared: barLine clefGrp custos keySig pad
+    - MEI.shared: clefGrp custos keySig pad
     '''
     # mapping from tag name to our converter function
     tagToFunction = {'{http://www.music-encoding.org/ns/mei}tuplet': tupletFromElement,
@@ -2463,7 +2720,8 @@ def tupletFromElement(elem, slurBundle=None):
                      '{http://www.music-encoding.org/ns/mei}rest': restFromElement,
                      '{http://www.music-encoding.org/ns/mei}chord': chordFromElement,
                      '{http://www.music-encoding.org/ns/mei}clef': clefFromElement,
-                     '{http://www.music-encoding.org/ns/mei}space': spaceFromElement}
+                     '{http://www.music-encoding.org/ns/mei}space': spaceFromElement,
+                     '{http://www.music-encoding.org/ns/mei}barLine': barLineFromElement}
 
     # get the @num and @numbase attributes, without which we can't properly calculate the tuplet
     if elem.get('num') is None or elem.get('numbase') is None:
@@ -2501,7 +2759,6 @@ def tupletFromElement(elem, slurBundle=None):
 
 
 def layerFromElement(elem, overrideN=None, slurBundle=None):
-    # TODO: clefs that should appear part-way through a measure don't
     '''
     <layer> An independent stream of events on a staff.
 
@@ -2526,7 +2783,8 @@ def layerFromElement(elem, overrideN=None, slurBundle=None):
 
     **Attributes/Elements Implemented:**
 
-    - <clef>, <chord>, <note>, <rest>, <mRest>, <beam>, <tuplet>, <space>, <mSpace> contained within
+    - <clef>, <chord>, <note>, <rest>, <mRest>, <beam>, <tuplet>, <space>, <mSpace> , and
+      <barLine> contained within
     - @n, from att.common
 
     **Attributes Ignored:**
@@ -2571,7 +2829,8 @@ def layerFromElement(elem, overrideN=None, slurBundle=None):
                      '{http://www.music-encoding.org/ns/mei}beam': beamFromElement,
                      '{http://www.music-encoding.org/ns/mei}tuplet': tupletFromElement,
                      '{http://www.music-encoding.org/ns/mei}space': spaceFromElement,
-                     '{http://www.music-encoding.org/ns/mei}mSpace': mSpaceFromElement}
+                     '{http://www.music-encoding.org/ns/mei}mSpace': mSpaceFromElement,
+                     '{http://www.music-encoding.org/ns/mei}barLine': barLineFromElement}
 
     # iterate all immediate children
     theLayer = _processEmbeddedElements(elem.iterfind('*'), tagToFunction, elem.tag, slurBundle)
@@ -2843,8 +3102,8 @@ def measureFromElement(elem, backupNum, expectedNs, slurBundle=None, activeMeter
     # will only work in cases where not all of the parts are resting. However, it avoids a more
     # time-consuming search later.
     if (maxBarDuration == _DUR_ATTR_DICT[None] and
-        activeMeter is not None and
-        maxBarDuration != activeMeter.totalLength):
+            activeMeter is not None and
+            maxBarDuration != activeMeter.totalLength):
         # In this case, all the staves have <mRest> elements without a @dur.
         _correctMRestDurs(staves, activeMeter.totalLength)
     else:
@@ -2925,18 +3184,16 @@ def sectionScoreCore(elem, allPartNs, slurBundle, **kwargs):
     nextMeasureLeft = kwargs['nextMeasureLeft'] if 'nextMeasureLeft' in kwargs else None
     backupMeasureNum = kwargs['backupMeasureNum'] if 'backupMeasureNum' in kwargs else 0
 
+    # NOTE: "activeMeter" holds the TimeSignature object that's currently active; it's used in the
+    # loop below to help determine the proper duration of a full-measure rest. It must persist
+    # between <section> elements, so it's a parameter for this function.
+
     scoreTag = '{http://www.music-encoding.org/ns/mei}score'
     sectionTag = '{http://www.music-encoding.org/ns/mei}section'
     measureTag = '{http://www.music-encoding.org/ns/mei}measure'
     scoreDefTag = '{http://www.music-encoding.org/ns/mei}scoreDef'
     staffDefTag = '{http://www.music-encoding.org/ns/mei}staffDef'
 
-    # "activeMeter" holds the TimeSignature object that's currently active; it's used in the
-    # loop below to help determine the proper duration of a full-measure rest. It appears here
-    # so it persists between <section> elements, and so to collect the first TimeSignature from
-    # a <staffDef> or <scoreDef>.
-    # --> defined as a parameter above
-
     # hold the music21.stream.Part that we're building
     parsed = {n: [] for n in allPartNs}
     # hold things that belong in the following "Thing" (either Measure or Section)
@@ -2956,11 +3213,6 @@ def sectionScoreCore(elem, allPartNs, slurBundle, **kwargs):
                 for eachThing in inNextThing[eachN]:
                     measureResult[eachN].insert(0, eachThing)
                 inNextThing[eachN] = []
-                # if it's the first measure, pad for a possible anacrusis
-                # TODO: this may have to change when @n is better set
-                # TODO: this doesn't actually solve the "pick-up measure" problem
-                if 1 == backupMeasureNum:
-                    measureResult[eachN].padAsAnacrusis()
                 # if we got a left-side barline from the previous measure, use it
                 if nextMeasureLeft is not None:
                     measureResult[eachN].leftBarline = nextMeasureLeft
@@ -3032,7 +3284,7 @@ def sectionScoreCore(elem, allPartNs, slurBundle, **kwargs):
             environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachElem.tag, elem.tag))
 
     # TODO: write the <section @label=""> part
-    # TODO: check if there's anything left in "inNextMeasure"
+    # TODO: check if there's anything left in "inNextThing"
 
     return parsed, activeMeter, nextMeasureLeft, backupMeasureNum
 
@@ -3146,7 +3398,7 @@ def scoreFromElement(elem, slurBundle):
 
     # put slurs in the Score
     theScore.append(slurBundle.list)
-    # TODO: when all the Slur objects are are at the end, they'll only be outputted properly if the
+    # TODO: when all the Slur objects are at the end, they'll only be outputted properly if the
     #       whole Score is outputted. show()-ing one Part or Measure won't display the slurs.
 
     return theScore
@@ -3177,7 +3429,7 @@ _DOC_ORDER = [
     tupletFromElement,
     ]
 
-class Test(unittest.TestCase):
+class Test(unittest.TestCase):  # pylint: disable=too-many-public-methods
     '''
     This class runs the tests from all the TestCase sub-classes in the "test_base" module.
 
@@ -3208,26 +3460,29 @@ if __name__ == "__main__":
     import music21
     from music21.mei import test_base
     music21.mainTest(
-                     test_base.TestMeiToM21Class,
-                     test_base.TestThings,
-                     test_base.TestAttrTranslators,
-                     test_base.TestNoteFromElement,
-                     test_base.TestRestFromElement,
-                     test_base.TestChordFromElement,
-                     test_base.TestClefFromElement,
-                     test_base.TestLayerFromElement,
-                     test_base.TestStaffFromElement,
-                     test_base.TestStaffDefFromElement,
-                     test_base.TestScoreDefFromElement,
-                     test_base.TestEmbeddedElements,
-                     test_base.TestAddSlurs,
-                     test_base.TestBeams,
-                     test_base.TestPreprocessors,
-                     test_base.TestTuplets,
-                     test_base.TestInstrDef,
-                     test_base.TestMeasureFromElement,
-                     test_base.TestSectionScore,
-                    )
+        test_base.TestMeiToM21Class,
+        test_base.TestThings,
+        test_base.TestMetadata,
+        test_base.TestAttrTranslators,
+        test_base.TestLyrics,
+        test_base.TestNoteFromElement,
+        test_base.TestRestFromElement,
+        test_base.TestChordFromElement,
+        test_base.TestClefFromElement,
+        test_base.TestLayerFromElement,
+        test_base.TestStaffFromElement,
+        test_base.TestStaffDefFromElement,
+        test_base.TestScoreDefFromElement,
+        test_base.TestEmbeddedElements,
+        test_base.TestAddSlurs,
+        test_base.TestBeams,
+        test_base.TestPreprocessors,
+        test_base.TestTuplets,
+        test_base.TestInstrDef,
+        test_base.TestMeasureFromElement,
+        test_base.TestSectionScore,
+        test_base.TestBarLineFromElement,
+    )
 
 #------------------------------------------------------------------------------
 # eof


Test patch:
diff --git a/music21/mei/test/test_file.mei b/music21/mei/test/test_file.mei
index 3afe32c2a..632f0d002 100644
--- a/music21/mei/test/test_file.mei
+++ b/music21/mei/test/test_file.mei
@@ -11,7 +11,12 @@
                         <persName role="encoder">Christopher Antila</persName>
                     </respStmt>
                 </titleStmt>
-                <tempo>Movement title</tempo>
+                <tempo>Allegro moderato</tempo>
+                <history>
+                    <creation>
+                        <date isodate="2014-05-20"/>
+                    </creation>
+                </history>
             </work>
         </fileDesc>
     </meiHead>
diff --git a/music21/mei/test_base.py b/music21/mei/test_base.py
index 06404d786..76214441f 100644
--- a/music21/mei/test_base.py
+++ b/music21/mei/test_base.py
@@ -27,17 +27,15 @@ Tests for :mod:`music21.mei.base`.
 # pylint is bad at guessing types in these tests---reasonably so
 # pylint: disable=maybe-no-member
 
+from music21.ext import six
+from six.moves import xrange  # pylint: disable=redefined-builtin,import-error,unused-import
+from six.moves import range  # pylint: disable=redefined-builtin,import-error,unused-import
+
 import unittest
-try:
-    # this works in Python 3.3+
+if six.PY3:
     from unittest import mock  # pylint: disable=no-name-in-module
-except ImportError:
-    try:
-        # system library overrides the built-in
-        import mock
-    except ImportError:
-        # last resort
-        from music21.ext import mock
+else:
+    from music21.ext import mock
 
 # To have working MagicMock objects, we can't use cElementTree even though it would be faster.
 # The C implementation provides some methods/attributes dynamically (notably "tag"), so MagicMock
@@ -54,6 +52,7 @@ from music21 import duration
 from music21 import instrument
 from music21 import interval
 from music21 import key
+from music21 import metadata
 from music21 import meter
 from music21 import note
 from music21 import pitch
@@ -112,6 +111,37 @@ class TestMeiToM21Class(unittest.TestCase):
         except base.MeiElementError as theError:
             self.assertEqual(base._WRONG_ROOT_ELEMENT.format('score-partwise'), theError.args[0])
 
+    @mock.patch('music21.mei.base._ppSlurs')
+    @mock.patch('music21.mei.base._ppTies')
+    @mock.patch('music21.mei.base._ppBeams')
+    @mock.patch('music21.mei.base._ppTuplets')
+    @mock.patch('music21.mei.base._ppConclude')
+    @mock.patch('music21.mei.base.scoreFromElement')
+    @mock.patch('music21.mei.base.makeMetadata')
+    def testRun1(self, mockMeta, mockScoreFE, mockConclude, mockTuplet, mockBeams, mockTies, mockSlurs):
+        '''
+        MeiToM21Converter.run(): that it works
+        '''
+        # ha... "test run"... get it?
+        testConv = base.MeiToM21Converter()
+        testConv.documentRoot = mock.MagicMock(spec_set=ETree.Element)
+        testConv.documentRoot.find.return_value = 5
+        expScore = mock.MagicMock(spec_set=stream.Stream)
+        mockScoreFE.return_value = expScore
+        expDocRootQuery = './/{mei}music//{mei}score'.format(mei=_MEINS)
+
+        actual = testConv.run()
+
+        self.assertIs(expScore, actual)
+        mockTuplet.assert_called_once_with(testConv)
+        mockBeams.assert_called_once_with(testConv)
+        mockTies.assert_called_once_with(testConv)
+        mockSlurs.assert_called_once_with(testConv)
+        mockConclude.assert_called_once_with(testConv)
+        testConv.documentRoot.find.assert_called_once_with(expDocRootQuery)
+        mockScoreFE.assert_called_once_with(5, testConv.slurBundle)
+        mockMeta.assert_called_once_with(testConv.documentRoot)
+
 
 #------------------------------------------------------------------------------
 class TestThings(unittest.TestCase):
@@ -322,6 +352,285 @@ class TestThings(unittest.TestCase):
         self.assertRaises(RuntimeError, base.getVoiceId, fromThese)
 
 
+#------------------------------------------------------------------------------
+class TestMetadata(unittest.TestCase):
+    '''Tests for the metadata-fetching functions.'''
+
+    @mock.patch('music21.mei.base.metaSetTitle')
+    @mock.patch('music21.mei.base.metaSetComposer')
+    @mock.patch('music21.mei.base.metaSetDate')
+    def testMakeMeta1(self, mockDate, mockComposer, mockTitle):
+        '''
+        makeMetadata() when there is no <work> element.
+        '''
+        documentRoot = mock.MagicMock(spec_set=ETree.Element)
+        documentRoot.find.return_value = None
+
+        actual = base.makeMetadata(documentRoot)
+
+        self.assertIsInstance(actual, metadata.Metadata)
+        documentRoot.find.assert_called_once_with('.//{}work'.format(_MEINS))
+        self.assertEqual(0, mockDate.call_count)
+        self.assertEqual(0, mockComposer.call_count)
+        self.assertEqual(0, mockTitle.call_count)
+
+    @mock.patch('music21.mei.base.metaSetTitle')
+    @mock.patch('music21.mei.base.metaSetComposer')
+    @mock.patch('music21.mei.base.metaSetDate')
+    def testMakeMeta2(self, mockDate, mockComposer, mockTitle):
+        '''
+        makeMetadata() when there is a <work> element.
+        '''
+        documentRoot = mock.MagicMock(spec_set=ETree.Element)
+        mockWork = mock.MagicMock(spec_set=ETree.Element)
+        documentRoot.find.return_value = mockWork
+        mockDate.side_effect = lambda x, y: y
+        mockComposer.side_effect = lambda x, y: y
+        mockTitle.side_effect = lambda x, y: y
+
+        actual = base.makeMetadata(documentRoot)
+
+        self.assertIsInstance(actual, metadata.Metadata)
+        documentRoot.find.assert_called_once_with('.//{}work'.format(_MEINS))
+        mockDate.assert_called_once_with(mockWork, actual)
+        mockComposer.assert_called_once_with(mockWork, actual)
+        mockTitle.assert_called_once_with(mockWork, actual)
+
+    def testMetaTitle1(self):
+        '''
+        metaSetTitle() with a title and tempo but no subtitle
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <titleStmt>
+                <title>Symphony No. 7</title>
+            </titleStmt>
+            <tempo>Adagio</tempo>
+        </work>"""
+        work = ETree.fromstring(work)
+        expTitle = 'Symphony No. 7'
+        expMovementName = 'Adagio'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetTitle(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertFalse(hasattr(meta, 'subtitle'))
+        self.assertEqual(expTitle, actual.title)
+        self.assertEqual(expMovementName, actual.movementName)
+
+    def testMetaTitle2(self):
+        '''
+        metaSetTitle() with a title, subtitle, but no tempo
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <titleStmt>
+                <title>Symphony No. 7</title>
+                <title type="subtitle">in one movement</title>
+            </titleStmt>
+        </work>"""
+        work = ETree.fromstring(work)
+        expTitle = 'Symphony No. 7 (in one movement)'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetTitle(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertFalse(hasattr(meta, 'subtitle'))
+        self.assertEqual(expTitle, actual.title)
+        self.assertIsNone(actual.movementName)
+
+    def testMetaComposer1(self):
+        '''
+        metaSetComposer() with no composers
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei"/>"""
+        work = ETree.fromstring(work)
+        meta = metadata.Metadata()
+
+        actual = base.metaSetComposer(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertIsNone(actual.composer)
+
+    def testMetaComposer2(self):
+        '''
+        metaSetComposer() with one composer in <respStmt>
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <titleStmt>
+                <respStmt>
+                    <persName role="composer">Jean Sibelius</persName>
+                </respStmt>
+            </titleStmt>
+        </work>"""
+        work = ETree.fromstring(work)
+        expComposer = 'Jean Sibelius'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetComposer(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expComposer, actual.composer)
+
+    def testMetaComposer3(self):
+        '''
+        metaSetComposer() with one composer in <composer>
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <titleStmt>
+                <composer>Jean Sibelius</composer>
+            </titleStmt>
+        </work>"""
+        work = ETree.fromstring(work)
+        expComposer = 'Jean Sibelius'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetComposer(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expComposer, actual.composer)
+
+    def testMetaComposer4(self):
+        '''
+        metaSetComposer() with two composers, one specified each way
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <titleStmt>
+                <respStmt>
+                    <persName role="composer">Jean Sibelius</persName>
+                </respStmt>
+                <composer>Sibelius, Jean</composer>
+            </titleStmt>
+        </work>"""
+        work = ETree.fromstring(work)
+        expComposer1 = "['Jean Sibelius', 'Sibelius, Jean']"
+        expComposer2 = "['Sibelius, Jean', 'Jean Sibelius']"
+        meta = metadata.Metadata()
+
+        actual = base.metaSetComposer(work, meta)
+
+        self.assertIs(meta, actual)
+        if expComposer1 != actual.composer and expComposer2 != actual.composer:
+            self.fail('composer names do not match in either order')
+
+    def testMetaDate1(self):
+        '''
+        metaSetDate() with no dates
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei"/>"""
+        work = ETree.fromstring(work)
+        expDate = 'None'  # I don't know why, but that's what it does
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+
+    def testMetaDate2(self):
+        '''
+        metaSetDate() with @isodate
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <history>
+                <creation>
+                    <date isodate="1924-03-02"/>
+                </creation>
+            </history>
+        </work>"""
+        work = ETree.fromstring(work)
+        expDate = '1924/03/02'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+
+    def testMetaDate3(self):
+        '''
+        metaSetDate() with text
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <history>
+                <creation>
+                    <date>1924-03-02</date>
+                </creation>
+            </history>
+        </work>"""
+        work = ETree.fromstring(work)
+        expDate = '1924/03/02'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+
+    @mock.patch('music21.mei.base.environLocal')
+    def testMetaDate4(self, mockEnviron):
+        '''
+        metaSetDate() with text that fails
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <history>
+                <creation>
+                    <date>2 March 1924</date>
+                </creation>
+            </history>
+        </work>"""
+        work = ETree.fromstring(work)
+        expDate = 'None'
+        expWarn = base._MISSED_DATE.format('2 March 1924')
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+        mockEnviron.warn.assert_called_once_with(expWarn)
+
+    def testMetaDate5(self):
+        '''
+        metaSetDate() with @notbefore and @notafter
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <history>
+                <creation>
+                    <date notbefore="1915" notafter="1924"/>
+                </creation>
+            </history>
+        </work>"""
+        work = ETree.fromstring(work)
+        expDate = '1915/--/-- to 1924/--/--'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+
+    def testMetaDate6(self):
+        '''
+        metaSetDate() with @startdate and @enddate
+        '''
+        work = """<work xmlns="http://www.music-encoding.org/ns/mei">
+            <history>
+                <creation>
+                    <date startdate="1915" enddate="1924"/>
+                </creation>
+            </history>
+        </work>"""
+        work = ETree.fromstring(work)
+        expDate = '1915/--/-- to 1924/--/--'
+        meta = metadata.Metadata()
+
+        actual = base.metaSetDate(work, meta)
+
+        self.assertIs(meta, actual)
+        self.assertEqual(expDate, actual.date)
+
+
 #------------------------------------------------------------------------------
 class TestAttrTranslators(unittest.TestCase):
     '''Tests for the one-to-one (string-to-simple-datatype) converter functions.'''
@@ -539,6 +848,146 @@ class TestAttrTranslators(unittest.TestCase):
         self.assertEqual(expected.type, expected.type)
 
 
+#------------------------------------------------------------------------------
+class TestLyrics(unittest.TestCase):
+    '''Tests for sylFromElement() and verseFromElement()'''
+
+    def testSyl1(self):
+        '''
+        sylFromElement() where @con is given and @wordpos="i"
+        '''
+        elem = ETree.Element('syl', attrib={'wordpos': 'i', 'con': 's'})
+        elem.text = 'Chri'
+
+        actual = base.sylFromElement(elem)
+
+        self.assertIsInstance(actual, note.Lyric)
+        self.assertEqual('begin', actual.syllabic)
+        self.assertEqual('Chri ', actual.text)
+
+    def testSyl2(self):
+        '''
+        sylFromElement() where @con is given and @wordpos="m"
+        '''
+        elem = ETree.Element('syl', attrib={'wordpos': 'm', 'con': 't'})
+        elem.text = 'sto'
+
+        actual = base.sylFromElement(elem)
+
+        self.assertIsInstance(actual, note.Lyric)
+        self.assertEqual('middle', actual.syllabic)
+        self.assertEqual('~sto~', actual.text)
+
+    def testSyl3(self):
+        '''
+        sylFromElement() where @con is not given and @wordpos="t"
+        '''
+        elem = ETree.Element('syl', attrib={'wordpos': 't', 'con': 'd'})
+        elem.text = 'pher'
+
+        actual = base.sylFromElement(elem)
+
+        self.assertIsInstance(actual, note.Lyric)
+        self.assertEqual('end', actual.syllabic)
+        self.assertEqual('-pher', actual.text)
+
+    def testSyl4(self):
+        '''
+        sylFromElement() where @wordpos is not specified
+        '''
+        elem = ETree.Element('syl')
+        elem.text = 'shoe'
+
+        actual = base.sylFromElement(elem)
+
+        self.assertIsInstance(actual, note.Lyric)
+        self.assertEqual('single', actual.syllabic)
+        self.assertEqual('shoe', actual.text)
+
+    def testVerse1(self):
+        '''
+        verseFromElement() with one <syl> and @n given
+        '''
+        elem = ETree.Element('verse', attrib={'n': '42'})
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = 'Hin-'
+        elem.append(syl)
+
+        actual = base.verseFromElement(elem, backupN=5)
+
+        self.assertEqual(1, len(actual))
+        self.assertIsInstance(actual[0], note.Lyric)
+        self.assertEqual('begin', actual[0].syllabic)
+        self.assertEqual('Hin', actual[0].text)
+        self.assertEqual(42, actual[0].number)
+
+    def testVerse2(self):
+        '''
+        verseFromElement() with three <syl> and @n not given
+        '''
+        elem = ETree.Element('verse')
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = 'Hin-'
+        elem.append(syl)
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = '-de-'
+        elem.append(syl)
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = '-mith'
+        elem.append(syl)
+
+        actual = base.verseFromElement(elem, backupN=5)
+
+        self.assertEqual(3, len(actual))
+        for eachSyl in actual:
+            self.assertIsInstance(eachSyl, note.Lyric)
+            self.assertEqual(5, eachSyl.number)
+        self.assertEqual('begin', actual[0].syllabic)
+        self.assertEqual('Hin', actual[0].text)
+        self.assertEqual('middle', actual[1].syllabic)
+        self.assertEqual('de', actual[1].text)
+        self.assertEqual('end', actual[2].syllabic)
+        self.assertEqual('mith', actual[2].text)
+
+    @mock.patch('music21.mei.base.environLocal')
+    def testVerse3(self, mockEnviron):
+        '''
+        verseFromElement() with one <syl> and invalid @n
+        '''
+        elem = ETree.Element('verse', attrib={'n': 'mistake'})
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = 'Hin-'
+        elem.append(syl)
+
+        actual = base.verseFromElement(elem)
+
+        self.assertEqual(1, len(actual))
+        self.assertIsInstance(actual[0], note.Lyric)
+        self.assertEqual('begin', actual[0].syllabic)
+        self.assertEqual('Hin', actual[0].text)
+        self.assertEqual(1, actual[0].number)
+        mockEnviron.warn.assert_called_once_with(base._BAD_VERSE_NUMBER.format('mistake'))
+
+    @mock.patch('music21.mei.base.environLocal')
+    def testVerse4(self, mockEnviron):
+        '''
+        verseFromElement() with one <syl> and no @n
+        '''
+        elem = ETree.Element('verse')
+        syl = ETree.Element('{}syl'.format(_MEINS))
+        syl.text = 'Hin-'
+        elem.append(syl)
+
+        actual = base.verseFromElement(elem)
+
+        self.assertEqual(1, len(actual))
+        self.assertIsInstance(actual[0], note.Lyric)
+        self.assertEqual('begin', actual[0].syllabic)
+        self.assertEqual('Hin', actual[0].text)
+        self.assertEqual(1, actual[0].number)
+        mockEnviron.warn.assert_called_once_with(base._BAD_VERSE_NUMBER.format('None'))
+
+
 #------------------------------------------------------------------------------
 class TestNoteFromElement(unittest.TestCase):
     '''Tests for noteFromElement()'''
@@ -784,17 +1233,20 @@ class TestNoteFromElement(unittest.TestCase):
     @mock.patch('music21.duration.GraceDuration')
     def testUnit5(self, mockGrace, mockMakeDuration, mockSafePitch, mockProcEmbEl, mockNote):
         '''
-        noteFromElement(): test @grace and @m21Beam where the duration requires adjusting beams
+        noteFromElement(): test @grace and @m21Beam where the duration requires adjusting beams,
+            and contained <syl>
 
         (mostly-unit test)
         '''
         elem = ETree.Element('note', attrib={'pname': 'D', 'oct': '2', 'dur': '16',
                                              'm21Beam': 'start', 'grace': 'acc'})
+        sylElem = ETree.Element('{}syl'.format(_MEINS))
+        elem.append(sylElem)
         mockSafePitch.return_value = 'safePitch() return'
         mockNewNote = mock.MagicMock()
         mockNewNote.beams = mock.MagicMock()
         mockNote.return_value = mockNewNote
-        mockProcEmbEl.return_value = []
+        mockProcEmbEl.return_value = [mock.MagicMock(spec_set=note.Lyric)]
         mockGrace.return_value = mock.MagicMock(spec_set=duration.Duration)
         mockGrace.return_value.type = '16th'
         expected = mockNewNote
@@ -807,14 +1259,19 @@ class TestNoteFromElement(unittest.TestCase):
         mockNote.assert_called_once_with(mockSafePitch.return_value)
         mockNewNote.beams.fill.assert_called_once_with('16th', 'start')
         self.assertEqual(mockGrace.return_value, mockNewNote.duration)
+        self.assertEqual(mockProcEmbEl.return_value, mockNewNote.lyrics)
 
     def testIntegration5(self):
         '''
-        noteFromElement(): test @grace and @m21Beam where the duration requires adjusting beams
+        noteFromElement(): test @grace and @m21Beam where the duration requires adjusting beams,
+            and contained <syl>
         (corresponds to testUnit5() with no mocks)
         '''
         elem = ETree.Element('note', attrib={'pname': 'D', 'oct': '2', 'dur': '16',
                                              'm21Beam': 'start', 'grace': 'acc'})
+        sylElem = ETree.Element('{}syl'.format(_MEINS))
+        sylElem.text = 'words!'
+        elem.append(sylElem)
         slurBundle = spanner.SpannerBundle()
 
         actual = base.noteFromElement(elem, slurBundle)
@@ -822,10 +1279,86 @@ class TestNoteFromElement(unittest.TestCase):
         self.assertEqual('D2', actual.nameWithOctave)
         self.assertEqual(0.0, actual.quarterLength)
         self.assertEqual('16th', actual.duration.type)
-        self.assertEqual(1, actual.beams.beamsList[0].number)
-        self.assertEqual('start', actual.beams.beamsList[0].type)
-        self.assertEqual(2, actual.beams.beamsList[1].number)
-        self.assertEqual('start', actual.beams.beamsList[1].type)
+        self.assertTrue(1, actual.beams.beamsList[0].number)
+        self.assertTrue('start', actual.beams.beamsList[0].type)
+        self.assertTrue(2, actual.beams.beamsList[1].number)
+        self.assertTrue('start', actual.beams.beamsList[1].type)
+        self.assertTrue(1, len(actual.lyrics))
+        self.assertTrue('words!', actual.lyrics[0].text)
+
+    @mock.patch('music21.note.Note')
+    @mock.patch('music21.mei.base._processEmbeddedElements')
+    @mock.patch('music21.mei.base.safePitch')
+    @mock.patch('music21.mei.base.makeDuration')
+    @mock.patch('music21.mei.base.verseFromElement')
+    def testUnit6(self, mockVerseFE, mockMakeDuration, mockSafePitch, mockProcEmbEl, mockNote):
+        '''
+        noteFromElement(): test contained <verse>
+
+        (mostly-unit test)
+        '''
+        elem = """<note pname="D" oct="2" dur="16" xmlns="http://www.music-encoding.org/ns/mei">
+            <verse>
+                <syl>au</syl>
+                <syl>luong</syl>
+            </verse>
+            <verse>
+                <syl>sun</syl>
+            </verse>
+        </note>
+        """
+        elem = ETree.fromstring(elem)
+        mockSafePitch.return_value = 'safePitch() return'
+        mockNewNote = mock.MagicMock()
+        mockNewNote.beams = mock.MagicMock()
+        mockNote.return_value = mockNewNote
+        mockProcEmbEl.return_value = []
+        expected = mockNewNote
+        # verseFromElement() return values
+        vfeReturns = [[mock.MagicMock(name='au'), mock.MagicMock(name='luong')],
+                      [mock.MagicMock(name='sun')]]
+        def mockVerseFESideEffect(elem, backupN):
+            "this way we can check it gets called with the right elements"
+            assert '{}verse'.format(_MEINS) == elem.tag
+            return vfeReturns.pop(0)
+        mockVerseFE.side_effect = mockVerseFESideEffect
+        expLyrics = [vfeReturns[0][0], vfeReturns[0][1], vfeReturns[1][0]]
+
+        actual = base.noteFromElement(elem, 'slur bundle')
+
+        self.assertEqual(expected, actual)
+        self.assertEqual(expLyrics, actual.lyrics)
+        self.assertEqual(2, mockVerseFE.call_count)
+        mockVerseFE.assert_any_call(mock.ANY, backupN=1)
+        mockVerseFE.assert_any_call(mock.ANY, backupN=2)
+
+    def testIntegration6(self):
+        '''
+        noteFromElement(): test contained <verse>
+        (corresponds to testUnit6() with no mocks)
+        '''
+        elem = """<note pname="D" oct="2" dur="16" xmlns="http://www.music-encoding.org/ns/mei">
+            <verse>
+                <syl>au</syl>
+                <syl>luong</syl>
+            </verse>
+            <verse>
+                <syl>sun</syl>
+            </verse>
+        </note>
+        """
+        elem = ETree.fromstring(elem)
+        slurBundle = spanner.SpannerBundle()
+
+        actual = base.noteFromElement(elem, slurBundle)
+
+        self.assertEqual(3, len(actual.lyrics))
+        self.assertEqual('au', actual.lyrics[0].text)
+        self.assertEqual('luong', actual.lyrics[1].text)
+        self.assertEqual('sun', actual.lyrics[2].text)
+        self.assertEqual(1, actual.lyrics[0].number)
+        self.assertEqual(1, actual.lyrics[1].number)
+        self.assertEqual(2, actual.lyrics[2].number)
 
     # NOTE: consider adding to previous tests rather than making new ones
 
@@ -1877,10 +2410,10 @@ class TestStaffDefFromElement(unittest.TestCase):
         # NB: differences from testUnit2() are marked with a "D2" comment at the end of the line
         # 1.) prepare
         elem = ETree.Element('{}staffDef'.format(_MEINS),
-                        attrib={'clef.shape': 'F', 'clef.line': '4', 'clef.dis': 'cd',
-                                'clef.dis.place': 'cdp', 'label': 'the label',
-                                'label.abbr': 'the l.', 'n': '1', 'meter.count': '1',
-                                'key.pname': 'G', 'trans.semi': '123'})
+                             attrib={'clef.shape': 'F', 'clef.line': '4', 'clef.dis': 'cd',
+                                     'clef.dis.place': 'cdp', 'label': 'the label',
+                                     'label.abbr': 'the l.', 'n': '1', 'meter.count': '1',
+                                     'key.pname': 'G', 'trans.semi': '123'})
         theMockInstrument = mock.MagicMock('mock instrument')
         mockFromString.side_effect = instrument.InstrumentException  # D2
         mockInstrInit.return_value = theMockInstrument  # D1 & D2
@@ -1986,7 +2519,7 @@ class TestStaffDefFromElement(unittest.TestCase):
     @mock.patch('music21.mei.base.staffDefFromElement')
     def testStaffGrpUnit1(self, mockStaffDefFE):
         '''
-        staffGrpFromElement(): it's not a complicated function!
+        staffGrpFromElement(): it's not a very complicated function!
         '''
         elem = ETree.Element('staffGrp')
         innerElems = [ETree.Element('{}staffDef'.format(_MEINS), attrib={'n': str(n)}) 
@@ -1996,7 +2529,7 @@ class TestStaffDefFromElement(unittest.TestCase):
         mockStaffDefFE.side_effect = lambda x, y: 'processed {}'.format(x.get('n'))
         expected = {str(n): 'processed {}'.format(n) for n in range(4)}
 
-        actual = base.staffGrpFromElement(elem, None)
+        actual = base.staffGrpFromElement(elem, None, {})
 
         self.assertEqual(expected, actual)
         self.assertEqual(len(innerElems), mockStaffDefFE.call_count)
@@ -2005,7 +2538,7 @@ class TestStaffDefFromElement(unittest.TestCase):
 
     def testStaffGrpInt1(self):
         '''
-        staffGrpFromElement(): we'll use
+        staffGrpFromElement(): with <staffDef> directly inside this <staffGrp>
         '''
         elem = ETree.Element('staffGrp')
         innerElems = [ETree.Element('{}staffDef'.format(_MEINS),
@@ -2023,6 +2556,28 @@ class TestStaffDefFromElement(unittest.TestCase):
 
         self.assertDictEqual(expected, actual)
 
+    def testStaffGrpInt2(self):
+        '''
+        staffGrpFromElement(): with <staffDef> embedded in another <staffGrp>
+        '''
+        elem = ETree.Element('staffGrp')
+        innerElems = [ETree.Element('{}staffDef'.format(_MEINS),
+                                    attrib={'n': str(n + 1), 'key.mode': 'major',
+                                            'key.sig': '{}f'.format(n + 1)})
+                      for n in range(4)]
+        innerGrp = ETree.Element('{}staffGrp'.format(_MEINS))
+        for eachElem in innerElems:
+            innerGrp.append(eachElem)
+        elem.append(innerGrp)
+        expected = {'1': {'key': key.KeySignature(sharps=-1, mode='major')},
+                    '2': {'key': key.KeySignature(sharps=-2, mode='major')},
+                    '3': {'key': key.KeySignature(sharps=-3, mode='major')},
+                    '4': {'key': key.KeySignature(sharps=-4, mode='major')}}
+
+        actual = base.staffGrpFromElement(elem, None)
+
+        self.assertDictEqual(expected, actual)
+
 
 
 #------------------------------------------------------------------------------
@@ -2089,8 +2644,7 @@ class TestScoreDefFromElement(unittest.TestCase):
         elem.append(staffGrp)
         mockTime.return_value = 'mockTime return'
         mockKey.return_value = 'mockKey return'
-        mockStaffGrpFE.return_value = {'1': {'instrument': 'A clarinet'}}  
-                    # but is it "any clarinet," or a specific clarinet in A? Ambiguity!
+        mockStaffGrpFE.return_value = {'1': {'instrument': 'A clarinet'}}
         expected = {'all-part objects': [mockTime.return_value, mockKey.return_value],
                     'whole-score objects': [],
                     '1': {'instrument': 'A clarinet'}}
@@ -2177,13 +2731,13 @@ class TestEmbeddedElements(unittest.TestCase):
         mapping = {'note': mockTranslator}
         callerName = 'ocean'
         expected = ['translator return']
-        exp_err = base._UNPROCESSED_SUBELEMENT.format(elements[1].tag, callerName)
+        expErr = base._UNPROCESSED_SUBELEMENT.format(elements[1].tag, callerName)
 
         actual = base._processEmbeddedElements(elements, mapping, callerName)
 
         self.assertSequenceEqual(expected, actual)
         mockTranslator.assert_called_once_with(elements[0], None)
-        mockEnviron.printDebug.assert_called_once_with(exp_err)
+        mockEnviron.printDebug.assert_called_once_with(expErr)
 
 
 
@@ -3432,7 +3986,7 @@ class TestMeasureFromElement(unittest.TestCase):
         mRestTag = '{}mRest'.format(_MEINS)
         elem = ETree.Element('measure', attrib={'right': 'rptboth'})
         innerStaffs = [ETree.Element(staffTag, attrib={'n': str(n + 1)}) for n in range(3)]
-        for i, eachStaff in enumerate(innerStaffs):
+        for eachStaff in innerStaffs:
             thisLayer = ETree.Element(layerTag, attrib={'n': '1'})
             thisLayer.append(ETree.Element(mRestTag))
             eachStaff.append(thisLayer)
@@ -3871,16 +4425,542 @@ class TestSectionScore(unittest.TestCase):
         # ensure "parsed" is the right format
         self.assertEqual(1, len(parsed))
         self.assertTrue('1' in parsed)
-        self.assertEqual(1, len(parsed['1']))  # there is one <section>
-        self.assertEqual(1, len(parsed['1'][0]))  # there is one Measure
+        self.assertEqual(1, len(parsed['1']))  # one <measure> from one <section>
         meas = parsed['1'][0][0]
         self.assertIsInstance(meas, stream.Measure)
         self.assertEqual(1, meas.number)
         self.assertEqual(3, len(meas))  # a Voice, a Clef, a TimeSignature
-        self.assertIsInstance(meas[2], stream.Voice)  # check out the Voice and its Note
-        self.assertEqual(1, len(meas[2]))
-        self.assertIsInstance(meas[2][0], note.Note)
-        self.assertEqual('G4', meas[2][0].nameWithOctave)
-        self.assertIsInstance(meas[0], clef.TrebleClef)  # check out the Clef
-        self.assertIsInstance(meas[1], meter.TimeSignature)  # check out the TimeSignature
-        self.assertEqual('8/8', meas[1].ratioString)
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        clefIndex = 0
+        timeSigIndex = 1
+        voiceIndex = 2
+        self.assertIsInstance(meas[voiceIndex], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[voiceIndex]))
+        self.assertIsInstance(meas[voiceIndex][0], note.Note)
+        self.assertEqual('G4', meas[voiceIndex][0].nameWithOctave)
+        self.assertIsInstance(meas[clefIndex], clef.TrebleClef)  # check out the Clef
+        self.assertIsInstance(meas[timeSigIndex], meter.TimeSignature)  # check out the TimeSignature
+        self.assertEqual('8/8', meas[timeSigIndex].ratioString)
+
+    @mock.patch('music21.mei.base.measureFromElement')
+    @mock.patch('music21.mei.base.sectionFromElement')
+    @mock.patch('music21.mei.base.scoreDefFromElement')
+    @mock.patch('music21.mei.base.staffDefFromElement')
+    def testCoreUnit2(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE):
+        '''
+        sectionScoreCore(): everything basic, as called by sectionFromElement()
+            - no kwargs
+                - but the <measure> elements do have @n so those values should be used
+            - one of most things (<section>, <scoreDef>, and <staffDef>)
+            - two of <measure> (one in a <section>)
+            - things in a <section> are appended properly (different for <score> and <section>)
+
+        This test is roughly equivalent to testCoreUnit1() but with a <section> instead of a <score>.
+        Note that, because the <measure> *should* be processed here, this test is indeed more
+        complicated.
+
+        mocked:
+            - measureFromElement()
+            - sectionFromElement()
+            - scoreDefFromElement()
+            - staffDefFromElement()
+        '''
+        # setup the arguments
+        # NB: there's more MEI here than we need, but it's shared between unit & integration tests
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <scoreDef meter.count="8" meter.unit="8"/>
+            <staffDef n="1" clef.shape="G" clef.line="2"/>
+            <measure n="400">
+                <staff n="1">
+                    <layer n="1">
+                        <note pname="E" oct="7" dur="1"/>
+                    </layer>
+                </staff>
+            </measure>
+            <section>
+                <measure n="92">
+                    <staff n="1">
+                        <layer n="1">
+                            <note pname="G" oct="4" dur="1"/>
+                        </layer>
+                    </staff>
+                </measure>
+            </section>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = mock.MagicMock()
+        allPartNs = ['1']
+        # setup measureFromElement()
+        expMeas1 = mock.MagicMock(spec_set=stream.Stream)
+        mockMeasureFE.return_value = {'1': expMeas1}
+        # setup sectionFromElement()
+        expActiveMeter = mock.MagicMock(spec_set=meter.TimeSignature)
+        expMeasureNum = 1
+        expNMLeft = 'barline for the next Meausre'
+        expPart1 = [mock.MagicMock(spec_set=stream.Stream)]
+        mockSectionFE.return_value = ({'1': expPart1},
+                                      expActiveMeter, expNMLeft, expMeasureNum)
+        # setup scoreDefFromElement()
+        scoreDefActiveMeter = mock.MagicMock(spec_set=meter.TimeSignature)
+        mockScoreDFE.return_value = {'all-part objects': [scoreDefActiveMeter],
+                                     '1': {'whatever': '8/8'}}
+        # setup staffDefFromElement()
+        mockStaffDFE.return_value = {'whatever': 'treble clef'}
+        # prepare the "expected" return
+        expected = {'1': [expMeas1, expPart1[0]]}  # must be [0] b/c expPart1 would be list of Measure
+        expected = (expected, expActiveMeter, expNMLeft, expMeasureNum)
+
+        actual = base.sectionScoreCore(elem, allPartNs, slurBundle)
+
+        # ensure expected == actual
+        self.assertEqual(expected, actual)
+        # ensure measureFromElement()
+        mockMeasureFE.assert_called_once_with(mock.ANY, 1, allPartNs,
+                                              slurBundle=slurBundle,
+                                              activeMeter=scoreDefActiveMeter)
+        # ensure sectionFromElement()
+        mockSectionFE.assert_called_once_with(mock.ANY,
+                                              allPartNs,
+                                              activeMeter=scoreDefActiveMeter,
+                                              nextMeasureLeft=None,
+                                              backupMeasureNum=1,  # incremented automatically on finding a <measure>
+                                              slurBundle=slurBundle)
+        self.assertEqual('{}section'.format(_MEINS), mockSectionFE.call_args_list[0][0][0].tag)
+        # ensure scoreDefFromElement()
+        mockScoreDFE.assert_called_once_with(mock.ANY, slurBundle)
+        self.assertEqual('{}scoreDef'.format(_MEINS), mockScoreDFE.call_args_list[0][0][0].tag)
+        # ensure staffDefFromElement()
+        mockStaffDFE.assert_called_once_with(mock.ANY, slurBundle)
+        self.assertEqual('{}staffDef'.format(_MEINS), mockStaffDFE.call_args_list[0][0][0].tag)
+        # ensure the "inNextThing" numbers and mock.TimeSignature were put into the mocked Measure,
+        # and not into the mocked Part
+        self.assertEqual(3, expMeas1.insert.call_count)
+        self.assertEqual(0, expPart1[0].insert.call_count)
+        expMeas1.insert.assert_any_call(0.0, scoreDefActiveMeter)
+        expMeas1.insert.assert_any_call(0.0, '8/8')
+        expMeas1.insert.assert_any_call(0.0, 'treble clef')
+
+    def testCoreIntegration2(self):
+        '''
+        sectionScoreCore(): everything basic, as called by sectionFromElement()
+            - no kwargs
+                - but the <measure> elements do have @n so those values should be used
+            - one of most things (<section>, <scoreDef>, and <staffDef>)
+            - two of <measure> (one in a <section>)
+            - things in a <section> are appended properly (different for <score> and <section>)
+        '''
+        # setup the arguments
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <scoreDef meter.count="8" meter.unit="8"/>
+            <staffDef n="1" clef.shape="G" clef.line="2"/>
+            <measure n="400">
+                <staff n="1">
+                    <layer n="1">
+                        <note pname="E" oct="7" dur="1"/>
+                    </layer>
+                </staff>
+            </measure>
+            <section>
+                <measure n="92">
+                    <staff n="1">
+                        <layer n="1">
+                            <note pname="G" oct="4" dur="1"/>
+                        </layer>
+                    </staff>
+                </measure>
+            </section>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = spanner.SpannerBundle()
+        allPartNs = ['1']
+
+        parsed, activeMeter, nextMeasureLeft, backupMeasureNum = base.sectionScoreCore(elem, allPartNs, slurBundle)
+
+        # ensure simple returns are okay
+        self.assertEqual('8/8', activeMeter.ratioString)
+        self.assertIsNone(nextMeasureLeft)
+        self.assertEqual(2, backupMeasureNum)
+        # ensure "parsed" is the right format
+        self.assertEqual(1, len(parsed))
+        self.assertTrue('1' in parsed)
+        self.assertEqual(2, len(parsed['1']))  # one <measure> plus one <section> with one <measure> in it
+        # check the first Measure
+        meas = parsed['1'][0]
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        clefIndex = 0
+        timeSigIndex = 1
+        voiceIndex = 2
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(400, meas.number)
+        self.assertEqual(3, len(meas))  # a Voice, a Clef, a TimeSignature
+        self.assertIsInstance(meas[voiceIndex], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[voiceIndex]))
+        self.assertIsInstance(meas[voiceIndex][0], note.Note)
+        self.assertEqual('E7', meas[voiceIndex][0].nameWithOctave)
+        self.assertIsInstance(meas[clefIndex], clef.TrebleClef)  # check out the Clef
+        self.assertIsInstance(meas[timeSigIndex], meter.TimeSignature)  # check out the TimeSignature
+        self.assertEqual('8/8', meas[timeSigIndex].ratioString)
+        # check the second Measure
+        meas = parsed['1'][1]
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        clefIndex = 0
+        timeSigIndex = 1
+        voiceIndex = 2
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(92, meas.number)
+        self.assertEqual(1, len(meas))  # a Voice
+        self.assertIsInstance(meas[0], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[0]))
+        self.assertIsInstance(meas[0][0], note.Note)
+        self.assertEqual('G4', meas[0][0].nameWithOctave)
+
+    @mock.patch('music21.mei.base.measureFromElement')
+    @mock.patch('music21.mei.base.sectionFromElement')
+    @mock.patch('music21.mei.base.scoreDefFromElement')
+    @mock.patch('music21.mei.base.staffDefFromElement')
+    def testCoreUnit3(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE):
+        '''
+        sectionScoreCore(): everything basic, as called by sectionFromElement()
+            - all kwargs
+                - and the <measure> has no @n; it should use the backupNum
+                - activeMeter = a MagicMock (we expect this returned)
+                - nextMeasureLeft = 'next left measure' (expected in the Measure)
+                - backupMeasureNum = 900 (expected in the Measure)
+
+        mocked:
+            - measureFromElement()
+            - sectionFromElement()
+            - scoreDefFromElement()
+            - staffDefFromElement()
+        '''
+        # setup the arguments
+        # NB: there's more MEI here than we need, but it's shared between unit & integration tests
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <measure>
+                <staff n="1">
+                    <layer n="1">
+                        <note pname="G" oct="4" dur="1"/>
+                    </layer>
+                </staff>
+            </measure>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = mock.MagicMock()
+        allPartNs = ['1']
+        activeMeter = mock.MagicMock(spec_set=meter.TimeSignature)
+        nextMeasureLeft = 'next left measure'
+        backupMeasureNum = 900
+        # setup measureFromElement()
+        expMeas1 = mock.MagicMock(spect_set=stream.Stream)
+        mockMeasureFE.return_value = {'1': expMeas1}
+        # prepare the "expected" return
+        expActiveMeter = activeMeter
+        expNMLeft = None
+        expMeasureNum = backupMeasureNum + 1
+        expected = {'1': [expMeas1]}
+        expected = (expected, expActiveMeter, expNMLeft, expMeasureNum)
+
+        actual = base.sectionScoreCore(elem, allPartNs, slurBundle, activeMeter=activeMeter,
+                                       nextMeasureLeft=nextMeasureLeft, backupMeasureNum=backupMeasureNum)
+
+        # ensure expected == actual
+        self.assertEqual(expected, actual)
+        # ensure measureFromElement()
+        mockMeasureFE.assert_called_once_with(mock.ANY,
+                                              backupMeasureNum + 1,
+                                              allPartNs,
+                                              activeMeter=activeMeter,
+                                              slurBundle=slurBundle)
+        # ensure sectionFromElement()
+        self.assertEqual(0, mockSectionFE.call_count)
+        # ensure scoreDefFromElement()
+        self.assertEqual(0, mockScoreDFE.call_count)
+        # ensure staffDefFromElement()
+        self.assertEqual(0, mockStaffDFE.call_count)
+        # ensure the "nextMeasureLeft" was actually put onto the Measure
+        self.assertEqual(nextMeasureLeft, actual[0]['1'][0].leftBarline)
+
+    def testCoreIntegration3(self):
+        '''
+        sectionScoreCore(): everything basic, as called by sectionFromElement()
+            - all kwargs
+                - and the <measure> has no @n; it should use the backupNum
+                - activeMeter = a MagicMock (we expect this returned)
+                - nextMeasureLeft = 'next left measure' (expected in the Measure)
+                - backupMeasureNum = 900 (expected in the Measure)
+        '''
+        # setup the arguments
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <measure>
+                <staff n="1">
+                    <layer n="1">
+                        <note pname="G" oct="4" dur="1"/>
+                    </layer>
+                </staff>
+            </measure>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = spanner.SpannerBundle()
+        allPartNs = ['1']
+        activeMeter = meter.TimeSignature('8/8')
+        nextMeasureLeft = bar.Repeat('start')
+        backupMeasureNum = 900
+
+        parsed, activeMeter, nextMeasureLeft, backupMeasureNum = base.sectionScoreCore(elem,
+                                                                                       allPartNs,
+                                                                                       slurBundle,
+                                                                                       activeMeter=activeMeter,
+                                                                                       nextMeasureLeft=nextMeasureLeft,
+                                                                                       backupMeasureNum=backupMeasureNum)
+
+        # ensure simple returns are okay
+        self.assertEqual('8/8', activeMeter.ratioString)
+        self.assertIsNone(nextMeasureLeft)
+        self.assertEqual(901, backupMeasureNum)
+        # ensure "parsed" is the right format
+        self.assertEqual(1, len(parsed))
+        self.assertTrue('1' in parsed)
+        self.assertEqual(1, len(parsed['1']))  # one <measure>
+        # check the Measure
+        meas = parsed['1'][0]
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        repeatIndex = 0
+        voiceIndex = 1
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(901, meas.number)
+        self.assertEqual(2, len(meas))  # a Voice, a Repeat
+        self.assertIsInstance(meas[voiceIndex], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[voiceIndex]))
+        self.assertIsInstance(meas[voiceIndex][0], note.Note)
+        self.assertEqual('G4', meas[voiceIndex][0].nameWithOctave)
+        self.assertIsInstance(meas[repeatIndex], bar.Repeat)  # check the Repeat barline
+        self.assertEqual('start', meas[repeatIndex].direction)
+        self.assertIs(meas[repeatIndex], meas.leftBarline)
+
+    @mock.patch('music21.mei.base.environLocal')
+    @mock.patch('music21.mei.base.measureFromElement')
+    @mock.patch('music21.mei.base.sectionFromElement')
+    @mock.patch('music21.mei.base.scoreDefFromElement')
+    @mock.patch('music21.mei.base.staffDefFromElement')
+    def testCoreUnit4(self, mockStaffDFE, mockScoreDFE, mockSectionFE, mockMeasureFE, mockEnviron):
+        '''
+        sectionScoreCore(): as called by sectionFromElement()
+            - there's an "rptboth" barline, so we have to return a "nextMeasureLeft"
+            - the <staffDef> gives a TimeSignature, so we have to change "activeMeter" (and return)
+            - there's a <staffDef> without @n attribute, so we have to warn the user about it
+            - there's an unknown element, so we have to debug-warn the user
+
+        mocked:
+            - measureFromElement()
+            - sectionFromElement()
+            - scoreDefFromElement()
+            - staffDefFromElement()
+            - environLocal
+        '''
+        # setup the arguments
+        # NB: there's more MEI here than we need, but it's shared between unit & integration tests
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <bogus>5</bogus>  <!-- this will be ignored -->
+            <staffDef n="1" meter.count="6" meter.unit="8"/>
+            <staffDef key.accid="3s" key.mode="minor"/>  <!-- this will be ignored -->
+            <measure n="42" right="rptboth">
+                <staff n="1"><layer n="1"><note pname="G" oct="4" dur="1"/></layer></staff>
+            </measure>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = mock.MagicMock()
+        allPartNs = ['1']
+        # setup measureFromElement()
+        # return a Mock with the right measure number
+        expMeas1 = mock.MagicMock(spec_set=stream.Stream)
+        expRepeat = mock.MagicMock(spec_set=bar.Repeat)
+        mockMeasureFE.return_value = {'1': expMeas1, 'next @left': expRepeat}
+        # setup staffDefFromElement()
+        expMeter = mock.MagicMock(spec_set=meter.TimeSignature)
+        mockStaffDFE.return_value = {'meter': expMeter}
+        # prepare the "expected" return
+        expActiveMeter = expMeter
+        expNMLeft = expRepeat
+        expMeasureNum = 1
+        expected = {'1': [expMeas1]}
+        expected = (expected, expActiveMeter, expNMLeft, expMeasureNum)
+        # prepare expected environLocal message
+        expPrintDebug = base._UNPROCESSED_SUBELEMENT.format('{}bogus'.format(_MEINS),
+                                                            '{}section'.format(_MEINS))
+        expWarn = base._UNIMPLEMENTED_IMPORT.format('<staffDef>', '@n')
+
+        actual = base.sectionScoreCore(elem, allPartNs, slurBundle)
+
+        # ensure expected == actual
+        self.assertEqual(expected, actual)
+        # ensure environLocal
+        mockEnviron.printDebug.assert_called_once_with(expPrintDebug)
+        mockEnviron.warn.assert_called_once_with(expWarn)
+        # ensure measureFromElement()
+        mockMeasureFE.assert_called_once_with(mock.ANY,
+                                              1,
+                                              allPartNs,
+                                              activeMeter=expActiveMeter,
+                                              slurBundle=slurBundle)
+        # ensure sectionFromElement()
+        self.assertEqual(0, mockSectionFE.call_count)
+        # ensure scoreDefFromElement()
+        self.assertEqual(0, mockScoreDFE.call_count)
+        # ensure staffDefFromElement()
+        mockStaffDFE.assert_called_once_with(mock.ANY, slurBundle)
+
+    def testCoreIntegration4(self):
+        '''
+        sectionScoreCore(): as called by sectionFromElement()
+            - there's an "rptboth" barline, so we have to return a "nextMeasureLeft"
+            - the <staffDef> gives a TimeSignature, so we have to change "activeMeter" (and return)
+            - there's a <staffDef> without @n attribute, so we have to warn the user about it
+            - there's an unknown element, so we have to debug-warn the user
+        '''
+        # setup the arguments
+        elem = """<section xmlns="http://www.music-encoding.org/ns/mei">
+            <bogus>5</bogus>  <!-- this will be ignored -->
+            <staffDef n="1" meter.count="6" meter.unit="8"/>
+            <staffDef key.accid="3s" key.mode="minor"/>  <!-- this will be ignored -->
+            <measure n="42" right="rptboth">
+                <staff n="1"><layer n="1"><note pname="G" oct="4" dur="1"/></layer></staff>
+            </measure>
+        </section>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = spanner.SpannerBundle()
+        allPartNs = ['1']
+
+        parsed, activeMeter, nextMeasureLeft, backupMeasureNum = base.sectionScoreCore(elem,
+                                                                                       allPartNs,
+                                                                                       slurBundle)
+
+        # ensure simple returns are okay
+        self.assertEqual('6/8', activeMeter.ratioString)
+        self.assertIsInstance(nextMeasureLeft, bar.Repeat)
+        self.assertEqual(1, backupMeasureNum)
+        # ensure "parsed" is the right format
+        self.assertEqual(1, len(parsed))
+        self.assertTrue('1' in parsed)
+        self.assertEqual(1, len(parsed['1']))  # one <measure>
+        # check the Measure
+        meas = parsed['1'][0]
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        timeSigIndex = 0
+        voiceIndex = 1
+        repeatIndex = 2
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(42, meas.number)
+        self.assertEqual(3, len(meas))  # a Voice, a TimeSignature, a Repeat
+        self.assertIsInstance(meas[voiceIndex], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[voiceIndex]))
+        self.assertIsInstance(meas[voiceIndex][0], note.Note)
+        self.assertEqual('G4', meas[voiceIndex][0].nameWithOctave)
+        self.assertIsInstance(meas[timeSigIndex], meter.TimeSignature)  # check the TimeSignature
+        self.assertEqual('6/8', meas[timeSigIndex].ratioString)
+        self.assertIsInstance(meas[repeatIndex], bar.Repeat)  # check the Repeat barline
+        self.assertEqual('end', meas[repeatIndex].direction)
+        self.assertIs(meas[repeatIndex], meas.rightBarline)
+
+    def testCoreIntegration5(self):
+        '''
+        sectionScoreCore(): as called by scoreFromElement()
+
+        With a preposterously embedded set of <section> elements, we have to ensure that
+        staff-related metadata are cascaded properly into the <measure>
+
+        NOTE there is no unit test corresponding to this integration test---this is really all about
+             the cumulative effect
+        '''
+        # setup the arguments
+        elem = """<score xmlns="http://www.music-encoding.org/ns/mei">
+            <scoreDef key.sig="1f" key.mode="minor">
+                <staffGrp>
+                    <staffDef n="1" clef.line="4" clef.shape="F"/>
+                </staffGrp>
+            </scoreDef>
+            <staffDef n="1" meter.count="6" meter.unit="8"/>
+            <section>
+                <staffDef n="1" label="tuba"/>
+                <section>
+                    <measure n="42">
+                        <staff n="1"><layer n="1"><note pname="C" oct="1" dur="1"/></layer></staff>
+                    </measure>
+                </section>
+            </section>
+            <section>
+                <measure n="402">
+                    <staff n="1"><layer n="1"><note pname="C" oct="2" dur="1"/></layer></staff>
+                </measure>
+            </section>
+        </score>"""
+        elem = ETree.fromstring(elem)
+        slurBundle = spanner.SpannerBundle()
+        allPartNs = ['1']
+
+        parsed, activeMeter, nextMeasureLeft, backupMeasureNum = base.sectionScoreCore(elem,
+                                                                                       allPartNs,
+                                                                                       slurBundle)
+
+        # ensure simple returns are okay
+        self.assertEqual('6/8', activeMeter.ratioString)
+        self.assertIsNone(nextMeasureLeft)
+        self.assertEqual(2, backupMeasureNum)
+        # ensure "parsed" is the right format
+        self.assertEqual(1, len(parsed))
+        self.assertTrue('1' in parsed)
+        self.assertEqual(2, len(parsed['1']))  # two <measure>s, each in a <section>
+        # check the first Measure
+        meas = parsed['1'][0][0]
+        # the order of these doesn't matter, but it may change, so this is easier to adjust
+        instrIndex = 0
+        clefIndex = 1
+        keysigIndex = 2
+        timesigIndex = 3
+        voiceIndex = 4
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(42, meas.number)
+        self.assertEqual(5, len(meas))  # an Instrument, a Voice, a Clef, a KeySignature, a TimeSignature
+        self.assertIsInstance(meas[instrIndex], instrument.Tuba)  # check out the Instrument
+        self.assertIsInstance(meas[voiceIndex], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[voiceIndex]))
+        self.assertIsInstance(meas[voiceIndex][0], note.Note)
+        self.assertEqual('C1', meas[voiceIndex][0].nameWithOctave)
+        self.assertIsInstance(meas[clefIndex], clef.BassClef)  # check out the Clef
+        self.assertIsInstance(meas[keysigIndex], key.KeySignature)  # check out the KeySignature
+        self.assertEqual(-1, meas[keysigIndex].sharps)
+        self.assertEqual('minor', meas[keysigIndex].mode)
+        self.assertIsInstance(meas[timesigIndex], meter.TimeSignature)  # check the TimeSignature
+        self.assertEqual('6/8', meas[timesigIndex].ratioString)
+        # check the second Measure
+        meas = parsed['1'][1][0]
+        self.assertIsInstance(meas, stream.Measure)
+        self.assertEqual(402, meas.number)
+        self.assertEqual(1, len(meas))  # a Voice
+        self.assertIsInstance(meas[0], stream.Voice)  # check out the Voice and its Note
+        self.assertEqual(1, len(meas[0]))
+        self.assertIsInstance(meas[0][0], note.Note)
+        self.assertEqual('C2', meas[0][0].nameWithOctave)
+
+
+
+#------------------------------------------------------------------------------
+class TestBarLineFromElement(unittest.TestCase):
+    '''Tests for barLineFromElement()'''
+
+    def testBarLine1(self):
+        '''
+        barLineFromElement(): <barLine rend="dbl"/>
+        '''
+        elem = ETree.Element('barLine', attrib={'rend': 'dbl'})
+        actual = base.barLineFromElement(elem)
+        self.assertIsInstance(actual, bar.Barline)
+        self.assertEqual('double', actual.style)
+
+    def testBarLine2(self):
+        '''
+        barLineFromElement(): <barLine/>
+        '''
+        elem = ETree.Element('barLine')
+        actual = base.barLineFromElement(elem)
+        self.assertIsInstance(actual, bar.Barline)
+        self.assertEqual('regular', actual.style)