<?xml version="1.0" encoding="UTF-8"?>
<!--
  The Canvas file format, schema versions 1 and 2.

  This grammar is the definition of the canvas vocabulary. It is normative and
  it is the only place the vocabulary is written down: the validator in
  canvas/validate.py knows about `id` and `v` as labels to print in a
  diagnostic and knows nothing else about what is legal.

  It stands on its own: xmllint validates a file against this grammar with no
  Python involved at all. README.md gives the exact command.

  The vocabulary is closed. There is no wildcard, no `<anyName/>`, no foreign
  namespace and no escape hatch, by design: <decision>, <risk> and
  <acceptance-criterion> are the named tripwire in the engineering spec, and a
  closed list that can be opened by configuration is not closed.
-->
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">

  <start>
    <choice>
      <ref name="canvas-v1"/>
      <ref name="canvas-v2"/>
    </choice>
  </start>

  <!-- The root. It is the one node that carries neither `id` nor `v`:
       node-identity.md §4 puts <canvas> outside the identity rules entirely. -->
  <define name="canvas-v1">
    <element name="canvas">
      <attribute name="ledger">
        <data type="token">
          <param name="minLength">1</param>
        </data>
      </attribute>
      <attribute name="schema">
        <value>1</value>
      </attribute>
      <zeroOrMore>
        <choice>
          <ref name="section-outer-v1"/>
          <ref name="leaf-v1"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>

  <define name="canvas-v2">
    <element name="canvas">
      <attribute name="ledger">
        <data type="token"><param name="minLength">1</param></data>
      </attribute>
      <attribute name="schema"><value>2</value></attribute>
      <zeroOrMore>
        <choice>
          <ref name="section-outer-v2"/>
          <ref name="leaf-v2"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>

  <!-- `id` and `v`, required on every node except the root.

       id: node-identity.md §1 — four characters, first a lowercase letter, the
       remaining three from lowercase letters and digits with the visually
       confusable l, 1, o, 0 and i excluded. The exclusion clause is attached to
       "remaining three", so the first character is [a-z]; a schema stricter
       than the minting rule would reject an id `insert` is entitled to produce.

       v: node-identity.md §4 — the number of commits whose `Canvas-Node:`
       trailer names the node. A node is born at v="1", so v is a positive
       integer with no leading zeros and v="0" is invalid. -->
  <define name="node-attributes">
    <attribute name="id">
      <data type="token">
        <param name="pattern">[a-z][a-hj-km-np-z2-9]{3}</param>
      </data>
    </attribute>
    <attribute name="v">
      <data type="token">
        <param name="pattern">[1-9][0-9]*</param>
      </data>
    </attribute>
  </define>

  <!-- Section nests one level: two levels of <section> in total, a third is
       invalid. The depth limit is why there are two patterns for one element
       name rather than one recursive pattern. -->
  <define name="section-outer-v1">
    <element name="section">
      <ref name="node-attributes"/>
      <attribute name="title"/>
      <zeroOrMore>
        <choice>
          <ref name="section-inner-v1"/>
          <ref name="leaf-v1"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>

  <define name="section-inner-v1">
    <element name="section">
      <ref name="node-attributes"/>
      <attribute name="title"/>
      <zeroOrMore>
        <ref name="leaf-v1"/>
      </zeroOrMore>
    </element>
  </define>

  <define name="section-outer-v2">
    <element name="section">
      <ref name="node-attributes"/>
      <attribute name="title"/>
      <zeroOrMore>
        <choice>
          <ref name="section-inner-v2"/>
          <ref name="leaf-v2"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>

  <define name="section-inner-v2">
    <element name="section">
      <ref name="node-attributes"/>
      <attribute name="title"/>
      <zeroOrMore><ref name="leaf-v2"/></zeroOrMore>
    </element>
  </define>

  <!-- Every non-section node. <item>, <row> and <cell> are not here: they
       exist only inside their own parent and nowhere else. -->
  <define name="leaf-v1">
    <choice>
      <ref name="text"/>
      <ref name="list"/>
      <ref name="table"/>
      <ref name="figure-v1"/>
      <ref name="link"/>
      <ref name="question"/>
    </choice>
  </define>

  <define name="leaf-v2">
    <choice>
      <ref name="text"/>
      <ref name="list"/>
      <ref name="table"/>
      <ref name="figure-v2"/>
      <ref name="link"/>
      <ref name="question"/>
    </choice>
  </define>

  <define name="text">
    <element name="text">
      <ref name="node-attributes"/>
      <text/>
    </element>
  </define>

  <!-- Every container is zeroOrMore and never oneOrMore. One edit is one node,
       and node-identity.md §4 gives the birth of a canvas no exemption from it,
       so an empty <list>, an empty <table>, an empty <row>, an empty <section>
       and a childless <canvas/> are all legal intermediate states. -->
  <define name="list">
    <element name="list">
      <ref name="node-attributes"/>
      <zeroOrMore>
        <ref name="item"/>
      </zeroOrMore>
    </element>
  </define>

  <define name="item">
    <element name="item">
      <ref name="node-attributes"/>
      <text/>
    </element>
  </define>

  <define name="table">
    <element name="table">
      <ref name="node-attributes"/>
      <zeroOrMore>
        <ref name="row"/>
      </zeroOrMore>
    </element>
  </define>

  <define name="row">
    <element name="row">
      <ref name="node-attributes"/>
      <zeroOrMore>
        <ref name="cell"/>
      </zeroOrMore>
    </element>
  </define>

  <define name="cell">
    <element name="cell">
      <ref name="node-attributes"/>
      <text/>
    </element>
  </define>

  <!-- A schema-v1 figure remains character data, preserving every existing
       canvas without migration. Schema v2 distinguishes the repository-owned
       Canvas Diagram 1 default from explicit Mermaid source and the validated
       inline-SVG escape hatch. -->
  <define name="figure-v1">
    <element name="figure">
      <ref name="node-attributes"/>
      <text/>
    </element>
  </define>

  <define name="figure-v2">
    <element name="figure">
      <ref name="node-attributes"/>
      <optional>
        <attribute name="payload">
          <choice>
            <value>mermaid</value>
            <value>svg</value>
          </choice>
        </attribute>
      </optional>
      <text/>
    </element>
  </define>

  <!-- A pointer out. `href` is the target and the character data is the label;
       a pointer with no target is not one. -->
  <define name="link">
    <element name="link">
      <ref name="node-attributes"/>
      <attribute name="href">
        <data type="token">
          <param name="minLength">1</param>
        </data>
      </attribute>
      <text/>
    </element>
  </define>

  <!-- The one node that carries state. node-state.md rules that a canvas says
       exactly one thing about a node's state — whether a <question> has been
       answered — so that an answered question can stand in the document
       instead of being destroyed by the `replace` that answers it.

       `answered` is legal here and on no other element: <question> is the
       declared semantic exception (engineering-spec.md:108-109), and state on
       a structural element is what turns the vocabulary into a taxonomy.

       Its value set has exactly one member, `true`. `answered="false"` is
       invalid deliberately: absence means open, and two ways to spell one
       state is how a document learns to contradict itself. There is no
       `answered-by` pointer either — the reason names the node that answered
       it, as node-identity.md section 7 already rules for merge lineage. -->
  <define name="question">
    <element name="question">
      <ref name="node-attributes"/>
      <optional>
        <attribute name="answered">
          <value>true</value>
        </attribute>
      </optional>
      <text/>
    </element>
  </define>

</grammar>
