nCoda Lychee Docs

MEI Document Representation

Note

Most external developers will not need to use this module directly. We recommend using functionality in Workflow and Action Management whenever possible.

The Document class represents a Lychee-MEI document. If version control integration is enabled, this is managed by the lychee.vcs module.

Guidelines for Using Lychee-MEI Documents

These guidelines help guarantee the integrity of the LMEI document, and avoid duplicating error-prone code. These are absolute requirements for Lychee developers and very strong recommendations for external Lychee users.

  1. For every capability the Document class offers, no other module or class may offer this functionality. This avoids duplicating functionality and thereby introducing possible errors. Other modules and classes may “shadow” Document methods.
  2. No filesystem access can happen outside the document module. The version control system is an exception, though it must be used properly (for example, through the mercurial-hug module) and in the proper place (lychee.vcs). This helps manage a range of potential problems, like XML-related security issues, data races, and so on.
  3. Document manipulation at a level higher than a Lychee-MEI <section> should be performed by the Document class itself. In a situation of “worst case” corruption, where a Lychee-MEI file cannot be loaded from the filesystem, this allows recovering the rest of a document by discarding or ignoring the corrupted <section>.
  4. Capabilities offered by private functions and methods in the document module may be changed to a public function or method if required by another module. Before doing this, carefully consider the implications: once a function or method becomes part of the public API, it is very difficult to change. Would it be better to add the functionality to the Document class? Should the function or method be renamed? Does the function or method make sense in the context of the other public functions and methods?

Lychee developers may be interested in the internal functions and methods, documented here:

Document Module Public Interface

class lychee.document.document.Document(repository_path=None)[source]

Object representing an MEI document. Use methods prefixed with get to obtain portions of the document, automatically loading from files if required. Use methods prefixed with put to submit a new portion to replace the existing portion outright.

If you do not provide a repository_path argument to the initialization method, no files will be written. Additionally, the save_everything() method therefore will not work, and all get_() methods will not return useful data until they have been given data.

Note

When you use a put_() method, the element(s) replace those already present in this Document instance, but they will not be written to the document’s directory until you call save_everything() or the context manager exits, if applicable.

The recommended way to use a Document with file output is as a context manager (using a with statement). This way, you cannot forget to save your changes to the filesystem.

get_from_head(what)[source]

Getter for elements in the <meiHead>.

Parameters:what (str) – The element name to find. See the list of valid values below.
Returns:A list of the requested elements.
Return type:list of lxml.etree.Element

You may request the following elements:

  • fileDesc
  • titleStmt
  • title
  • respStmt
  • a role (arranger, author, composer, editor, funder, librettist, lyricist, or sponsor)
  • pubStmt

The “respStmt” child elements describe Lychee users who have edited this document, whether or not they hold a more specific role.

Also note that, if a role-specific element (such as <composer>) corresponds to a Lychee user, the role-specific element will contain a <persName> with a @nymref attribute that holds the @xml:id value of a <persName> given in the <respStmt>. For example, if this work’s composer is also the only person who has edited this score:

>>> etree.dump(doc.get_from_head('composer')[0])
<composer>
    <persName nymref="#p1234"/>
</composer>
>>> etree.dump(doc.get_from_head('respStmt')[0])
<respStmt>
    <persName xml:id="p1234">
        <persName type="full">Danceathon Smith</persName>
    </persName>
</respStmt>

Note

At this point in time, get_from_head() does not raise (its own) exceptions. If the “what” argument is invalid, this is treated the same as a missing element, so the return value will be None.

get_head()[source]

Load and return the MEI header metadata.

Returns:The <meiHead> portion of the MEI document.
Return type:lxml.etree.Element
Raises:lychee.exceptions.HeaderNotFoundError if the <meiHead> element is missing or it contains a <ptr> without a @target attribute
get_score()[source]

Load and return the whole score, excluding metadata and “inactive” <section> elements.

Returns:A <score> element with relevant <section> elements in the proper order.
Return type:lxml.etree.Element
Raises:lychee.exceptions.SectionNotFoundError if one or more of the <section> elements require for the <score> cannot be found.

Side Effect

Caches the returned <score> for later access.

get_section(section_id)[source]

Load and return a section of the score.

Returns:The section with an @xml:id matching section_id.
Return type:lxml.etree.Element
Raises:lychee.exceptions.SectionNotFoundError if no <section> with the specified @xml:id can be found.
Raises:lychee.exceptions.InvalidFileError if the <section> is found but cannot be loaded because it is invalid.

Side Effects

If the section is not already loaded, get_section() will try to fetch it from the filesystem, if a repository is configured.

get_section_ids(all_sections=False)[source]

By default, return the ordered @xml:id attributes of active <section> elements in this score. If all_sections is True, return the @xml:id attributes of all <section> elements in this MEI document, in arbitrary order.

Parameters:all_sections (bool) – Whether to return the IDs of all sections in this document, rather than just the sections currently active in the score.
Returns:A list of the section IDs.
Return type:list of str
move_section_to(xmlid, position)[source]

Move a <section> to another position in the score.

Parameters:
  • xmlid (string) – The @xml:id attribute of the <section> to move. The section may or may not already be in the active score, but it must already be part of the document.
  • position (int) – The requested new index of the section in the active score.
Raises:

SectionNotFoundError if the <section> to move is not found in the repository.

Note that this function simply moves the indicated section to the requested position, but does not save the Document instance or anything else (e.g., does not cause an outbound conversion that would update code external to Lychee).

put_head(new_head)[source]

Save new header metadata.

Param:new_head: An <meiHead> element that should replace the existing one.
put_in_head(new_elem)[source]

As per get_from_head(), but with setting instead.

Warning

This method is not implemented. Refer to T109 for more information.

put_score(new_music)[source]

Save a new score in place of the existing one.

Parameters:new_music (lxml.etree.Element) – The <score> element to use in place of the existing one.
Returns:The @xml:id attributes of all top-level <section> elements found in new_music, in the order they were found.
Return type:list of str

The “score order” in new_music replaces the existing “score order.” Note that existing <section> elements that aren’t part of new_music are not deleted—they remain tracked internally. Also note that any <section> elements already contained in the local list of sections is replaced by the section that has a matching @xml:id in new_music. Sections that don’t have an @xml:id will be given one.

put_section(new_section)[source]

Add or replace a <section> in the current MEI document.

Parameters:new_section (lxml.etree.Element) – A new section or a replacement for an existing section with the same @xml:id attribute.
Returns:The @xml:id of the saved new_section.
Return type:str

Note

If new_section is missing an @xml:id attribute, or has an invalid @xml:id attribute, a new one is created.

save_everything()[source]

Write the MEI document(s) into files.

Returns:A list of the absolute pathnames that are part of this Lychee-MEI document.
Return type:list of str
Raises:lychee.exceptions.CannotSaveError if the document cannot be written to the filesystem (this happens when repository_path was not supplied on initialization).

A Lychee-MEI document is a complex of various XML elements. This method arranges for the documents stored in memory to be saved into files in the proper arrangement as specified by the order.

Note that the return value includes any file in the document. The files may not have been modified, and in fact may not even have been saved at all—they are simply part of this document.