Data Validation

Data validation is the process of ensuring that the structure of the data confirms to a defined format. This is especially useful for developers when they add support for a data format to their application. They are able to test that the data they produce is correct and conforms to the format.

In the world of XML, validation involves checking the names of elements, their nesting, names of attribues and the type of values in elements and attributes. Several XML Schema languages exist for the formal definition of an XML structure. For the OpenLyrics data format the RelaxNG chosen and used to create the format definition.

Tools and Libraries

The OpenLyrics RelaxNG XML schema can be used in any programming language which has libraries with support for XML schemas. It is also possible, to some extent, to convert RelaxNG schemas to other languages, like DTD or W3C XML Schema.

A list of other software for RelaxNG can be found on the RelaxNG site.

Validation examples

CLI validation using Libxml2

To validate an OpenLyrics XML file use the following command:

xmllint --noout --relaxng openlyrics-0.9.rng xmlfile.xml

xmlfile.xml is the OpenLyrics file which you need to validate and openlyrics-0.9.rng contains OpenLyrics RelaxNG XML schema. xmllint is part of the libxml2 library and can be installed on Debian based system with apt-get install libxml2-utils.

Validating using Python

Example:

#!/usr/bin/env python3

from lxml import etree

xml_validator = etree.RelaxNG(file = "openlyrics-0.9.rng")
xml_file = etree.parse("xmlfile.xml")
is_valid = xml_validator.validate(xml_file)

print(f'xmlfile.xml is{"" if is_valid else " not"} valid')

xmlfile.xml is the OpenLyrics file which you need to validate and openlyrics-0.9.rng contains OpenLyrics RelaxNG XML schema.

Bundled CLI Script

To execute the included script, use the following command:

python3 tools/validate.py openlyrics-0.9.rng xmlfile.xml

xmlfile.xml is the OpenLyrics file which you need to validate and openlyrics-0.9.rng contains OpenLyrics RelaxNG XML schema. Python >= 3.6 and lxml is required.

RelaxNG XML schema

The following RelaxNG XML schema was created:

<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
         ns="http://openlyrics.info/namespace/2009/song">

  <!-- TOP LEVEL -->

  <start>
    <element name="song">
      <ref name="songAttributes"/>
      <ref name="properties"/>
      <optional>
        <ref name="format"/>
      </optional>
      <ref name="lyrics"/>
    </element>
  </start>

  <define name="properties">
    <element name="properties">
      <interleave> <!-- allow occur in any order -->
        <!-- at least one title is always required -->
        <ref name="titles"/>
        <!-- other properties items are optional -->
        <optional>
          <ref name="authors"/>
        </optional>
        <optional>
          <ref name="copyright"/>
        </optional>
        <optional>
          <ref name="ccliNo"/>
        </optional>
        <optional>
          <ref name="released"/>
        </optional>
        <!-- Music Info -->
        <optional>
          <ref name="transposition"/>
        </optional>
        <optional>
          <ref name="tempo"/>
        </optional>
        <optional>
          <ref name="key"/>
        </optional>
        <optional>
          <ref name="timeSignature"/>
        </optional>
        <!-- Other Info -->
        <optional>
          <ref name="variant"/>
        </optional>
        <optional>
          <ref name="publisher"/>
        </optional>
        <optional>
          <ref name="version"/>
        </optional>
        <optional>
          <ref name="keywords"/>
        </optional>
        <optional>
          <ref name="verseOrder"/>
        </optional>
        <optional>
          <ref name="songbooks"/>
        </optional>
        <optional>
          <ref name="themes"/>
        </optional>
        <optional>
          <ref name="comments"/>
        </optional>
      </interleave>
    </element>
  </define>

  <define name="format">
    <element name="format">
      <ref name="formatTags"/>
    </element>
  </define>


  <define name="lyrics">
    <element name="lyrics">
      <interleave>
        <!-- at least one verse is required -->
        <oneOrMore>
          <ref name="verse"/>
        </oneOrMore>
        <zeroOrMore>
          <ref name="instrument"/>
        </zeroOrMore>
      </interleave>
    </element>
  </define>

  <!-- PROPERTIES -->

  <define name="titles">
    <element name="titles">
      <oneOrMore>
        <element name="title">
          <ref name="nonEmptyContent"/>
          <optional>
            <ref name="langAttribute"/>
            <optional>
              <ref name="translitAttribute"/>
            </optional>
          </optional>
          <optional>
            <attribute name="original">
              <data type="boolean"/>
            </attribute>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </define>

  <!-- AUTHOR info -->

  <define name="authors">
    <element name="authors">
      <oneOrMore>
        <element name="author">
          <ref name="nonEmptyContent"/>
          <optional>
            <choice>
              <attribute name="type">
                <choice>
                  <value>words</value>
                  <value>music</value>
                  <value>arrangement</value>
                </choice>
              </attribute>
              <!-- when attrib 'type' value is 'translation' it can have an attribute 'lang'.
                   'xml:lang' can't be used. xml:lang means in what language is the
                   content of an element and this is not the case. -->
              <group>
                <attribute name="type">
                  <value>translation</value>
                </attribute>
                <optional>
                  <ref name="langAttribute"/>
                </optional>
              </group>
            </choice>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </define>

  <define name="copyright">
    <element name="copyright">
      <ref name="nonEmptyContent"/>
    </element>
  </define>

  <define name="ccliNo">
    <element name="ccliNo">
      <data type="positiveInteger"/>
    </element>
  </define>

  <define name="released">
    <element name="released">
      <!-- allowed values
           1779
           1779-12
           1779-12-31
           1779-12-31T13:15:30+01:00 -->
      <choice>
        <data type="gYear"/>
        <data type="gYearMonth"/>
        <data type="date"/>
        <data type="dateTime"/>
      </choice>
    </element>
  </define>

  <!-- MUSIC INFO -->

  <define name="transposition">
    <element name="transposition">
      <data type="integer">
        <param name="minInclusive">-11</param>
        <param name="maxInclusive">11</param>
      </data>
    </element>
  </define>

  <define name="tempo">
    <element name="tempo">
      <choice>
        <!-- attrib 'type' value 'bpm' - beatss per minute required -->
        <group>
          <data type="positiveInteger">
            <param name="minInclusive">30</param>
            <param name="maxInclusive">250</param>
          </data>
          <attribute name="type">
            <value>bpm</value>
          </attribute>
        </group>
        <!-- attrib 'type' value 'text' - any text -->
        <group>
          <ref name="nonEmptyContent"/>
          <attribute name="type">
            <value>text</value>
          </attribute>
        </group>
      </choice>
    </element>
  </define>

  <define name="key">
    <element name="key">
      <ref name="keyNote"/>
    </element>
  </define>

  <define name="timeSignature">
    <element name="timeSignature">
      <data type="token">
        <param name="minLength">3</param>
        <param name="maxLength">5</param>
        <!-- number between 1 and 63 + "/" + numbers: 1, 2, 4, 8, 16, 32, 64 -->
        <param name="pattern">(6[0-3]|[1-5][0-9]|[1-9])/(64|32|16|8|4|2|1)</param>
      </data>
   </element>
  </define>

  <!-- OTHER INFO -->

  <define name="variant">
    <element name="variant">
      <ref name="nonEmptyContent"/>
    </element>
  </define>

  <define name="publisher">
    <element name="publisher">
      <ref name="nonEmptyContent"/>
    </element>
  </define>

  <define name="version">
    <element name="version">
      <ref name="nonEmptyContent"/>
    </element>
  </define>

  <define name="keywords">
    <element name="keywords">
      <ref name="nonEmptyContent"/>
    </element>
  </define>

  <define name="verseOrder">
    <element name="verseOrder">
      <list>
        <oneOrMore>
          <choice>
            <ref name="verseNameType"/>
            <ref name="instrumentNameType"/>
          </choice>
        </oneOrMore>
      </list>
    </element>
  </define>

  <define name="songbooks">
    <element name="songbooks">
      <oneOrMore>
        <element name="songbook">
          <attribute name="name">
            <ref name="nonEmptyContent"/>
          </attribute>
          <optional>
            <!-- 'entry' is like song number but song number must not
                 always be integer and it can contain letters.
                 examples: '153c' or '023', etc. -->
            <attribute name="entry">
              <ref name="nonEmptyContent"/>
            </attribute>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </define>

  <define name="themes">
    <element name="themes">
      <oneOrMore>
        <element name="theme">
          <ref name="nonEmptyContent"/>
          <optional>
            <ref name="langAttribute"/>
            <optional>
              <ref name="translitAttribute"/>
            </optional>
          </optional>
        </element>
      </oneOrMore>
    </element>
  </define>

  <define name="comments">
    <element name="comments">
      <oneOrMore>
        <element name="comment">
          <ref name="nonEmptyContent"/>
        </element>
      </oneOrMore>
    </element>
  </define>

  <!-- FORMAT -->

  <define name="formatTags">
    <!-- Allow only one set of formatting tags for lyrics -->
    <element name="tags">
      <attribute name="application">
        <ref name="nonEmptyContent"/>
      </attribute>
      <oneOrMore>
        <ref name="formatTagsTag"/>
      </oneOrMore>
    </element>
  </define>

  <define name="formatTagsTag">
    <element name="tag">
      <attribute name="name">
        <ref name="nonEmptyContent"/>
      </attribute>
      <element name="open">
        <ref name="nonEmptyContent"/>
      </element>
      <!-- Close element is optional. Formatting without text may be present.
           e.g. <br/> -->
      <optional>
        <element name="close">
          <ref name="nonEmptyContent"/>
        </element>
      </optional>
    </element>
  </define>

 <!-- LYRICS -->

  <define name="verse">
    <element name="verse">
      <ref name="verseAttributes"/>
      <optional>
        <ref name="langAttribute"/>
        <optional>
          <ref name="translitAttribute"/>
        </optional>
      </optional>
      <oneOrMore>
        <ref name="lines"/>
      </oneOrMore>
    </element>
  </define>

  <define name="lines">
    <element name="lines">
      <optional>
        <attribute name="part">
          <ref name="nonEmptyContent"/>
        </attribute>
      </optional>
      <optional>
        <attribute name="break">
          <value>optional</value>
        </attribute>
      </optional>
      <optional>
        <attribute name="repeat">
          <data type="integer">
            <param name="minInclusive">2</param>
          </data>
        </attribute>
      </optional>
      <zeroOrMore>
        <ref name="linesContent"/>
      </zeroOrMore>
      <ref name="linesContent"/>
    </element>
  </define>

  <define name="chord">
    <element name="chord">
      <attribute name="root">
        <ref name="musicalNote"/>
      </attribute>
      <optional>
        <attribute name="bass">
          <ref name="musicalNote"/>
        </attribute>
      </optional>
      <optional>
        <attribute name="structure">
          <ref name="chords"/>
        </attribute>
      </optional>
      <optional>
        <attribute name="upbeat">
          <data type="boolean"/>
        </attribute>
      </optional>
      <choice>
        <oneOrMore>
          <ref name="linesContent"/>
        </oneOrMore>
        <empty/>
      </choice>
    </element>
  </define>

  <define name="keyNote">
    <choice>
      <!-- theoretical keys -->
      <!-- 10♯ -->
      <value>A#</value>
      <value>Fxm</value>
      <!-- 9♯ -->
      <value>D#</value>
      <value>B#m</value>
      <!-- 8♯ -->
      <value>G#</value>
      <value>E#m</value>
      <!-- /theoretical keys -->
      <!-- 7♯ -->
      <value>C#</value>
      <value>A#m</value>
      <!-- 6♯ -->
      <value>F#</value>
      <value>D#m</value>
      <!-- 5♯ -->
      <value>B</value>
      <value>G#m</value>
      <!-- 4♯ -->
      <value>E</value>
      <value>C#m</value>
      <!-- 3♯ -->
      <value>A</value>
      <value>F#m</value>
      <!-- 2♯ -->
      <value>D</value>
      <value>Bm</value>
      <!-- 1♯ -->
      <value>G</value>
      <value>Em</value>
      <!-- 0  -->
      <value>C</value>
      <value>Am</value>
      <!-- 1♭ -->
      <value>F</value>
      <value>Dm</value>
      <!-- 2♭ -->
      <value>Bb</value>
      <value>Gm</value>
      <!-- 3♭ -->
      <value>Eb</value>
      <value>Cm</value>
      <!-- 4♭ -->
      <value>Ab</value>
      <value>Fm</value>
      <!-- 5♭ -->
      <value>Db</value>
      <value>Bbm</value>
      <!-- 6♭ -->
      <value>Gb</value>
      <value>Ebm</value>
      <!-- 7♭ -->
      <value>Cb</value>
      <value>Abm</value>
    </choice>
  </define>

  <define name="musicalNote">
    <!-- Only English notation is allowed -->
    <choice>
      <!-- chromatic notes -->
      <value>C</value>
      <value>C#</value>
      <value>Db</value>
      <value>D</value>
      <value>D#</value>
      <value>Eb</value>
      <value>E</value>
      <value>F</value>
      <value>F#</value>
      <value>Gb</value>
      <value>G</value>
      <value>G#</value>
      <value>Ab</value>
      <value>A</value>
      <value>A#</value>
      <value>Bb</value>
      <value>B</value>
      <!-- supporting theoretical keys -->
      <value>E#</value><!-- supporting major F# scale (6#) -->
      <value>B#</value><!-- supporting major C# scale (7#) -->
      <value>Fx</value><!-- supporting major G# scale (8#) -->
      <value>Cx</value><!-- supporting major D# scale (9#) -->
      <value>Gx</value><!-- supporting major A# scale (10#) -->
      <value>Cb</value><!-- supporting major Gb scale (6b) -->
      <value>Fb</value><!-- supporting major Cb scale (7b) -->
      <!-- /supporting theoretical keys -->
    </choice>
  </define>

  <define name="chords">
    <choice>
      <!-- ** 2 note chords -->
      <!-- perfect 5th; power chord -->
      <value>5</value>
      <value>power</value>

      <!-- *** 3 note chords -->
      <!-- major -->
      <value>3-5</value>
      <!-- minor -->
      <value>m3-5</value>
      <value>min</value>
      <!-- augmented -->
      <value>3-a5</value>
      <value>aug</value>
      <!-- diminished -->
      <value>m3-d5</value>
      <value>dim</value>

      <!-- **** 4 note chords -->
      <!-- dominant 7th -->
      <value>3-5-m7</value>
      <value>dom7</value>
      <!-- major 7th -->
      <value>3-5-7</value>
      <value>maj7</value>
      <!-- minor 7th -->
      <value>m3-5-m7</value>
      <value>min7</value>
      <!-- diminished 7th -->
      <value>m3-d5-d7</value>
      <value>dim7</value>
      <!-- half-diminished 7th -->
      <value>m3-d5-m7</value>
      <value>halfdim7</value>
      <!-- minor major 7th -->
      <value>m3-5-7</value>
      <value>minmaj7</value>
      <!-- augmented major 7th -->
      <value>3-a5-7</value>
      <value>augmaj7</value>
      <!-- dominant 7th flat 5 -->
      <value>3-d5-m7</value>
      <!-- dominant 7th sharp 5; augmented 7th -->
      <value>3-a5-m7</value>
      <value>aug7</value>
      <!-- diminished major 7th -->
      <value>m3-d5-7</value>
      <!-- major 7th flat 5 -->
      <value>3-d5-7</value>
      <!-- major 6th -->
      <value>3-5-6</value>
      <value>maj6</value>
      <!-- (major minor 6th) -->
      <value>3-5-m6</value>
      <value>maj6b</value>
      <!-- minor 6th -->
      <value>m3-5-6</value>
      <value>min6</value>
      <!-- (minor minor 6th) -->
      <value>m3-5-m6</value>
      <value>min6b</value>

      <!-- ***** 5 note chords -->
      <!-- (dominant) 9th -->
      <value>3-5-m7-9</value>
      <value>dom9</value>
      <!-- dominant minor 9th -->
      <value>3-5-m7-m9</value>
      <value>dom9b</value>
      <!-- major 9th -->
      <value>3-5-7-9</value>
      <value>maj9</value>
      <!-- minor (dominant) 9th -->
      <value>m3-5-m7-9</value>
      <value>min9</value>
      <!-- minor major 9th -->
      <value>m3-5-7-9</value>
      <value>minmaj9</value>
      <!-- augmented major 9th -->
      <value>3-a5-7-9</value>
      <!-- augmented (dominant) 9th -->
      <value>3-a5-m7-9</value>
      <value>aug9</value>
      <!-- half-diminished 9th -->
      <value>m3-d5-m7-9</value>
      <value>halfdim9</value>
      <!-- half-diminished minor 9th -->
      <value>m3-d5-m7-m9</value>
      <!-- diminished 9th -->
      <value>m3-d5-d7-9</value>
      <!-- diminished minor 9th -->
      <value>m3-d5-d7-m9</value>
      <!-- dominant flat 10 -->
      <value>3-5-m7-m10</value>

      <!-- ****** 6 note chords -->
      <!-- (dominant) 11th -->
      <value>3-5-m7-9-11</value>
      <!-- major 11th -->
      <value>3-5-7-9-11</value>
      <!-- minor (dominant) 11th -->
      <value>m3-5-m7-9-11</value>
      <!-- minor major 11th -->
      <value>m3-5-7-9-11</value>
      <!-- acoustic (dominant) 11th -->
      <value>3-5-m7-9-a11</value>
      <!-- acoustic major 11th -->
      <value>3-5-7-9-a11</value>
      <!-- acoustic minor (dominant) 11th -->
      <value>m3-5-m7-9-a11</value>
      <!-- acoustic minor major 11th -->
      <value>m3-5-7-9-a11</value>
      <!-- augmented major 11th -->
      <value>3-a5-7-9-11</value>
      <!-- augmented (dominant) 11th -->
      <value>3-a5-m7-9-11</value>
      <!-- half-diminished 11th -->
      <value>m3-d5-m7-m9-11</value>
      <!-- diminished 11th -->
      <value>m3-d5-d7-m9-d11</value>

      <!-- 7 note chords -->
      <!-- (dominant) 13th -->
      <value>3-5-m7-9-11-13</value>
      <!-- major 13th -->
      <value>3-5-7-9-11-13</value>
      <!-- minor (dominant) 13th -->
      <value>m3-5-m7-9-11-13</value>
      <!-- minor major 13th -->
      <value>m3-5-7-9-11-13</value>
      <!-- (dominant) 13th -->
      <value>3-5-m7-9-a11-13</value>
      <!-- major 13th -->
      <value>3-5-7-9-a11-13</value>
      <!-- minor (dominant) 13th -->
      <value>m3-5-m7-9-a11-13</value>
      <!-- minor major 13th -->
      <value>m3-5-7-9-a11-13</value>
      <!-- augmented major 13th -->
      <value>3-a5-7-9-11-13</value>
      <!-- augmented (dominant) 13th -->
      <value>3-a5-m7-9-11-13</value>
      <!-- half-diminished 13th -->
      <value>m3-d5-m7-m9-11-13</value>

      <!-- *** Figured 3 note chords -->
      <!-- major/minor suspended 4th -->
      <value>4-5</value>
      <value>sus4</value>
      <!-- major/minor suspended 2nd -->
      <value>2-5</value>
      <value>sus2</value>

      <!-- **** Figured 4 note chords -->
      <!-- dominant (7th) major 6th -->
      <value>3-5-m7-13</value>
      <!-- major 6th 9th -->
      <value>3-5-6-9</value>
      <!-- major added 9th -->
      <value>3-5-9</value>
      <value>add9</value>
      <!-- minor added 9th -->
      <value>m3-5-9</value>
      <!-- augmented added 9th -->
      <value>3-a5-9</value>
      <!-- major 6th suspended 4th -->
      <value>4-5-6</value>
      <!-- major 6th suspended 2nd -->
      <value>2-5-6</value>
      <!-- minor 6th suspended 4th -->
      <value>4-5-m6</value>
      <!-- minor 6th suspended 2nd -->
      <value>2-5-m6</value>
      <!-- dominant/minor 7th suspended 4th -->
      <value>4-5-m7</value>
      <!-- dominant/minor 7th suspended 2nd -->
      <value>2-5-m7</value>
      <!-- (minor) major 7th suspended 4th -->
      <value>4-5-7</value>
      <!-- (minor) major 7th suspended 2nd -->
      <value>2-5-7</value>
      <!-- augmented major 7th suspended 4th -->
      <value>4-a5-7</value>
      <!-- augmented major 7th suspended 2nd -->
      <value>2-a5-7</value>
      <!-- half-diminished 7th suspended 4th; dominant 7th flat 5 suspended 4th -->
      <value>4-d5-m7</value>
      <!-- half-diminished 7th suspended 2nd; dominant 7th flat 5 suspended 2nd -->
      <value>2-d5-m7</value>
      <!-- diminished 7th suspended 4th -->
      <value>4-d5-d7</value>
      <!-- diminished 7th suspended 2nd -->
      <value>2-d5-d7</value>
      <!-- diminished major 7th suspended 4th; major 7th flat 5 suspended 4th -->
      <value>4-d5-7</value>
      <!-- diminished major 7th suspended 2nd; major 7th flat 5 suspended 2nd -->
      <value>2-d5-7</value>
      <!-- dominant (7th) major 6th suspended 4th -->
      <value>4-5-m7-13</value>
      <!-- dominant (7th) major 6th suspended 4th -->
      <value>2-5-m7-13</value>

      <!-- ***** Figured 5 note chords -->
      <!-- (dominant) 9th suspended 4th -->
      <value>4-5-m7-9</value>
      <!-- dominant minor 9th suspended 4th -->
      <value>4-5-m7-m9</value>
      <!-- major 9th suspended 4th -->
      <value>4-5-7-9</value>
      <!-- augmented major 9th suspended 4th -->
      <value>4-a5-7-9</value>
      <!-- augmented (dominant) 9th suspended 4th -->
      <value>4-a5-m7-9</value>

      <data type="token">
        <param name="minLength">1</param>
        <!--
        Notation:
        - major      - [not marked]
        - minor      - m
        - perfect    - [not marked]
        - diminished - d
        - augmented  - a

        Music intervals:
        | Int. | Dim. | Min. | Per. | Maj. | Aug. |
        |::::::|::::::|::::::|::::::|::::::|::::::|
        |  1   |  d1  |      |  1   |      |  a1  |
        |  2   |  d2  |  m2  |      |  2   |  a2  |
        |  3   |  d3  |  m3  |      |  3   |  a3  |
        |  4   |  d4  |      |  4   |      |  a4  |
        |  5   |  d5  |      |  5   |      |  a5  |
        |  6   |  d6  |  m6  |      |  6   |  a6  |
        |  7   |  d7  |  m7  |      |  7   |  a7  |
        |  8   |  d8  |      |  8   |      |  a8  |
        |  9   |  d9  |  m9  |      |  9   |  a9  |
        | 10   | d10  | m10  |      |  10  | a10  |
        | 11   | d11  |      |  11  |      | a11  |
        | 12   | d12  |      |  12  |      | a12  |
        | 13   | d13  | m13  |      |  13  | a13  |
        | 14   | d14  | m14  |      |  14  | a14  |
        | 15   | d15  |      |  15  |      | a15  |

        Format:
        1. The root note (1) should not be included
        2. All other intervals (see the table above) can be included once
        3. Intervals must be separated by a hyphen character (-)
        4. Intervals should be in ascending order (see the table above)
        5. At least 1, but no more than 12, intervals can be specified

        Variants:
        - Harmonics possible: (a?2)?-?(m?3)?-?((d|a)?4)?-?((d|a)?5)?-?(m?6)?-?((d|m)?7)?-?(d?8)?-?((m|a)?9)?-?(m?10)?-?((d|a)?11)?-?(m?13)?
          Covers all variants from standardized 69 chords + a2, d4, a4, d8, a9, m13 (one per step)
        - Logically possible: (d1)?-?(a1)?-?(d2)?-?(m2)?-?2?-?(a2)?-?(d3)?-?(m3)?-?3?-?(a3)?-?(d4)?-?4?-?(a4)?-?(d5)?-?5?-?(a5)?-?(d6)?-?(m6)?-?6?-?(a6)?-?(d7)?-?(m7)?-?7?-?(a7)?-?(d8)?-?8?-?(a8)?-?(d9)?-?(m9)?-?9?-?(a9)?-?(d10)?-?(m10)?-?(10)?-?(a10)?-?(d11)?-?(11)?-?(a11)?-?(d12)?-?(12)?-?(a12)?-?(d13)?-?(m13)?-?(13)?-?(a13)?-?(d14)?-?(m14)?-?(14)?-?(a14)?-?(d15)?-?(15)?-?(a15)?
          Covers all possible variants (52) in ascending order. Covers format aspects of expectations: 1, 2, 3, 4.
        - Logically possible (max. 12 notes): (([2-9]|1[0-5]|m([23679]|10|13|14)|(d|a)(1[0-5]|[1-9]))-){0,11}([2-9]|1[0-5]|m([23679]|10|13|14)|(d|a)(1[0-5]|[1-9]))
          Covers all variants (52), but only 12 segments (in any order). Covers format aspects of expectations: 1, 3, 5.
        We need to use both logical regular expressions to cover all 5 aspects of expectations.
        -->
        <param name="pattern">(d1)?-?(a1)?-?(d2)?-?(m2)?-?2?-?(a2)?-?(d3)?-?(m3)?-?3?-?(a3)?-?(d4)?-?4?-?(a4)?-?(d5)?-?5?-?(a5)?-?(d6)?-?(m6)?-?6?-?(a6)?-?(d7)?-?(m7)?-?7?-?(a7)?-?(d8)?-?8?-?(a8)?-?(d9)?-?(m9)?-?9?-?(a9)?-?(d10)?-?(m10)?-?(10)?-?(a10)?-?(d11)?-?(11)?-?(a11)?-?(d12)?-?(12)?-?(a12)?-?(d13)?-?(m13)?-?(13)?-?(a13)?-?(d14)?-?(m14)?-?(14)?-?(a14)?-?(d15)?-?(15)?-?(a15)?</param>
        <param name="pattern">(([2-9]|1[0-5]|m([23679]|10|13|14)|(d|a)(1[0-5]|[1-9]))-){0,11}([2-9]|1[0-5]|m([23679]|10|13|14)|(d|a)(1[0-5]|[1-9]))</param>
      </data>
    </choice>
  </define>

  <define name="tag">
    <element name="tag">
      <attribute name="name">
        <ref name="nonEmptyContent"/>
      </attribute>
      <!-- allow using more formatting tags for text -->
      <!-- e.g. <tag name="bold"><tag name="red">my text</tag></tag> -->
      <choice>
        <oneOrMore>
          <ref name="linesContent"/>
        </oneOrMore>
        <!-- Allow empty tag. Formatting without text may be present.
             e.g. <tag name="br"/> -->
        <empty/>
      </choice>
    </element>
  </define>

  <define name="verseAttributes">
    <attribute name="name">
      <ref name="verseNameType"/>
    </attribute>
  </define>

  <define name="songAttributes">
    <!-- by default: value of type string is required in attr -->
    <attribute name="version">
      <data type="NMTOKEN"> <!-- one word value -->
        <!-- allow only values like: '0.1' '11.2' '13.14.15'
        <param name="pattern">[0-9]+\.[0-9]+(\.[0-9]+)?</param> -->
        <!-- RelaxNG xml schema is specific for openlyrics version -->
        <param name="pattern">0\.9</param>
      </data>
    </attribute>
    <optional>
      <attribute name="xml:lang">
        <data type="language"/>
      </attribute>
    </optional>
    <optional>
      <attribute name="createdIn">
        <ref name="nonEmptyContent"/>
      </attribute>
    </optional>
    <optional>
      <attribute name="modifiedIn">
        <ref name="nonEmptyContent"/>
      </attribute>
    </optional>
    <optional>
      <attribute name="modifiedDate">
        <!-- date format: ISO 8601 -->
        <data type="dateTime"/>
      </attribute>
    </optional>
    <optional>
      <attribute name="chordNotation">
        <choice>
          <value>english</value>
          <value>english-b</value>
          <value>german</value>
          <value>dutch</value>
          <value>hungarian</value>
          <value>neolatin</value>
        </choice>
      </attribute>
    </optional>
  </define>

  <define name="verseNameType">
    <data type="NMTOKEN">
      <param name="minLength">1</param>
      <!-- 3 part: [verse][verse_number][verse_part]
           verse      -        v1, v2, v1a, …
           chorus     - c, ca, c1, c2, c1a, …
           pre-chorus - p, pa, p1, p2, p1a, …
           bridge     - b, ba, b1, b2, b1a, …
           other      - o, oa, o1, o2, o1a, …
           intro      - i, ia, i1, i2, i1a, …
           ending     - e, ea, e1, e2, e1a, … -->
      <param name="pattern">(v[1-9]\d*[a-z]?)|([cpboie][1-9]\d?[a-z]?)|([cpboie][a-z]?)</param>
    </data>
  </define>

  <define name="instrumentNameType">
    <data type="NMTOKEN">
      <param name="minLength">1</param>
      <!-- 3 part: [verse][verse_number][verse_part]
           intro  - i, ia, i1, i2, i1a, …
           ending - e, ea, e1, e2, e1a, …
           solo   - s, sa, s1, s2, s1a, …
           middle - m, ma, m1, m2, m1a, … -->
      <param name="pattern">([iesm][1-9]\d?[a-z]?)|([iesm][a-z]?)</param>
    </data>
  </define>

  <define name="langAttribute">
    <attribute name="lang">
      <data type="language"/>
    </attribute>
  </define>

  <!-- transliteration -->
  <define name="translitAttribute">
    <attribute name="translit">
      <data type="language"/>
    </attribute>
  </define>

  <define name="nonEmptyContent">
    <data type="string">
      <param name="minLength">1</param>
    </data>
  </define>

  <define name="linesContent">
    <!-- allow tag 'tag' inside regular text - mixed content -->
    <optional>
      <ref name="tag"/>
    </optional>
    <!-- allow tag 'comment' inside regular text - mixed content -->
    <optional>
      <element name="comment">
        <ref name="nonEmptyContent"/>
      </element>
    </optional>
    <!-- allow tag 'chord' inside regular text - mixed content -->
    <optional>
      <ref name="chord"/>
    </optional>
    <text/>
    <optional>
      <element name="br">
        <empty/>
      </element>
    </optional>
  </define>

 <!-- INSTRUMENT -->

  <define name="instrument">
    <element name="instrument">
      <attribute name="name">
        <ref name="instrumentNameType"/>
      </attribute>
      <oneOrMore>
        <element name="lines">
          <optional>
            <attribute name="repeat">
              <data type="integer">
                <param name="minInclusive">2</param>
              </data>
            </attribute>
          </optional>
          <choice>
            <oneOrMore>
              <ref name="beat"/>
            </oneOrMore>
            <oneOrMore>
              <ref name="chord"/>
            </oneOrMore>
          </choice>
        </element>
      </oneOrMore>
    </element>
  </define>

  <define name="beat">
    <element name="beat">
      <zeroOrMore>
        <ref name="chord"/>
      </zeroOrMore>
    </element>
  </define>

</grammar>