Skip to main content

PreTeXt RELAX-NG Schema

Robert A. Beezer
Department of Mathematics and Computer Science
University of Puget Sound
Tacoma, Washington, USA
beezer@pugetsound.edu
September 2, 2026
This is a literate programming version of the RELAX-NG schema for PreTeXt. As such, it is used to generate the RELAX-NG compact syntax version (pretext.rnc) and other versions are derived from the compact version with standard tools.
We intend this to be helpful for both authors and implementers. The schema is the contract between authors and implementers. If an author’s source validates against the schema, then an implementer’s conversion should render the content accurately, or warn about why it cannot. That said, it is still a work in progress:
  • New features are not added until they are reasonably stable. Validating the sample article can be a good way to see what these are.
  • Even for stable features, the schema will sometimes lag behind the code.
  • There will be other inaccuracies here, so reports or pull requests are welcome.
The RELAX-NG syntax is built on patterns, which describe how XML elements and attributes may be combined. It begins with a start pattern. Patterns separated by commas must appear in that order. Elements separated by a vertical bar represent a choice. Parentheses are used for grouping. Braces are basic syntax, reminiscent of the syntax for Java. An equals sign is assignment and |= is a continuation of an assignment. Finally, optional and/or multiple occurrences can be specified with modifiers:
?
Zero or one. Optional, at most one.
*
Zero or more. Optional, with no limit.
+
One or more. Required, with no limit.
Appendix A contains a list of all the fragments described here, in order of appearance, and may be useful if you are looking for some particular topic, element, or attribute.

Section 1 Start Elements

To support modular source files, we specify which elements can naturally be the root of a fragment file in a PreTeXt document. These include the pretext element itself, as well as most divisions. All of these are defined as elements later in the schema.

⟨1 Start elements⟩ ≡

start = PretextRoot | DocInfo | Part | Chapter | Section | Subsection | Subsubsection | Printout | Slideshow | Paragraphs | ReadingQuestions | Exercises | Subexercises |  Solutions | BookFrontMatter | ArticleFrontMatter | BookBackMatter | ArticleBackMatter | Preface | Acknowledgement | ArticleAppendix | BookAppendix | IndexDivision | References | Glossary | Figure | WebWork

Section 2 Gross Structure

A PreTeXt document is always a single pretext element below the root. There are two divisions, a docinfo, which is a database of sorts about the document, along with a sibling element that indicates the type of the document and contains all the content.

⟨2 Gross structure⟩ ≡

PretextRoot =
    element pretext {
        XMLLang?,
        DocInfo?,
        (Book | Article | Letter | Memorandum | Slideshow)
    }

Section 3 Document Types

letter and memo elements are not documented.

⟨3 Document types⟩ ≡

Article =
    element article {
        MetaDataLinedSubtitle,
        ArticleFrontMatter?,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Section | Printout),
                (Section | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?),
                ArticleBackMatter?
            )
        )
    }
Book =
    ## Here is what a book looks like.
    element book {
        MetaDataLinedSubtitle,
        BookFrontMatter?,
        (Part+ | Chapter+ | Printout+),
        BookBackMatter?
    }
Letter =
    element letter {empty}
Memorandum =
    element memo {empty}

Section 4 Slideshows

A <slideshow> is a document type of its own, a peer of <book> and <article>, whose <docinfo> and <bibinfo> are just as for the other types (its front matter has the shape of an article’s). The show is a sequence of <section>, each a titled sequence of <slide>, or is simply a sequence of <slide>. A slide holds division-style content, with two conveniences particular to the genre. A list (<ol>, <ul>, <dl>) may stand bare, outside any <p>, at the top level of a slide or subslide—a nested list still requires its paragraph. And presentation is incremental: a @pause attribute on a paragraph, list, image, or side-by-side holds that material (list items one at a time) for the next advance of the show, while a <subslide> holds a group of material, all at once, the same way. The @pause attribute appears throughout the grammar for this purpose; the validation-plus stylesheet confines it to a slideshow.

⟨4 Slideshows⟩ ≡

Slideshow =
    element slideshow {
        MetaDataLinedSubtitle,
        ArticleFrontMatter?,
        (SlideshowSection+ | Slide+)
    }
# a slideshow's "section" organizes slides, nothing more
SlideshowSection =
    element section {
        MetaDataAltTitle,
        Slide+
    }
Slide =
    element slide {
        # vertical placement of this slide's content,
        # overriding the document-wide default
        attribute valign {"top" | "middle" | "bottom"}?,
        MetaDataAltTitle,
        (BlockDivision | List | Subslide)+
    }
# a group of material appearing all at once, after a pause
Subslide =
    element subslide {
        (BlockDivision | List)+
    }

Section 5 Document Structure

A document is typically divided into sections. But we reserve the word section for one very specific type of division. To avoid confusion, we speak generically of divisions. So, for example, a section is a division of a chapter. Here we list all of the possible divisions, even if they are not available in each document type.
An appendix looks like a chapter of a book, with the option to have a notation-list as its entire contents. It is possible this is not the best structure for an article, which might best be divided by subsection.
There are several things to note (expand this): always a title, dead-end with blocks, or subdivide with optional intro and conclusion.

⟨5 Divisions⟩ ≡

Part =
    element part {
        MetaDataLinedTitle, Chapter+
    }
Chapter =
    element chapter {
        MetaDataLinedTitle,
        AuthorByline*,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Section | Printout),
                (Section | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?)
            )
        )
    }
Section =
    element section {
        MetaDataLinedTitle,
        AuthorByline*,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Subsection | Printout),
                (Subsection | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?)
            )
        )
    }
Subsection =
    element subsection {
        MetaDataAltTitle,
        AuthorByline*,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Subsubsection | Printout),
                (Subsubsection | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?)
            )
        )
    }
Subsubsection =
    element subsubsection {
        MetaDataAltTitle,
        AuthorByline*,
        Objectives?,
        (BlockDivision | Paragraphs)+,
        (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
         Solutions? & References? & Glossary?),
        Outcomes?
    }
ArticleAppendix =
    element appendix {
        MetaDataAltTitle,
        AuthorByline*,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs | NotationList)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Subsection | Printout),
                (Subsection | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?)
            )
        |
            (
                HeadNote?,
                NotationList
            )
        )
    }
BookAppendix =
    element appendix {
        MetaDataAltTitle,
        AuthorByline*,
        (
            (
                Objectives?,
                (BlockDivision | Paragraphs | NotationList)+,
                (Worksheet? & Handout? & ReadingQuestions? & Exercises? &
                 Solutions? & References? & Glossary?),
                Outcomes?
            )
        |
            (
                (Objectives? & IntroductionCompanion?),
                (Section | Printout),
                (Section | Printout | ReadingQuestions | Exercises |
                 Solutions | References | Glossary)*,
                (Outcomes? & ConclusionCompanion?)
            )
        |
            (
                HeadNote?,
                NotationList
            )
        )
    }
IndexDivision =
    element index {
        MetaDataAltTitleOptional,
        HeadNote?,
        IndexList
    }

Section 6 Lightweight Divisions

The paragraphs element, which is not to be confused with a real paragraph as implemented by the p element, is an exceptional type of division (both in design and utility). It must have a title, can appear anywhere within any of the divisions, cannot be further subdivided, and is not ever numbered. Its contents are conceptually a run of paragraphs, but as described here allow much more than that.
It is especially useful in a short document (like a class handout, letter, memorandum, or short proposal) where numbered divisions might feel like overkill.
The NoNumber variant allows for light-weight sectioning of un-numbered divisions, such as a Preface.

⟨6 Paragraphs division⟩ ≡

Paragraphs =
    element paragraphs {
        MetaDataTitle,
        Index*,
        BlockDivision+
    }
ParagraphsNoNumber =
    element paragraphs {
        MetaDataTitle,
        Index*,
        BlockStatementNoNumber+
    }

Section 7 Specialized Divisions

We add specialized divisions, which may appear within any of the above divisions. Titles will be provided as defaults.

⟨7 Specialized divisions⟩ ≡

ReadingQuestions =
    element reading-questions {
        MetaDataAltTitleOptional,
        IntroductionDivision?,
        Exercise+,
        ConclusionDivision?
    }
Exercises =
    element exercises {
        MetaDataAltTitleOptional,
        IntroductionDivision?,
        (
            (Exercise | ExerciseGroup)+ |
            Subexercises+
        ),
        ConclusionDivision?
    }
Subexercises =
    element subexercises {
        MetaDataAltTitleOptional,
        IntroductionDivision?,
        (Exercise | ExerciseGroup)+,
        ConclusionDivision?
    }
Solutions =
    element solutions {
        MetaDataAltTitleOptional,
        attribute inline {text}?,
        attribute divisional {text}?,
        attribute project {text}?,
        attribute admit {"all"|"odd"|"even"}?,
        IntroductionDivision?,
        ConclusionDivision?
    }
# A "references" is a list-like division: its one preface
# is a "headnote", and it has no "conclusion"
References =
    element references {
        MetaDataAltTitleOptional,
        HeadNote?,
        BibliographyItem+
    }
Glossary =
    element glossary {
        MetaDataAltTitleOptional,
        HeadNote?,
        GlossaryItem+
    }

Section 8 Solutions (experimental)

The solutions division can now have additional attributes: @scope, @reading, and @worksheet. We collect these three here.

⟨8 Solutions (experimental)⟩ ≡

Solutions |=
    element solutions {
        MetaDataAltTitleOptional,
        attribute inline {text}?,
        attribute divisional {text}?,
        attribute project {text}?,
        attribute worksheet {text}?,
        attribute reading {text}?,
        attribute scope {text}?,
        attribute admit {"all"|"odd"|"even"}?,
        IntroductionDivision?,
        ConclusionDivision?
    }

Section 9 Printout Divisions

A printout division (a <worksheet> or a <handout>) is a specialized division, allowing for some additional control of spacing and page delineation, to allow for workspace.
The attributes on a printout include margin information to control layout. Inside a printout we can have either a number of <page> elements that hold the content or just the content itself.
The contents of a printout can include the same blocks as a division, namely BlockDivision, plus the <paragraphs> division.
A <handout> is very similar to a <worksheet>, differing only in the absence of the course-identification attributes. Many of the blocks a printout may contain (theorems, definitions, examples, projects, exercises, tasks, proofs) accept a @workspace attribute. It is treated as a hint: respected inside a printout, and ignored elsewhere (which the validation-plus stylesheet will point out).
A structured division may hold any number of printouts, in any order, interleaved with one another and with the traditional divisions—which is how a “workbook” of printouts is authored. An unstructured division admits at most one of each, so Worksheet and Handout appear separately there rather than through Printout.

⟨9 Printout⟩ ≡

PrintoutAttributes =
    attribute margin { text }?,
    attribute top { text }?,
    attribute bottom { text }?,
    attribute right { text }?,
    attribute left { text }?

PrintoutBlock =
    BlockDivision | Paragraphs

# Page can contain a printout block or be empty
Page =
    element page {
        PrintoutBlock+ | empty
    }
# Main worksheet definition
Worksheet =
    element worksheet {
        PrintoutAttributes,
        attribute courseid {text}?,
        attribute series {text}?,
        attribute seriescode {text}?,
        MetaDataAltTitleOptional,
        (Objectives? & IntroductionDivision?),
        (Page+ | PrintoutBlock+),
        (Outcomes? & ConclusionDivision?)
    }

# Main handout definition
Handout =
    element handout {
        PrintoutAttributes,
        MetaDataAltTitleOptional,
        (Objectives? & IntroductionDivision?),
        (Page+ | PrintoutBlock+),
        (Outcomes? & ConclusionDivision?)
    }

# A printout is a worksheet or a handout
Printout =
    Worksheet | Handout

Section 10 Paragraphs

Most PreTeXt elements are about delineating structure. What you actually write happens in very few places. Principally paragraphs, but also titles, captions, index headings, and other short bursts. The shorter the burst, the more likely the text will be recycled in other places (Table of Contents, List of Figures, or Index perhaps). And the more text gets re-purposed, the more care we need to take with its contents.
Simple text is simply runs of characters, some of which is accomplished with empty elements. This is used for names of people, etc. It should not be confused with the RELAX-NG keyword text which matches runs of (Unicode) characters, with no intervening markup. So the latter is used for things like URLs, internal identifiers, configuration parameters, and so on.
Short text is used for titles, subtitles, names, index headings, and so on. It allows a variety of characters, font styling, groupings, and convenience constructions. It does not allow for references, nor anything that typographically requires more than the linearity of a sentence. In other words, no lists, no images, no tables, no displayed equations. Because of the potential for movement, we also do not include footnotes within short text.
Long text is everything that is short text, but also allows for references, both external (internet URLs) and internal (cross-references). It is used for the content of footnotes and captions. The WeBWorK variant allows for variables in inline mathematics.

⟨10 Running text⟩ ≡

TextSimple = mixed {
    Character* }
TextShort = mixed { (
    Character |
    Generator |
    Verbatim |
    Group |
    MathInline |
    Music)* }
TextLongContent =
    Character |
    Generator |
    Verbatim |
    Group |
    MathInline |
    Music |
    Reference |
    Custom |
    WWVariable
TextLong =  mixed { (TextLongContent)* }
A paragraph is a key bottleneck between structure and prose. You can use a variety of constructs in a paragraph, and you may use a paragraph in many places. So the name of the element is very simple, just a p. Now you can include footnotes, display mathematics, display verbatim text, and lists. Note that a list can only occur in a paragraph, so to make nested lists you must structure a list item of the exterior list with a paragraph to contain the interior list. A paragraph can contain some metadata, like index entries and mathematical notation. It does not have a title, nor is it ever numbered. It can be the target of a cross-reference, but only with some care.
A lined paragraph is a variant, for use when the line-by-line structure is necessary. The WeBWorK variant of a p element allows for using the var element as an answer blank or generated content, possibly inside mathematics, and possibly inside lists.
Note: A paragraph effectively could have the MetaDataTarget pattern, except that we allow index elements (<idx>) to go anywhere within the paragraph.

⟨11 Paragraphs⟩ ≡

# The union is named so extensions (e.g. the development
# schema) can widen it with a choice-combine ("|=")
TextParagraphItem =
    Character |
    Generator |
    Verbatim |
    Group |
    WWVariable |
    MathInline |
    Music |
    Reference |
    Custom |
    CodeDisplay |
    MathDisplay |
    List |
    Footnote |
    Notation |
    Index
TextParagraph = mixed { TextParagraphItem* }
Paragraph =
    element p {
        UniqueID?,
        LabelID?,
        Component?,
        attribute workspace {text}?,
        # pausing, within a slideshow
        attribute pause {"yes" | "no"}?,
        TextParagraph
    }
ParagraphLined =
    element p {
        UniqueID?,
        LabelID?,
        Component?,
        element line {TextShort}+
    }
Fundamentally PreTeXt allows for conversion to other markup languages, such as or HTML, and of course XML is a syntax for designing a markup vocabulary. As such, certain characters traditionally found on keyboards have been co-opted for special purposes. And once you actually want one of those special characters, you need an escape character to indicate a “normal” use. For these reasons, certain characters have empty elements to represent them.
Special characters for XML are the ampersand, less than, greater than, single quote and double quote: &, <, >, ', ". The ampersand is the escape character for XML. In practice, the first two characters are the most important, since processing of your XML will be confused by any attempt to use them directly. So in regular text (not mathematics, not verbatim), always use the the escaped versions: &amp;, &lt;, and perhaps &gt;.
See below for elements that can be used to form groupings with left and right delimiters. For example, a simple quotation should use a left double quote and a right double quote, and these characters should look different (so-called smart quotes). Notice that a keyboard only has a single dumb quote. If you need these characters in isolation (i.e., not in pairs), these elements are the best way to ensure you get what you want in all possible conversions. Note that left and right braces , {, } (“curly brackets”); brackets, [, ]; may be used directly. To create individual, left or right, create angle brackets us the elements here, not the keyboard characters (which are different).

⟨12 Delimiter characters⟩ ≡

Character =
    element lsq {empty} |
    element rsq {empty} |
    element rq {empty} |
    element lq {empty} |
    element ldblbracket {empty} |
    element rdblbracket {empty} |
    element langle {empty}|
    element rangle {empty}
A space is a space. But sometimes you want a space between two associated items which will not get split across two lines (e.g., Chapter 23). An element will create a non-breaking space using the right technique for the conversion at hand.
There is a variety of dashes of various lengths. Use the keyboard character for a hyphen, use an ndash to separate a range of numbers or dates, and use an mdash as punctuation within a sentence to isolate a clause. These are implemented differently for different conversions, so their use is strongly encouraged.

⟨13 Dash characters⟩ ≡

Character |=
    element nbsp {empty} |
    element ndash {empty} |
    element mdash {empty}
We define a few characters to help with simple arithmetic expressions authored within regular text. (Perhaps you are writing a novel with PreTeXt.) These are for simple uses in regular text, not for actual mathematics, which is described later. The solidus is slightly different from the slash found on a keyboard and is used for fractions and ratios. The <minus/> is for subtraction and negation, and is not a hyphen or dash. An obelus is better known as a division sign. <degree/>, <prime/>, and <dblprime/> are designed for specifying coordinates in degrees, minutes, and seconds. Use the unambiguous + keyboard character for addition.

⟨14 Arithmetic characters⟩ ≡

Character |=
    element minus {empty} |
    element times {empty} |
    element solidus {empty} |
    element obelus {empty} |
    element plusminus {empty} |
    element degree {empty} |
    element prime {empty} |
    element dblprime {empty}
The following are largely conveniences. They are typically not available on keyboards, and their implementations for various conversions can involve some subtleties. Again, their use is encouraged for the best quality output.

⟨15 Exotic characters⟩ ≡

CopyrightCharacter =
    element copyright {empty}
Character |=
    element ellipsis {empty} |
    element midpoint {empty} |
    element swungdash {empty} |
    element permille {empty} |
    element pilcrow {empty} |
    element section-mark {empty} |
    element copyleft {empty} |
    CopyrightCharacter |
    element registered {empty} |
    element trademark {empty} |
    element phonomark {empty} |
    element servicemark {empty}
Icons are available through a @name attribute, which is meant to usually be more semantic than just a description of the picture, though that may sometimes be the case. These are intended for use when describing elements of computer interfaces. Icons which are decorative should be supplied as part of styling, not as part of the source language.

⟨16 Icon characters⟩ ≡

Character |=
    element icon {
        attribute name {text}
    }
The <kbd> element will produce something akin to a calculator key or a keyboard key. It may have (simple) content, which will be reproduced as the label of the key, or it may have a @name attribute which describes a key that looks more like a graphic, such as an arrow key.

⟨17 Keyboard characters⟩ ≡

Character |=
    element kbd {
        (text | attribute name {text})
    }
We support musical notation as if they were characters: accidentals, scale degrees, notes, and chords. Implementation of these is about as complicated as inline mathematical notation, hence they have identical rules about placement.

⟨18 Music characters⟩ ≡

MusicFlat =
    element flat {empty}
MusicSharp =
    element sharp {empty}
Music =
    element doublesharp {empty} |
    MusicSharp |
    element natural {empty} |
    MusicFlat |
    element doubleflat {empty} |
    element scaledeg {"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"} |
    element timesignature {
        attribute top {text},
        attribute bottom {text}
    } |
    element n {
        attribute pc {
            "A"|"B"|"C"|"D"|"E"|"F"|"G"|"a"|"b"|"c"|"d"|"e"|"f"|"g"|
            "1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10"
        },
        attribute acc {"doublesharp"|"sharp"|"flat"|"doubleflat"}?,
        attribute octave {"1"|"2"|"3"|"4"|"5"}?
    } |
    element chord {
        attribute root {text}?,
        attribute mode {text}?,
        attribute bps {text}?,
        attribute bass {text}?,
        attribute suspended {"yes"|"no"}?,
        attribute parentheses {"yes"|"no"}?,
        element alteration {
            (TextSimple |
            MusicSharp |
            MusicFlat)*
        }*
    }

⟨19 Characters⟩ ≡

Dash characters 13⟩
Delimiter characters 12⟩
Arithmetic characters 14⟩
Exotic characters 15⟩
Icon characters 16⟩
Keyboard characters 17⟩
Music characters 18⟩
There are empty elements to generate certain items, like the date, or names of commonly referenced tools, such as PreTeXt itself. These include some common Latin abbreviations, for the purpose of handling the periods properly in conversions to .

⟨20 Text generators⟩ ≡

Generator =
    element today {empty} |
    element timeofday {empty} |
    element tex {empty} |
    element latex {empty} |
    element xetex {empty} |
    element xelatex {empty} |
    element pretext {empty} |
    element prefigure {empty} |
    element webwork {empty} |
    element ad {empty} |
    element am {empty} |
    element bc {empty} |
    element ca {empty} |
    element eg {empty} |
    element etal {empty} |
    element etc {empty} |
    element ie {empty} |
    element nb {empty} |
    element pm {empty} |
    element ps {empty} |
    element vs {empty} |
    element viz {empty}
A fillin blank is not really a character, but maybe a really long, low dash? The characters attribute controls the length. It is atomic, indivisible, and content-less, like all the other characters. fillin is also unusual due to its allowed use within mathematics.

⟨21 Fill-in blank character⟩ ≡

FillInText =
    element fillin {
        attribute characters {xsd:integer}?,
        attribute rows {xsd:integer}?,
        attribute cols {xsd:integer}?,
        empty
    }
Generator |=
    FillInText
A large class of similarly indivisible items are units on physical quantities. The <quantity> element is allowed to be empty, and the code should silently produce no output. Expressing non-emptiness here might get a bit messy, so a warning from the validation-plus stylesheet could be a good alternative.

⟨22 SI units⟩ ≡

UnitSpecification =
        attribute prefix {text}?,
        attribute base {text},
        attribute exp {xsd:integer}?
Generator |=
    element quantity {
        element mag {text}?,
        element unit {UnitSpecification}*,
        element per {UnitSpecification}*
    }
Some markup is for just ASCII characters, in other words, unadorned verbatim text.

⟨23 Verbatim text⟩ ≡

Verbatim =
    element c {text} |
    Email |
    element pf {
        attribute language {text}?,
        text
    }
Simple markup is groupings of text that gets a different typographic appearance, either through font changes or through delimiters. Examples are emphasis or paired quotations, non-examples are cross-references or footnotes.
Abbreviations are sequences of characters that shorten some longer word or words (e.g. vs. for the Latin versus), initialisms are formed from the first letters of a sequence of words (e.g. HTML), acronyms are pronounceable as words (e.g. SCUBA).

⟨24 Abbreviations⟩ ≡

Group |=
    element abbr {TextSimple} |
    element acro {TextSimple} |
    element init {TextSimple}
Notice that long text can be part of a grouping construction, and that long text can can contain a group construction. The effect is that these groupings can be nested arbitrarily deep.

⟨25 Delimited groups⟩ ≡

Group |=
    element q {TextLong} |
    element sq {TextLong} |
    element angles {TextLong} |
    element dblbrackets {TextLong}

⟨26 Highlighted groups⟩ ≡

Group |=
    element em {TextLong} |
    element term {TextLong} |
    element alert {TextLong} |
    element pubtitle {TextLong} |
    element articletitle {TextLong} |
    element foreign {
      XMLLang?,
      TextLong
    }

⟨27 Editing groups⟩ ≡

Group |=
    element delete {TextLong} |
    element insert {TextLong} |
    element stale {TextLong}
We use elements to get consistent typography when discussing PreTeXt itself. We could probably limit the content of these elements to lowercase letters and a hyphen. The definitions here will preclude any contained markup.

⟨28 XML syntax groups⟩ ≡

Group |=
    element tag {text} |
    element tage {text} |
    element attr {text}
An empty taxon will match either version.

⟨29 Taxonomic groups⟩ ≡

Group |=
    element taxon {
        attribute ncbi {xsd:integer}?,
        (
            text |
            (
                element genus {text}?,
                element species {text}?
            )
        )
    }

⟨30 Text groups⟩ ≡

Abbreviations 24⟩
Delimited groups 25⟩
Highlighted groups 26⟩
Editing groups 27⟩
XML syntax groups 28⟩
Taxonomic groups 29⟩

Section 11 Mathematics

All mathematics appears inside paragraphs, and the syntax is that of , as supported by MathJax, whose supported commands and macros are meant to be very similar to those of the AMSMath package. Note that the content is typically unstructured, excepting “fill-in-the-blank”, WeBWorK variables (see variants), and internal cross-references in multi-row display mathematics. Also, displayed mathematics, md, comes in two forms. A single-line md has no mrow children and may carry an @xml:id to be a cross-reference target; it does not use a @label. A multi-row md has mrow children and is not itself a target of cross-references, so takes neither an @xml:id nor a @label, though its rows can be targets. Either form may carry a @number attribute, defaulting to “no”; on a multi-row md it applies to each mrow that does not override it. A single-line md may carry a @tag attribute as an alternative to @number, supplying a symbolic local tag in place of a number; the two attributes are mutually exclusive. Fill-in blanks have a variant attribute @fill more suited for mathematics. An intertext may appear between two mrow, but must always be sandwiched: it cannot be the first or last child of md, and two intertext may not be adjacent.

⟨31 Mathematics⟩ ≡

FillInMath = element fillin {
                 (attribute fill{text}?|attribute characters {xsd:integer}?),
                 empty
             }
# named so extensions can widen it ("|=")
MathInlineItem = FillInMath | WWVariable
MathInline =
    element m {
        mixed {MathInlineItem*}
    }
TagSymbol = "star" | "dstar" | "tstar" |
            "dagger" | "ddagger" | "tdagger" |
            "daggerdbl" | "ddaggerdbl" | "tdaggerdbl" |
            "hash" | "dhash" | "thash" |
            "maltese" | "dmaltese" | "tmaltese"
MathRow =
    element mrow {
        MetaDataTarget,
        (
            attribute number {"yes" | "no"} |
            attribute tag { TagSymbol }
        )?,
        attribute break {"yes" | "no"}?,
        mixed {MathRowItem*}
    }
# named so extensions can widen it ("|=")
MathRowItem = Xref | FillInMath | WWVariable
MathIntertext = element intertext {TextLong}
MathDisplay =
    element md {
        # common to both forms
        Component?,
        Index*,
        (
            # single-line: text content, optional @xml:id
            # makes it a cross-reference target, no @label;
            # @number and @tag are mutually exclusive,
            # @number defaults to "no"
            (
                UniqueID?,
                (
                    attribute number {"yes" | "no"} |
                    attribute tag { TagSymbol }
                )?,
                mixed {MathRowItem*}
            ) |
            # multi-line: "mrow" content, not itself a
            # cross-reference target, so no @xml:id, no @label;
            # @number defaults to "no" and applies to each "mrow".
            # Each "intertext" must be sandwiched between "mrow":
            # it cannot lead, cannot trail, and two cannot be
            # adjacent.
            (
                attribute number {"yes" | "no"}?,
                attribute break {"yes" | "no"}?,
                attribute alignment {"align" | "gather" | "alignat"}?,
                attribute alignat-columns {text}?,
                MathRow, (MathRow | (MathIntertext, MathRow))*
            )
        )
    }

Section 12 Blocks

A text block is very similar to a paragraph. It can be an actual paragraph, a sequence of paragraphs enclosed as a block quote (with attribution, perhaps), or a large chunk of unformatted text presented typically in a monospace font. Certain “atomic” objects, such as an <image> may be placed as peers of paragraph-like objects.
A statement block is used in statements. What are those? Theorems have statements, exercises have statements, questions have statements. Some of these blocks with statements also have peers of statements that are proofs, hints, answers, and solutions. In statements, and their peers, we include text blocks, captioned items, asides, side-by-side layouts, and Sage computations, but exclude many of the numbered and titled division blocks. A slight extension is a solution block, which is everything that can go in a <statement>, plus one or more <proof>, only as part of a <hint>, <answer>, or <solution>.
A division block includes text blocks, statement blocks, plus topical chunks of text that can have numbered headings or numbered captions, with optional titles, and are set apart slightly from the surrounding narrative. These are placed mostly as children of divisions, and so one cannot contain another. They certainly contain paragraphs, and all that goes into them, such as mathematics (inline and display) and figures (and other captioned items). The sidebyside element can be used to illustrate a division block with a variety of images and displayed text in flexible layouts.
A <fragment> is used for literate programming, and is numbered, so it is allowed places where other numbered items go.
Other division blocks include poem, aside, and assemblage. These are never numbered, but can have titles. The list-of mechanism is a convenience device to automatically create lists of contents, and so we leave surrounding divisional structure to the author. A sidebyside, and its cousin, sbsgroup, are strictly layout devices. The sage element is unique for its possibilities in certain electronic formats.

⟨32 Blocks⟩ ≡

BlockText =
    Paragraph | BlockQuote | Preformatted |
    Image | Video | Audio | Program | Console | Tabular
BlockStatementNoNumber =
    BlockText | Aside |
    SideBySideNoNumber | SideBySideGroupNoNumber
BlockStatement =
    BlockText |
    Figure | Aside |
    SideBySide | SideBySideGroup | Sage
BlockSolution =
    BlockStatement | Proof
BlockDivision =
    BlockStatement |
    Remark | Computation | Theorem | Proof | Definition |
    Axiom | Example | Exercise | Project | OpenProblem |
    Poem | Assemblage | ListGenerator | Fragment
Blocks are often structured, in a light way. Hints, answers, and solutions adorn exercises, examples, and projects. A simple introduction or conclusion is sometimes useful. A prelude or postlude are authored inside a block and so are associated with it. But they are presented before and after the block visually.
When a block is structured to allow some of the ancillary parts, a statement element is used to structure the main part. Hints, answers, and solutions can be the target of cross-references, but do not get author-supplied titles.

⟨33 Common components of blocks⟩ ≡

Prelude =
    element prelude {BlockText+}
Postlude =
    element postlude {BlockText+}
Statement =
    element statement {
        BlockStatement+
    }
Hint =
    element hint {
        MetaDataTitleOptional,
        BlockSolution+
    }
Answer =
    element answer {
        MetaDataTitleOptional,
        BlockSolution+
    }
Solution =
    element solution {
        MetaDataTitleOptional,
        BlockSolution+
    }

Section 13 Introductions, Conclusions, and Headnotes

The introduction and conclusion containers can be used in a variety of other structured elements. They come in four levels, according to what they can contain, and are meant to be consonant with their surroundings. As children of a division, they may carry a title, which in turn allows them to be cross-referenced by that text.
A <headnote> is like an <introduction>, but does not have a symmetric concluding element, and is meant for divisions whose content is essentially one list: a <glossary>, a <references>, an <index>, or an <appendix> that is exactly a notation list. It is a light prefatory note, only necessary when something about the list is unusual and deserves a reader’s advance attention.

⟨34 Introductions, conclusions, headnotes⟩ ≡

IntroductionText =
    element introduction {BlockText+}
ConclusionText =
    element conclusion {BlockText+}
IntroductionStatementNoNumber =
    element introduction {BlockStatementNoNumber+}
ConclusionStatementNoNumber =
    element conclusion {BlockStatementNoNumber+}
IntroductionStatement =
    element introduction {BlockStatement+}
ConclusionStatement =
    element conclusion {BlockStatement+}
IntroductionCompanion =
    element introduction {
        MetaDataNoTitle,
        BlockDivision+
    }
ConclusionCompanion =
    element conclusion {
        MetaDataNoTitle,
        BlockDivision+
    }
IntroductionDivision =
    element introduction {
        MetaDataTitleOptional,
        BlockDivision+
    }
ConclusionDivision =
    element conclusion {
        MetaDataTitleOptional?,
        BlockDivision+
    }
HeadNote =
    element headnote {BlockStatementNoNumber+}

Section 14 References

There are a variety of referencing mechanisms, external references, internal cross-references, index entries, and specialized support for a table of mathematical notation.

⟨35 Cross-references⟩ ≡

XrefTextStyle =
    "local" | "global" | "hybrid" | "type-local" | "type-global" |
    "type-hybrid" | "phrase-global" | "phrase-hybrid" |
    "type-local-title" | "type-global-title" |
    "title" | "custom"
Reference = Url | Xref | DataUrl
Custom =
    element custom {
        attribute ref {text},
        empty
    }
DataUrl =
    element dataurl {
        (attribute href {text} | attribute source {text}),
        attribute visual {text}?,
        TextShort?
    }
Url =
    element url {
        attribute href {text},
        attribute visual {text}?,
        TextShort?
    }
Xref =
    element xref {
            (
                attribute ref {text} |
                (attribute first {text}, attribute last {text}) |
                attribute provisional {text}
            ),
            attribute text { XrefTextStyle }?,
            attribute detail {text}?,
            TextShort
    }
NotationDescription =
    element description {
        TextShort
    }
Notation =
    element notation {
        element usage {MathInline},
        NotationDescription
    }
Footnotes are especially dangerous. They should contain quite a bit of content, and should be targets of cross-references. So the content is not as expansive as a regular paragraph, which is possibly too restrictive.

⟨36 Footnotes⟩ ≡

Footnote =
    element fn {
        MetaDataTarget,
        TextLong
    }
Index entries have two forms, simple and structured. The start and finish attributes are meant to use xml:id to create an index range that crosses XML boundaries. (Replace principal tags with idx/h/h.)
The actual index is generated within the <index> division via the index-list element.
Note that we might point to another index entry as part of a “see also” mechanism.

⟨37 Index entries⟩ ≡

IdxHeading =
    element h {
        attribute sortby {text}?,
        TextShort
    }
Index =
    element idx {
        MetaDataTarget,
        attribute sortby {text}?,
        attribute start {text}?,
        attribute finish {text}?,
        (
            TextShort
        |
            (
            IdxHeading,
            IdxHeading?,
            IdxHeading?,
            (element see {TextShort} | element seealso {TextShort})?
            )
        )
    }
IndexList = element index-list {empty}

Section 15 Objectives

A division may lead (first) with an optional list of objectives for the division and may be followed by a (final) optional list of outcomes. The element names are only chosen to reflect a pre- and post- behavior and so could be used for objectives, outcomes, and standards in a variety of ways.

⟨38 Objectives and outcomes⟩ ≡

Objectives =
    element objectives {
        MetaDataTitleOptional,
        IntroductionText?,
        List,
        ConclusionText?
    }
Outcomes =
    element outcomes {
        MetaDataTitleOptional,
        IntroductionText?,
        List,
        ConclusionText?
    }

Section 16 Block Quotes

These are a run of paragraphs, but may optionally have an attribution.

⟨39 Block quotes⟩ ≡

BlockQuote =
    element blockquote {
        MetaDataTitleOptional,
        Paragraph+,
        Attribution?
    }
SimpleLine =
    element line {TextSimple}
ShortLine =
    element line {TextShort}
LongLine =
    element line {TextLong}

Section 17 Verbatim Text

Large blocks of verbatim material, rather than just little bits in a sentence. A code display, cd, is an analog of a math display, and meant to be used within a paragraph, either as a single line of text, or optionally structured as several lines by using code lines, cline. pre is a block, which preserves line breaks and sanitizes whitespace to the left. It can be optionally structured as code lines. It should be thought of as a monospace analogue of a “regular” paragraph, minus indentation and automatic line-breaking. A <program> maybe structured with a <code> element, or simply text.

⟨40 Verbatim displays⟩ ≡

CodeLine =
    element cline {text}
CodeDisplay =
    element cd {
        attribute latexsep {text}?,
        # "all" renders every space as a visible glyph; default "none"
        attribute showspaces {"all" | "none"}?,
        (text | CodeLine+)
    }
Preformatted =
    element pre {
        text | CodeLine+
    }
ConsoleOutput =
    element output {text}
ConsoleInput =
    element input {
        attribute prompt {text}?,
        attribute continuation {text}?,
        text
    }
Console =
    element console {
        Component?,
        attribute prompt {text}?,
        attribute continuation {text}?,
        attribute width {text}?,
        attribute margins {text}?,
        (
            ConsoleInput,
            ConsoleOutput?
        )+
    }
ProgramPreamble =
    element preamble {
        attribute visible {"yes"|"no"}?,
        text
    }
ProgramCode = element code {text}
ProgramPostamble =
    element postamble {
        attribute visible {"yes"|"no"}?,
        text
    }
ProgramTests =
    element tests {
        attribute visible {"yes"|"no"}?,
        (
            text
        |
            element iotest {
                element input {text},
                element output {text}
            }+
        )
    }
ProgramStdin =
    element stdin {
        text
    }
# A checkpoint's prompt and feedback are short bursts of
# marked-up text, not sequences of blocks
CodelensCheckpoint =
    element checkpoint {
        attribute line {text},
        (
            attribute answer {text}
        |
            attribute answer-variable {text}
        ),
        element prompt {TextLong},
        element feedback {TextLong}?
    }
Program =
    element program {
        Component?,
        LabelID?,
        UniqueID?,
        attribute width {text}?,
        attribute margins {text}?,
        attribute autorun {"yes"|"no"}?,
        attribute chatcodes {"yes"|"no"}?,
        attribute codelens {"yes"|"no"}?,
        attribute codetailor {"all"|"incorrect"}?,
        attribute codetailor-fallback {text}?,
        # the codelens trace step (1-based) to open on
        attribute starting-step {xsd:integer}?,
        attribute compiler-args {text}?,
        attribute extra-compiler-args {text}?,
        attribute database {text}?,
        attribute datafile {text}?,
        attribute add-files {text}?,
        attribute compile-also {text}?,
        attribute download {"yes"|"no"}?,
        attribute hidecode {"yes"|"no"}?,
        attribute highlight-lines {text}?,
        attribute include {text}?,
        attribute filename {text}?,
        attribute interactive {"codelens"|"activecode"|"no"}?,
        attribute interpreter-args {text}?,
        attribute language {text}?,
        attribute line-numbers {"yes"|"no"}?,
        attribute linker-args {text}?,
        attribute timelimit {text}?,
        attribute pck {text}?,
        attribute scene {text}?,
        (
            text
        |
            (
                ProgramPreamble?,
                ProgramCode,
                ProgramPostamble?,
                ProgramTests?,
                ProgramStdin?,
                CodelensCheckpoint*
            )
        )
    }

Section 18 Lists

Are complicated. Maybe we need a special type of paragraph which does not allow nesting a description list down into some other list?
As a container, the lists themselves get no metadata. But the numbered or titled list items do get metadata. To point to an entire list, make it a named list and point to that.

⟨41 Lists⟩ ≡

ListItem = element li {
    (
        (MetaDataTarget, TextParagraph)
    |
        (MetaDataTitleOptional, BlockStatement+)
    )
}
DefinitionListItem = element li {
    MetaDataTitle,
    BlockStatement+
}
List =
    element ol {
        Component?,
        attribute cols {"2"|"3"|"4"|"5"|"6"}?,
        attribute marker {text}?,
        # pausing, within a slideshow: items appear one at a time
        attribute pause {"yes" | "no"}?,
        ListItem+
    } |
    element ul {
        Component?,
        attribute cols {"2"|"3"|"4"|"5"|"6"}?,
        attribute marker {"disc" | "circle" | "square" | ""}?,
        attribute pause {"yes" | "no"}?,
        ListItem+
    } |
    element dl {
        Component?,
        attribute width {"narrow" | "medium" | "wide"}?,
        attribute pause {"yes" | "no"}?,
        DefinitionListItem+
    }

Section 19 Definitions

Definitions are special, there is nothing else quite like them. A statement, no proof, and also a natural place for notation entries.

⟨42 Definitions⟩ ≡

DefinitionLike =
    attribute workspace {text}?,
    MetaDataTitleOptional,
    Notation*,
    Statement
Definition =
    element definition {DefinitionLike}

Section 20 Theorems, And Other Results

Theorems, corollaries, lemmas — they all have statements, and should have proof(s). Otherwise they are all the same. A proof may be divided with cases, in no particular rigid way, just as a marker of any number of different, non-overlapping portions of a proof. Titles can be used to describe each case, or implication arrows may be used (typically with a proof of an equivalence). A proof is also allowed to stand on its own as a block, independent of a structure like a theorem or algorithm.

⟨43 Theorems, and similar⟩ ≡

Case =
    element case {
       MetaDataTitleOptional,
       attribute direction {text}?,
       BlockStatement+
       }
Proof =
    element proof {
        attribute ref {text}?,
        attribute text {XrefTextStyle}?,
        attribute workspace {text}?,
        MetaDataTitleOptional,
        (BlockStatement | Case)+
    }
TheoremLike =
    attribute workspace {text}?,
    MetaDataTitleCreatorOptional,
    (BlockStatement+ | (Statement, Proof*))
Theorem =
    element theorem {TheoremLike} |
    element lemma {TheoremLike} |
    element corollary {TheoremLike} |
    element claim {TheoremLike} |
    element proposition {TheoremLike} |
    element algorithm {TheoremLike} |
    element fact {TheoremLike} |
    element identity {TheoremLike}

Section 21 Proof-like (experimental)

We extend the types of elements that are types of proofs, as well as create a ProofLike named pattern for what can go in them.

⟨44 Proofs, and similar⟩ ≡

ProofLike =
    MetaDataTitleOptional,
    (BlockStatement | Case)+
Proof |=
    element proof {ProofLike} |
    element argument {ProofLike} |
    element justification {ProofLike} |
    element reasoning {ProofLike} |
    element explanation {ProofLike}

Section 22 Axioms and Other Mathematical Statements

Mathematical statements that do not have proofs (in other words, no proof is known, or a proof is not appropriate).

⟨45 Axioms, and similar⟩ ≡

AxiomLike =
    MetaDataTitleCreatorOptional,
    Statement
Axiom =
    element axiom {AxiomLike} |
    element principle {AxiomLike} |
    element conjecture {AxiomLike} |
    element heuristic {AxiomLike} |
    element hypothesis {AxiomLike} |
    element assumption {AxiomLike}

Section 23 Projects and Activities

A favorite of Inquiry-Based Learning textbooks. Numbered independently. Possibly structured with task. Three different ways to structure this, we combine the second two so that the derived XML Schema (XSD) version is less-confusing to certain tools (e.g. the Red Hat XML schema validator used within VS Code).

⟨46 Projects, and similar⟩ ≡

ProjectLike =
    attribute workspace {text}?,
    MetaDataTitleOptional,
    (
        (BlockStatement+) |
        (
           Prelude?,
           (
              (Statement, Hint*, Answer*, Solution*) |
              (Statement, Program, Hint*, Answer*, Solution*) |
              (IntroductionStatement?, Task+, ConclusionStatement?) |
              (IntroductionText?, WebWork, ConclusionText?)
           ),
           Postlude?
        )
    )
Project =
    element activity {ProjectLike} |
    element investigation {ProjectLike} |
    element exploration {ProjectLike} |
    element project {ProjectLike}
Task =
    element task {
        MetaDataTitleOptional,
        attribute workspace {text}?,
        (
            BlockStatement+ |
            (Statement, Hint*, Answer*, Solution*) |
            (IntroductionStatement?, Task+, ConclusionStatement?)
        )
    }

Section 24 Open Problems

Open problems collect unsolved questions, perhaps the subject of current research. They are numbered like a project—sharing the overall blocks counter, or running on their own counter when a publisher requests a @distinct number. Possibly structured with task. The appendages are discussion-like (<discussion>, <context>, and similar), rather than the hints, answers, and solutions of an exercise or a project.

⟨47 Open problems, and similar⟩ ≡

OpenProblemLike =
    MetaDataTitleOptional,
    Prelude?,
    (
        (Statement, Discussion*) |
        (IntroductionStatement?, OpenProblemTask+, ConclusionStatement?)
    ),
    Postlude?
OpenProblem =
    element openproblem {OpenProblemLike} |
    element openquestion {OpenProblemLike} |
    element openconjecture {OpenProblemLike}
OpenProblemTask =
    element task {
        MetaDataTitleOptional,
        (
            (Statement, Discussion*) |
            (IntroductionStatement?, OpenProblemTask+, ConclusionStatement?)
        )
    }
Discussion =
    element context {DiscussionLike} |
    element discussion {DiscussionLike} |
    element opinion {DiscussionLike} |
    element status {DiscussionLike} |
    element suggestion {DiscussionLike}
DiscussionLike =
    MetaDataTitleOptional,
    BlockStatement+

Section 25 Remarks and Other Comments

Really simple blocks, they do not have much structure, and so are just runs of paragraphs, though <figure>, <table>, <listing>, and <list> may be included.

⟨48 Remarks, and similar⟩ ≡

RemarkLike =
    MetaDataTitleOptional,
    BlockStatement+
Remark =
    element remark {RemarkLike} |
    element convention {RemarkLike} |
    element note {RemarkLike} |
    element observation {RemarkLike} |
    element warning {RemarkLike} |
    element insight {RemarkLike}

Section 26 Computations and Technology

Somewhat simple blocks, they do not have much structure, but can hold more than a Remark.

⟨49 Computation, and similar⟩ ≡

ComputationLike =
    MetaDataTitleOptional,
    BlockStatement+
Computation =
    element computation {ComputationLike} |
    element technology {ComputationLike} |
    element data {ComputationLike}

Section 27 Asides

An aside is a deviation from the narrative, and might physically move in the presentation (say, to a margin, or to a knowl). biographical and historical may be further developed.

⟨50 Asides, and similar⟩ ≡

AsideLike =
    MetaDataTitleOptional,
    BlockText+
Aside =
    element aside {AsideLike} |
    element biographical {AsideLike} |
    element historical {AsideLike}

Section 28 Assemblages

Since an assemblage is meant to accumulate significant content (as a review or summary, or for initial presentation) lists are allowed here, an exception to their restriction to paragraphs. We are also mildly restrictive about what can be content here—in particular blocks are excluded, despite not strictly being blocks themselves.

⟨51 Assemblages⟩ ≡

Assemblage =
    element assemblage {
        MetaDataTitleOptional,
        (BlockText | SideBySideNoNumber | SideBySideGroupNoNumber)+
    }

Section 29 Figures, Tables, Listings and Named Lists

These are containers that all carry titles (mandatory and optional), captions for two, and numbers. They need to be filled with other (atomic) items, which we generally call planar due to their two-dimensional and rigid characteristics. These have also called captioned items in the code, even if not all allow a caption. The option for a lanscape orientation is only relevant for print.
A figure may also hold a single paragraph. A captioned, numbered display of mathematics (a multiplication table as an md array, say), or the transcription of an inscription, is honestly a figure, and one paragraph is a display, not a container.
A tabular never takes a number directly. Bare, it may sit between paragraphs as unnumbered, uncaptioned content; the table wrapper is the one route to a number and a title. In particular, a tabular is not a child of a figure.

⟨52 Captioned and titled displays⟩ ≡

Caption =
    element caption {Component?, TextLong}
Landscape =
    attribute landscape {"yes" | "no"}
Figure =
    element figure {
        MetaDataCaption,
        Landscape?,
        (
            Image |
            Video |
            Audio |
            SideBySide |
            SideBySideGroup |
            MuseScore |
            Paragraph
        )
    } |
    FigureTable | FigureListing | FigureList
# the one route to a numbered, titled "tabular"
FigureTable =
    element table {
        MetaDataAltTitle,
        Landscape?,
        Tabular
    }
FigureListing =
    element listing {
        MetaDataAltTitle,
        Landscape?,
        (
            Program |
            Console
        )
    }
FigureList =
    element list {
        MetaDataAltTitle,
        Landscape?,
        IntroductionText?,
        List,
        ConclusionText?
    }
The guts of a table go in a tabular element.

⟨53 Tabular display⟩ ≡

BorderThickness = "none" | "minor" | "medium" | "major"
BorderTop =
    attribute top {BorderThickness}
BorderBottom =
    attribute bottom {BorderThickness}
BorderLeft =
    attribute left {BorderThickness}
BorderRight =
    attribute right {BorderThickness}
AlignmentHorizontal =
    attribute halign {"left" | "center" | "right" | "justify"}
AlignmentVertical =
    attribute valign {"top" | "middle" | "bottom"}

# A table cell takes "tn" table notes, not "fn" footnotes.  A
# table note is lettered a, b, c per-tabular in reading order
# and is placed below the table, disjoint from the numbering
# of true footnotes.  Notes to tables: CMoS 18th ed.,
# 3.77-3.81; the specific (lettered) notes here are 3.80.
# A "tn" is not a cross-reference target, so takes no
# identification attributes.
TableNote = element tn {TextLong}
TableCellText = mixed { (TextLongContent | TableNote)* }
TableCell =
    element cell {
        AlignmentHorizontal?,
        BorderBottom?,
        BorderRight?,
        attribute colspan {text}?,
        (
            TableCellText |
            LongLine+ |
            # a paragraph's content is the same everywhere, so a
            # cell of paragraphs anchors a table note after any
            # of them; assembly relocates the note's mark to the
            # end of the paragraph it follows
            (Paragraph, TableNote*)+
        )
    }
TableRow =
    element row {
        attribute header {"yes" | "no" | "vertical"}?,
        AlignmentHorizontal?,
        AlignmentVertical?,
        BorderBottom?,
        BorderLeft?,
        TableCell+
    }
TableColumn =
    element col {
        AlignmentHorizontal?,
        BorderTop?,
        BorderRight?,
        attribute width {text}?
    }
Tabular =
    element tabular {
        Component?,
        attribute width {text}?,
        attribute margins {text}?,
        attribute row-headers {"yes" | "no"}?,
        attribute break {"yes" | "no"}?,
        AlignmentHorizontal?,
        AlignmentVertical?,
        BorderTop?,
        BorderBottom?,
        BorderLeft?,
        BorderRight?,
        TableColumn*,
        TableRow+
    }

Section 30 Side-By-Side Layout

Page width or screen width, both are at a premium. Height goes on forever (barring physical page breaks) and we have many devices for demarcating that flow. But sometimes you need to organize items horizontally, i.e. side-by-side. We place the components of a sidebyside into generic regions of specified width called panels. There are at least two panels—a lone component has nothing to sit beside.
This is a pure layout device. So you cannot title it, nor caption it. It does not admit a xml:id attribute, since you cannot make it the target of a cross-reference. Nor can you reference it from the index (but you can point to its surroundings from the index).
Because of its utility, it can go anywhere a block can go (i.e., as a child of a division) and it can go many other places as a sibling of a paragraph (such as to illustrate an example).
Note that widths give on a sidebyside override any width given to the components of the panels.
A <stack> allows non-captioned, non-titled elements to accumulate vertically in a single panel. It is a basic container.
An <exercise> may be a panel, in support of compact layout of a <worksheet> or a <handout>, where workspace is relevant. The schema does not restrict the arrangement to those locations, but the validation-plus stylesheet warns about others.
An <audio>, <video>, or <interactive> may never be a panel, nor part of a <stack>. Static conversions represent each of these elements as a sidebyside holding a preview image, a QR code, and links, and one sidebyside must never nest inside another. The schema precludes these elements as children, and a <figure> occupying a panel is restricted the same way (no layout elements, no media elements); the validation-plus stylesheet checks arbitrary depth.
A group of side-by-sides is designed to stack vertically with common controls on widths, etc. Its implementation is entirely experimental right now, even if we are relatively confident of the markup.

⟨54 Side-by-side layouts⟩ ≡

Stack =
    element stack {
        (
            Tabular |
            Image |
            Program |
            Console |
            Paragraph |
            Preformatted |
            List
        )+
    }
SidebySideAttributes =
    Component?,
    attribute margins {text}?,
    # pausing, within a slideshow
    attribute pause {"yes" | "no"}?,
    attribute landscape {"yes" | "no"}?,
    (attribute width {text} | attribute widths {text})?,
    (
        AlignmentVertical |
        attribute valigns { list { ("top" | "middle" | "bottom")+ } }
    )?
# A "figure" occupying a panel is restricted: the layout
# elements ("sidebyside", "sbsgroup") may not nest, and a
# static conversion realizes the media elements ("video",
# "audio") as a manufactured "sidebyside", so none of these
# may appear.  The schema stops these immediate routes; the
# validation-plus stylesheet polices arbitrary depth.
FigurePanel =
    element figure {
        MetaDataCaption,
        Landscape?,
        (
            Image |
            MuseScore |
            Paragraph
        )
    } |
    FigureTable | FigureListing | FigureList
# The panels themselves; a narrative "sidebyside" holds at
# least two, else nothing is side by side
Panel =
    FigurePanel |
    Poem |
    Tabular |
    Image |
    Program |
    Console |
    Paragraph |
    Preformatted |
    List |
    Stack |
    Exercise
PanelNoNumber =
    Poem |
    Tabular |
    Image |
    Program |
    Console |
    Paragraph |
    Preformatted |
    List |
    Stack
SideBySide =
    element sidebyside {
        SidebySideAttributes,
        Panel, Panel+
    }
SideBySideNoNumber =
    element sidebyside {
        SidebySideAttributes,
        PanelNoNumber, PanelNoNumber+
    }
# a group of one "sidebyside" is no group: the layout
# attributes might as well sit on the lone "sidebyside"
SideBySideGroup =
    element sbsgroup {
        UniqueID?,
        SidebySideAttributes,
        SideBySide, SideBySide+
    }
SideBySideGroupNoNumber =
    element sbsgroup {
        UniqueID?,
        SidebySideAttributes,
        SideBySideNoNumber, SideBySideNoNumber+
    }

Section 31 Images and Graphics

Raster, and described by languages, plus 100% duplicates. The WeBWorK variant is quite different.
Note: the ImageCode pattern allows an @xml:id attribute since it is used to construct a filename.
A pf:prefigure image holds a PreTeXt-namespaced label attribute followed by a single PreFigure diagram. Rather than validate the diagram with a permissive wildcard, we defer to PreFigure’s own schema by way of an externalRef to the adapter described below. This isolates the two grammars, so the many pattern names they share (such as Label, Image, and Caption) do not collide.

⟨55 Images⟩ ≡

Image = ImageRaster | ImageCode | ImagePG
# An image drawn by the PG code of a WeBWorK problem, named
# by @pg-name.  Like "var", the form is admitted schema-wide
# (it can appear deep within list items, say) but is
# exclusive to a "webwork" problem; the validation-plus
# stylesheet enforces the location.
ImagePG =
    element image {
        attribute pg-name {text},
        attribute width {text}?,
        attribute margins {text}?,
        empty
    }
ImageDescription = element description {(Paragraph | Tabular)+}
# A "var" may ride along, per the schema-wide "var" pattern:
# the validation-plus stylesheet confines it to a WeBWorK problem
ImageShortDescription = element shortdescription {(text | WWVariable)*}
ImageShortDescriptionCode = element shortdescription {
    (text | WWVariable)+
}
ImageRaster =
    element image {
        UniqueID?,
        Component?,
        attribute width {text}?,
        attribute margins {text}?,
        # pausing, within a slideshow
        attribute pause {"yes" | "no"}?,
        attribute rotate {text}?,
        attribute archive {text}?,
        attribute source {text},
        (
          attribute decorative {"yes"} |
          (
            attribute decorative {"no"}?,
            (
              ImageShortDescription? &
              ImageDescription?
            )
          )
        )
    }
CodeLatexImage =
    element latex-image {
        LabelID?,
        Component?,
        text
    }
ImageCode =
    element image {
        UniqueID?,
        Component?,
        attribute width {text}?,
        attribute margins {text}?,
        # pausing, within a slideshow
        attribute pause {"yes" | "no"}?,
        attribute archive {text}?,
        (
          attribute decorative {"yes"} |
          (
            attribute decorative {"no"}?,
            (
              ImageShortDescriptionCode ? &
              ImageDescription? &
              (
                CodeLatexImage |
                element asymptote {
                  LabelID?,
                  Component?,
                  text
                } |
                element sageplot {
                    LabelID?,
                    Component?,
                    attribute variant {'2d'|'3d'}?,
                    attribute aspect {text}?,
                    text
                } |
                element mermaid {
                    LabelID?,
                    Component?,
                    text
                } |
                element pf:prefigure {
                    attribute label {text}?,
                    external "pf-adapter.rnc"
                }
              )
            )
          )
        )
    }
WWLatexImage = element latex-image {
    text
  }
ImageWW =
    element image {
        attribute pg-name {text}?,
        attribute width {text}?,
        (
          attribute decorative {"yes"} |
          (
            attribute decorative {"no"}?,
            (
              ImageShortDescriptionCode? &
              ImageDescription? &
              WWLatexImage?
            )
          )
        )
    }

Section 32 Sage Code

Sage is integral.

⟨56 Sage code⟩ ≡

SageOutput = element output {text}
SageInput = element input {text}
Sage = element sage {
    Component?,
    attribute doctest {text}?,
    attribute tolerance {text}?,
    attribute auto-evaluate {'no'|'yes'}?,
    attribute language {text}?,
    attribute type {text}?,
    (SageInput, SageOutput?)?
}

Section 33 Legacy Interactive Elements

Some specific interactive goodies. These are being phased-out in favor of a more general <interactive> element.

⟨57 Interactives⟩ ≡

MuseScore =
    element score {
        attribute musescoreuser {text},
        attribute musescore {text}
    }

Section 34 Interactive Elements (experimental)

A general <interactive> element.

⟨58 Interactives⟩ ≡

Interactive =
    element interactive {
        UniqueID?,
        LabelID?,
        Component?,
        attribute aspect { text }?,
        attribute width { text }?,
        attribute platform { text }?,
        attribute preview { text }?,
        attribute iframe { text }?,
        attribute source { text }?,
        attribute version { text }?,
        attribute circuitjs { text }?,
        attribute calcplot3d { text }?,
        attribute geogebra { text }?,
        attribute desmos { text }?,
        # currently only consumed by the CalcPlot3D flavor;
        # widen this enum if another flavor adopts "@variant"
        attribute variant { "application" | "controls" | "minimal" }?,
        attribute dark-mode-enabled { text }?,
        attribute resize-behavior { text }?,
        attribute css { text }?,
        attribute design-width { text }?,
        attribute reset-icon { text }?,
        (
          ImageShortDescription? &
          ImageDescription? &
          (
            Slate |
            SideBySideInteractive |
            SideBySideGroupInteractive
          )* &
          element script { attribute type { text }?, text }* &
          element source { text }? &
          element instructions { mixed { MetaDataTitleOptional, BlockText+ } }? &
          element static { Image }?
        )

    }

# A "stack" occupying a panel of an interactive's
# "sidebyside" may also stack slates; a narrative
# "stack" may not
StackInteractive =
    element stack {
        (
            Tabular |
            Image |
            Program |
            Console |
            Paragraph |
            Preformatted |
            List |
            Slate
        )+
    }

# Within an "interactive" or a "slate", a "sidebyside" is
# scaffolding: it arranges slates, or narrows a lone panel
# to a chosen width, so the two-panel minimum of a
# narrative "sidebyside" is not imposed here
SideBySideInteractive =
    element sidebyside {
        SidebySideAttributes,
        ((PanelNoNumber | StackInteractive)+ | Slate+)
    }
SideBySideGroupInteractive =
    element sbsgroup {
        UniqueID?,
        SidebySideAttributes,
        SideBySideInteractive, SideBySideInteractive+
    }

Slate =
    element slate {
        UniqueID?,
        LabelID?,
        Component?,
        (
          JessieCodeAtt |
          (
            attribute surface { text },
            (
              attribute source { text } |
              attribute material { text }
            )?,
            attribute aspect { text }?,
            attribute perspective { text }?,
            (
              Paragraph |
              Tabular |
              SideBySideInteractive |
              AnyXHTML |
              text*
            )*
          )
        )
    }

  JessieCodeAtt =
    attribute surface {"jessiecode"},
    attribute axis {"true" | "false"}?,
    attribute grid {"true" | "false"}?,
    (
      attribute source {text} |
      text*
    )

  # A slate with surface="html" embeds an arbitrary XHTML
  # fragment, and the XHTML namespace is the author's
  # declaration that the content is foreign: anything
  # un-namespaced still gets the schema's full scrutiny.
  # Declare it once, as a prefix on the "slate" itself;
  # the conversion keeps only local names, so the output
  # page carries plain HTML tags, free of namespaces.
  AnyXHTML =
    element xhtml:* {
      (attribute * { text })*,
      mixed { AnyXHTML* }
    }

# add Interactives where used
BlockStatement |= Interactive

Figure |= element figure { MetaDataCaption, Interactive }

Exercises |= element exercises {
    MetaDataAltTitleOptional,
    IntroductionDivision?,
    (
    (Exercise | ExerciseGroup)+ |
    Subexercises+ | Interactive
    ),
    ConclusionDivision?
}

# Hosted on Runestone, an "exercises" division can be a
# timed exam (@time-limit in minutes; results, feedback,
# the visible timer, and pausing each default on), or can
# collect group-work submissions (@groupwork, group size
# capped by @groupsize); a "worksheet" can do the latter
Exercises |= element exercises {
    (
        (attribute time-limit {xsd:integer},
         attribute results {"yes"|"no"}?,
         attribute timer {"yes"|"no"}?,
         attribute feedback {"yes"|"no"}?,
         attribute pause {"yes"|"no"}?)
    |
        (attribute groupwork {"yes"|"no"},
         attribute groupsize {xsd:integer}?)
    ),
    MetaDataAltTitleOptional,
    IntroductionDivision?,
    (Exercise | ExerciseGroup)+,
    ConclusionDivision?
}
Worksheet |= element worksheet {
    attribute groupwork {"yes"|"no"},
    attribute groupsize {xsd:integer}?,
    PrintoutAttributes,
    attribute courseid {text}?,
    attribute series {text}?,
    attribute seriescode {text}?,
    MetaDataAltTitleOptional,
    (Objectives? & IntroductionDivision?),
    (Page+ | PrintoutBlock+),
    (Outcomes? & ConclusionDivision?)
}

Section 35 Audio and Video

Video and audio are both supported. For video, the xml:id is not used as a target, but rather as a name for a static preview image that is auto-generated by the pretext script thumbnail file, hence optional. preview maybe be one of two reserved switches, or the filename of a static preview image. Audio is simpler: it has no preview image, aspect ratio, or playback mode, and points to either a local @source file or a network @href.
Note: the Video pattern allows an @xml:id attribute since it is used to construct a filename for preview images (“poster”), especially when scraped.

⟨59 Video and audio⟩ ≡

Video =
    element video {
        UniqueID?,
        LabelID?,
        Component?,
        attribute width {text}?,
        attribute margins {text}?,
        attribute aspect {text}?,
        attribute start {xsd:integer}?,
        attribute end {xsd:integer}?,
        attribute play-at {"embed" | "popout" | "select"}?,
        attribute preview {"default" | "generic" | text}?,
        (AttributesSourceFile | AttributesNetwork | AttributesYouTube |
         AttributesYouTubePlaylist | AttributesVimeo),
        # names the video, as in a Runestone manifest
        Title?,
        Track*
    }
Audio =
    element audio {
        UniqueID?,
        LabelID?,
        Component?,
        attribute width {text}?,
        attribute margins {text}?,
        attribute start {xsd:integer}?,
        attribute end {xsd:integer}?,
        (AttributesSourceFile | AttributesNetwork)
    }
Track =
    element track {
        attribute kind {text},
        attribute listing {text}?,
        XMLLang?,
        attribute source {text},
        attribute default {"yes"}?
    }
AttributesSourceFile =
    attribute source {text}
AttributesNetwork =
    attribute href {text}
AttributesYouTube =
    attribute youtube {text}
AttributesYouTubePlaylist =
    attribute youtubeplaylist {text}
AttributesVimeo =
    attribute vimeo {text}

Section 36 Poetry

Poems!

⟨60 Poems⟩ ≡

AlignmentPoem = attribute halign {"left" | "center" | "right"}
PoemAuthor =
    element author {
        AlignmentPoem?,
        TextShort
    }
Poem =
    element poem {
        MetaDataTitleOptional,
        AlignmentPoem?,
        PoemAuthor?,
        (PoemLine+ | Stanza+)
    }
Stanza =
    element stanza {
        MetaDataTitleOptional,
        PoemLine+
    }
PoemLine =
    element line {
        attribute indent {xsd:integer}?,
        TextShort
    }

Section 37 Exercises

Inline, divisional, and WeBWorK. Exercises use task to structure parts.

⟨61 Exercises⟩ ≡

ExerciseBody =
    BlockStatement+
StatementExercise =
    element statement { ExerciseBody }
Exercise =
    element exercise {
        MetaDataTitleOptional,
        attribute number {text}?,
        attribute workspace {text}?,
        (
        ExerciseBody |
        (StatementExercise, Hint*, Answer*, Solution*) |
        (IntroductionStatement?, Task+, ConclusionStatement?) |
        (IntroductionText?, WebWork, ConclusionText?)
        )
    }
ExerciseGroup =
    element exercisegroup {
        MetaDataTitleOptional,
        attribute cols {"2"|"3"|"4"|"5"|"6"}?,
        IntroductionStatementNoNumber,
        Exercise+,
        ConclusionStatementNoNumber?
    }

Section 38 Exercises (experimental)

We can have exercises that are interactive, such as true/false, multiple choices, Parson’s problems, etc.

⟨62 Exercises (experimental)⟩ ≡

MyOpenMath =
    MetaDataTitleOptional,
    IntroductionText?,
    element myopenmath {
        attribute problem {text},
        attribute params {text}?
    },
    ConclusionText?
TrueFalse =
    MetaDataTitleOptional,
    attribute number {text}?,
    element statement {
        attribute correct {"yes"|"no"},
        Paragraph
    },
    Feedback?, Hint*, Answer*, Solution*
MultipleChoice =
    MetaDataTitleOptional,
    attribute number {text}?,
    StatementExercise,
    element choices {
        attribute randomize {"yes"|"no"}?,
        attribute multiple-correct {"yes"|"no"}?,
        attribute cols {xsd:integer}?,
        Choice+
    },
    Hint*, Answer*, Solution*
Choice =
    element choice {
        attribute correct {"yes"|"no"}?,
        ((mixed {BlockText*})
        | (StatementExercise, Feedback?))
    }
# A horizontal SQL Parsons may name a grading database and
# carry raw unit tests; a horizontal layout may permit the
# reuse of blocks, where a source block carries an @xml:id
# (and no @order) and each use references it with @ref
Parsons =
        MetaDataTitleOptional,
        attribute number {text}?,
        attribute language {text}?,
        attribute adaptive {"yes"|"no"}?,
        attribute indentation {text}?,
        attribute database {text}?,
        StatementExercise,
        element blocks {
            attribute layout {"horizontal"}?,
            attribute randomize {"yes"|"no"}?,
            attribute reuse {"yes"|"no"}?,
            attribute numbered {"left"|"right"|"no"}?,
            ( (BlockOrdered | BlockSource)+ | BlockUnordered+ )
        },
        element tests {text}?,
        Hint*, Answer*, Solution*,
        (ParsonsProgramPreamble?, Program, ParsonsProgramPostamble?)?
# Fixed code surrounding a runnable Parsons solution.  These
# patterns stand apart so the element names can be revisited
# on their own: the plain "preamble" and "postamble" within
# a "program" are close cousins, and some later decision may
# unify them.
ParsonsProgramPreamble =
    element program-preamble {
        attribute indent {text}?,
        text
    }
ParsonsProgramPostamble =
    element program-postamble {
        attribute indent {text}?,
        text
    }
# Horizontal blocks hold short inline bursts (code via "c",
# mathematics, text); vertical blocks hold code lines or
# natural-language blocks
BlockBody =
    ((
        attribute correct {"yes"|"no"}?,
        mixed {BlockText?, CodeLine?, Verbatim?, MathInline?}+
    ) |
    (
        element choice {
            attribute correct {"yes"|"no"}?,
            mixed {BlockText?, CodeLine?, Verbatim?, MathInline?}+
        }+
    ))
BlockOrdered =
    element block {
        attribute order {xsd:integer},
        attribute name {text}?,
        attribute depends {text}?,
        attribute ref {text}?,
        BlockBody
    }
BlockSource =
    element block {
        UniqueID,
        attribute name {text}?,
        BlockBody
    }
BlockUnordered =
    element block {
        attribute name {text}?,
        attribute depends {text}?,
        BlockBody
    }
Matching =
    MetaDataTitleOptional,
    attribute number {text}?,
    StatementExercise,
    Feedback?,
    Matches,
    Hint*, Answer*, Solution*
Matches = (
    element cardsort {
        Match+
    } |
    element matching {
        element premise {
            attribute ref {text}?,
            TextLong
        }+,
        element response {
            UniqueID,
            attribute order {xsd:integer}?,
            TextLong
        }+
    })
# Premises and the response arrive in any order; a match
# with premises and no response, or a response and no
# premises, is a distractor
Match =
    element match {
        element premise {
            attribute order {xsd:integer}?,
            TextLong
        }*
        &
        element response {
            attribute order {xsd:integer}?,
            TextLong
        }?
    }
FreeResponse =
    MetaDataTitleOptional,
    attribute number {text}?,
    attribute attachment {"yes" | "no"}?,
    (
    (ExerciseBody, Response?) |
    (StatementExercise, Response?, Hint*, Answer*, Solution*) |
    (IntroductionStatement?, Task+, ConclusionStatement?)
    )
Response =
    element response {empty}


# Selectable areas
Area =
    element area {
        attribute correct {"yes"|"no"}?,
        TextLong
    }
TextLongAreas =  mixed { (
      Area |
      Character |
      Generator |
      Verbatim |
      GroupAreas |
      MathInline |
      Music |
      Reference |
      WWVariable)* }
GroupAreas |=
    element q {TextLongAreas} |
    element sq {TextLongAreas}
TextParagraphAreas = mixed { (
  Character |
  Generator |
  Verbatim |
  Group |
  WWVariable |
  MathInline |
  Music |
  Reference |
  CodeDisplay |
  MathDisplay |
  List |
  Footnote |
  Notation |
  Index |
  Area |
  GroupAreas)* }
ParagraphAreas =
    element p {
        UniqueID?,
        LabelID?,
        Component?,
        TextParagraphAreas
    }
# The areas come in three forms: paragraphs of prose, lines
# of code (@language names the language), or a tabular with
# clickable cells.  Trailing components in any order.
Areas =
    MetaDataTitleOptional,
    attribute number {text}?,
    StatementExercise,
    element areas {
      attribute language {text}?,
      (ParagraphAreas | ClineAreas | Tabular)+
    },
    (Feedback? & Hint* & Answer* & Solution*)
ClineAreas =
    element cline { mixed {Area*} }
# within the development schema, any tabular cell may hold
# a clickable area
TableCellText |= mixed { (TextLongContent | TableNote | Area)* }

# General feedback element; a short burst of marked-up text
# is as welcome as a sequence of blocks
Feedback =
    element feedback {
        MetaDataTitleOptional,
        (BlockSolution+ | TextLong)
    }
# A dynamic exercise's automatic solution can be declined
Solution |=
    element solution {
        attribute include-automatic {"yes"|"no"},
        MetaDataTitleOptional,
        BlockSolution+
    }
# Fill-in-the-blank exercises: a setup and an evaluation
# accompany the statement.  Content model per the FITB
# maintainer (Brian Walton), issue #2980.
# "setup" and "evaluation" are data rather than content:
# neither is rendered where it sits, so their placement
# carries no meaning and is left to the author.  They may
# precede the "statement" (natural when the statement
# refers to objects the setup defines) or trail the
# solutions.  The remaining order is the usual one for an
# exercise, with "statement" ahead of any "hint", "answer"
# or "solution"; nesting that sequence inside the
# interleave is what preserves it.
FillInBlank =
    MetaDataTitleOptional,
    attribute number {text}?,
    (
    Setup? &
    Evaluation &
    ( StatementExercise, (Hint* & Answer* & Solution*) )
    )
# Problem setup: imported javascript libraries, a configuration JSON for passing data,
# named objects defined sequentially (later ones
# may reference earlier ones), then optional raw-Javascript hooks.
# External libraries can be passed as relative path to asset (using @source),
# or as an https:// url to the library (using @url).
# A missing @seed makes any randomization unpredictable, so the
# FITB machinery warns; but setup has non-randomizing uses too.
Setup =
    element setup {
        attribute seed {text}?,
        element jsimports {
            element jslibrary {
                (attribute source {text} | attribute url {text})
            }+
        }?,
        element config-json {text}?,
        DEObject*,
        element setupScript {text}?,
        element postRenderScript {text}?
    }
DEObject =
    element de-object {
        attribute name {text},
        attribute context {"number" | "formula"},
        (DERandom | DENumber | DEExpression | DEEvaluate)
    }
# A random number; @distribution may grow beyond "discrete".
# @by defaults to 1; @nonzero omitted means "no".
DERandom =
    element de-random {
        attribute distribution {"discrete"},
        attribute min {text},
        attribute max {text},
        attribute by {text}?,
        attribute nonzero {"yes"}?,
        empty
    }
# Text parses to a formula representing a constant (no free
# variables).  N.B. @reduce here follows sample usage; the
# maintainer's description lists it only on de-expression
# and de-evaluate.
DENumber =
    element de-number {
        attribute reduce {"yes"}?,
        text
    }
# A formula; the content model follows @mode.  Outside a
# "de-object" there is no enclosing context, so the
# expression may declare its own with @context.
DEExpression =
    element de-expression {
        attribute reduce {"yes"}?,
        attribute context {"number" | "formula"}?,
        (
            (attribute mode {"formula"}?, text) |
            (attribute mode {"substitution"}, DEFormula, DEVariableValued) |
            (attribute mode {"derivative"}, DEFormula, DEVariableEmpty)
        )
    }
# The evaluated (numeric) version of a formula
DEEvaluate =
    element de-evaluate {
        attribute reduce {"yes"}?,
        DEFormula,
        DEVariableValued
    }
# A reference to some other formula, from which a new one is
# derived.  (de-random and de-number are technically possible
# here but have no practical use, so are not admitted.)
DEFormula =
    element formula {
        DEExpression | DEEvaluate | Eval
    }
# The variable of a substitution or evaluation carries its
# value; plain-text values happen to work as raw Javascript
# but are deliberately NOT valid
DEVariableValued =
    element variable {
        attribute name {text},
        (Eval | DENumber | DEExpression)
    }
# The variable of a differentiation is name-only
DEVariableEmpty =
    element variable {
        attribute name {text},
        empty
    }
# An inline reference to a named setup object: within
# mathematics it renders as the TeX form of the object,
# elsewhere as its plain string form.  Admitted anywhere
# text or mathematics occurs (statements, solutions,
# feedback of dynamic exercises are where it is useful).
Eval =
    element eval {
        attribute obj {text},
        empty
    }
# eval joins the inline unions (development schema only)
TextParagraphItem |= Eval
MathInlineItem |= Eval
MathRowItem |= Eval
Evaluation =
    element evaluation {
        attribute answers-coupled {"yes"|"no"}?,
        Evaluate+
    }
# An "evaluate" with @all="yes" tests correctness of ALL the
# blanks (optional; meaningful with several blanks).  The
# maintainer describes an optional trailing "feedback" here;
# observed usage places feedback inside "test", so both are
# admitted.
# A bare "evaluate", with no "test" at all, is deliberately
# allowed: when the paired "fillin" carries an @answer or an
# @ansobj, the correct test is implicit and gets synthesized
# (the assembly "exercise" pass, or the Runestone conversion
# for @ansobj).  The element itself is still required, one
# per blank, since the pairing is positional; the
# validation-plus stylesheet checks that count, which no
# grammar can express.
Evaluate =
    element evaluate {
        attribute name {text}?,
        attribute submit {text}?,
        attribute all {"yes"|"no"}?,
        FITBTest*,
        Feedback?
    }
# At most one test should carry @correct="yes", determining
# correctness of a submission (unclosed by this schema); with
# none, the provided answer or answer object is the default
# comparison.  A test holds a single numcmp or strcmp, OR any
# number of jscmp/mathcmp/logic combined with an implied AND.
# A bare "eval" or a bare constructor within a test is
# shorthand for a comparison with the submission; the
# assembly substitutes the full form
FITBTest =
    element test {
        attribute correct {"yes"|"no"}?,
        (FITBNumcmp | FITBStrcmp |
         (FITBJscmp | FITBMathcmp | FITBLogic |
          Eval | DENumber | DEExpression | DEEvaluate)+),
        Feedback?
    }
FITBComparison =
    FITBNumcmp | FITBStrcmp | FITBJscmp | FITBMathcmp | FITBLogic
FITBNumcmp =
    element numcmp {
        attribute use-answer {"yes"|"no"}?,
        attribute value {text}?,
        attribute tolerance {text}?,
        attribute min {text}?,
        attribute max {text}?,
        attribute object {text}?
    }
FITBStrcmp =
    element strcmp {
        attribute use-answer {"yes"|"no"}?,
        attribute literal {"yes"}?,
        attribute case {"insensitive"}?,
        attribute strip {"no"}?,
        text
    }
FITBJscmp =
    element jscmp {
        text
    }
# Compare the submission to the provided answer
# (@use-answer), to a named setup object (@obj), or to one
# constructed child object; two children compare with each
# other instead.  Children use the same building blocks as
# setup does.
FITBMathcmp =
    element mathcmp {
        (
            attribute use-answer {"yes"} |
            attribute obj {text} |
            ((DENumber | DEExpression | DEEvaluate | Eval),
             (DENumber | DEExpression | DEEvaluate | Eval)?)
        )?
    }
FITBLogic =
    element logic {
        attribute op {"and"|"or"|"not"},
        (FITBComparison)+
    }
# Extended fillin with interactive attributes (extends the inline
# text fill-in blank; the bare "FillIn" name was orphaned)
FillInText |=
    element fillin {
        attribute characters {xsd:integer}?,
        attribute cols {xsd:integer}?,
        attribute rows {xsd:integer}?,
        attribute width {text}?,
        attribute answer {text}?,
        attribute parser {text}?,
        attribute mode {"string"|"number"|"math"}?,
        attribute name {text}?,
        attribute ansobj {text}?,
        attribute fill {text}?
    }

# Datafile element for student data
Datafile =
    element datafile {
        UniqueID?,
        LabelID?,
        Component?,
        attribute filename {text},
        attribute rows {xsd:integer}?,
        attribute cols {xsd:integer}?,
        attribute editable {"yes"|"no"}?,
        # a non-editable datafile can start hidden
        attribute hide {"yes"|"no"}?,
        (
            element pre {
                attribute source {text}?,
                text
            } |
            Image
        )
    }
BlockStatement |= Datafile

# Query (poll/survey) element; results are private to the
# instructor unless @visibility says otherwise
Query =
    element query {
        LabelID?,
        attribute scale {xsd:integer}?,
        attribute visibility {"instructor"|"all"}?,
        StatementExercise,
        element choices {
            Choice+
        }?
    }
BlockStatement |= Query

# Coding exercise: an interactive program (typically
# activecode) whose "tests" hold unit tests
CodingExercise =
    MetaDataTitleOptional,
    attribute number {text}?,
    StatementExercise,
    Program,
    Hint*, Answer*, Solution*
# LEGACY fill-in ("fillin-basic" to the assembly classifier):
# empty "var" marks in the statement pair positionally with
# the "var" elements of the "setup", each holding conditions
# tried in order (a numeric interval via @number/@tolerance,
# or a @string regular expression), with optional feedback.
# The machinery is fully implemented, interactive and
# static, so the form is admitted here.  It will NEVER move
# to the stable schema: its only future is retirement in
# favor of the dynamic fill-in above.
FillInLegacy =
    MetaDataTitleOptional,
    attribute number {text}?,
    StatementExercise,
    element setup {
        element var {
            attribute case {"insensitive"}?,
            element condition {
                (
                    (attribute number {text},
                     attribute tolerance {text}?)
                |
                    attribute string {text}
                ),
                Feedback?
            }+
        }+
    },
    Hint*, Answer*, Solution*
# the empty mark in a statement, sized by @width
VarMark =
    element var {
        attribute width {xsd:integer}?,
        empty
    }
TextParagraphItem |= VarMark
# A select exercise poses one of several existing questions
# (variants), located by their @label values; @grade says
# how the graded variant is chosen: at random, by A/B
# experiment group, always the first listed (the others
# remain viewable), or any one the reader selects
Select =
    MetaDataTitleOptional,
    element select {
        attribute grade {"random"|"ab-experiment"|"first"|"any"}?,
        attribute questions {text},
        attribute experiment-name {text}?,
        empty
    }
# Include all exercise types in exercise and activity
Exercise |=
    element exercise {
        attribute workspace {text}?,
        (
        MyOpenMath |
        TrueFalse |
        MultipleChoice |
        Parsons |
        Matching |
        FreeResponse |
        FillInBlank |
        FillInLegacy |
        Areas |
        CodingExercise |
        Select
        )
    }
# STRICTLY EXPERIMENTAL, matching the sample's own alert:
# a dual exercise pairs a "dynamic" realization (typically
# an embedded interactive) with a "static" stand-in, and
# the assembly chooses one per output
Exercise |=
    element exercise {
        MetaDataTitleOptional,
        element dynamic {
            StatementExercise
        },
        element static {
            (
                (StatementExercise, Hint*, Answer*, Solution*)
            |
                (IntroductionStatement?, Task+, ConclusionStatement?)
            )
        }
    }
# STACK assessment exercise type
Exercise |=
    element exercise {
        MetaDataTitleOptional,
        IntroductionStatement?,
        element stack {
            LabelID?,
            attribute source {text}?
        },
        ConclusionStatement?
    }
ProjectLike |=
    MyOpenMath |
    TrueFalse |
    MultipleChoice |
    Parsons |
    Matching |
    FreeResponse |
    FillInBlank |
    Areas
# The assembly classifies "task" alongside "exercise" and the
# project-like blocks, and every interactive form is
# implemented within a "task", so the same union applies
Task |=
    element task {
        attribute workspace {text}?,
        (
        TrueFalse |
        MultipleChoice |
        Parsons |
        Matching |
        FreeResponse |
        FillInBlank |
        FillInLegacy |
        Areas |
        CodingExercise
        )
    }

Section 39 Bibliography

This support is in-progress and may be expanded. The CSL-style entry, in particular, covers a curated scholarly subset of the CSL-JSON model for now, and may grow beyond this initial curation.

⟨63 Bibliography⟩ ≡

TextBib = mixed { (Character | MathInline)* }
BibliographyItem =
    element biblio {
        MetaDataTarget,
        ((
            attribute type {"raw"},
            (TextLong |
            Ibid |
            BibTitle |
            BibYear |
            BibJournal |
            BibNumber |
            BibVolume |
            BibNote)*
        ) |
        (
            attribute type {"bibtex"},
            (BibTitle |
            BibAuthor |
            BibEditor |
            BibYear |
            BibJournal |
            BibNumber |
            BibVolume |
            BibSeries |
            BibPublisher |
            BibPages |
            BibPubnote |
            BibNote)*
        ) |
        (
            # A CSL-style entry, modeled on the CSL-JSON data
            # format.  Structured fields, all optional, in a fixed
            # canonical order.  With no CSL style active the fields
            # are rendered in this (document) order; with a style,
            # citeproc-py supplies the order from the CSL-JSON.
            attribute type {CSLType},
            CSLAuthor?,
            CSLEditor?,
            CSLTranslator?,
            BibTitle?,
            BibContainerTitle?,
            BibCollectionTitle?,
            BibGenre?,
            Edition?,
            BibVolume?,
            BibNumber?,
            BibIssue?,
            BibIssued?,
            BibAccessed?,
            BibPage?,
            BibPageFirst?,
            BibNumberOfPages?,
            BibPublisher?,
            BibPublisherPlace?,
            BibDOI?,
            BibISBN?,
            BibISSN?,
            BibURL?))
    }
# CSL-JSON item types, a curated scholarly subset
CSLType =
    "article" |
    "article-journal" |
    "article-magazine" |
    "article-newspaper" |
    "book" |
    "chapter" |
    "collection" |
    "dataset" |
    "document" |
    "entry" |
    "entry-dictionary" |
    "entry-encyclopedia" |
    "manuscript" |
    "paper-conference" |
    "patent" |
    "report" |
    "review" |
    "software" |
    "speech" |
    "thesis" |
    "webpage"
Ibid = element ibid {empty}
BibYear = element year {text}
BibJournal = element journal { TextBib }
BibNumber = element number {text}
BibVolume = element volume {text}
BibTitle = element title {TextLong}
BibNote = element note {UniqueID?, Paragraph+}
BibAuthor = element author {text}
BibEditor = element editor {text}
BibSeries = element series {text}
BibPublisher = element publisher {text}
# A BibTeX "pubnote" holds free-form publication information
BibPubnote = element pubnote {TextBib}
BibPages = element pages {
    (
        attribute start {text},
        attribute end {text},
        empty
    ) |
    (
        text
    )
    }
# CSL contributors carry structured "name" elements only.  The
# element names "author"/"editor" are deliberately shared with the
# plain-text BibTeX patterns above; the gating "@type" attribute
# keeps the two content models apart (a RELAX NG co-occurrence
# constraint).
CSLAuthor = element author {CSLName+}
CSLEditor = element editor {CSLName+}
CSLTranslator = element translator {CSLName+}
# A CSL "name": recognized parts in any order, or a literal string
CSLName =
    element name {
        (BibFamily |
        BibGiven |
        BibDroppingParticle |
        BibNonDroppingParticle |
        BibSuffix |
        BibStaticOrdering |
        BibLiteral)*
    }
BibFamily = element family {text}
BibGiven = element given {text}
BibDroppingParticle = element dropping-particle {text}
BibNonDroppingParticle = element non-dropping-particle {text}
BibSuffix = element suffix {text}
BibStaticOrdering = element static-ordering {text}
BibLiteral = element literal {text}
# CSL standard (string) fields
BibContainerTitle = element container-title {TextBib}
BibCollectionTitle = element collection-title {TextBib}
# A CSL "genre" gives a sub-type, e.g. "Ph.D. thesis"
BibGenre = element genre {text}
BibIssue = element issue {text}
BibPage = element page {text}
BibPageFirst = element page-first {text}
BibNumberOfPages = element number-of-pages {text}
BibPublisherPlace = element publisher-place {TextBib}
BibDOI = element DOI {text}
BibISBN = element ISBN {text}
BibISSN = element ISSN {text}
BibURL = element URL {text}
# CSL date variables; one "date" per point, two for a range
BibIssued = element issued {BibDate+}
BibAccessed = element accessed {BibDate+}
BibDate =
    element date {
        attribute year {text}?,
        attribute month {text}?,
        attribute day {text}?,
        empty
    }

Section 40 Glossary

A <glossary> is primarly built up as a sequence of “glossary items,”, using the <gi> element, by analogy with list items.

⟨64 Glossary⟩ ≡

GlossaryItem =
    element gi {
        MetaDataTitle,
        BlockStatementNoNumber+
    }

Section 41 Examples and Questions

Expository, but with solutions, etc. (Borrows from exercises and projects.)

⟨65 Examples, and similar⟩ ≡

ExampleLike =
    attribute workspace {text}?,
    MetaDataTitleOptional,
    (
        (BlockStatement)+ |
        (Statement, Hint*, Answer*, Solution*) |
        (IntroductionStatement?, Task+, ConclusionStatement?)
    )
Example =
    element example {ExampleLike} |
    element question {ExampleLike} |
    element problem {ExampleLike}

Section 42 WeBWorK Exercises

Modified versions of various aspects to allow authoring WeBWorK exercises.
Notes:
  • Statements, hints and solutions do not require at least one paragraph, so may be just a table or figure (say).
  • Are static and set elements mutually exclusive?
  • Can the usage part of the var element be split across math and paragraphs?

⟨66 WeBWorK⟩ ≡

WebWork = (WebWorkAuthored | WebWorkSource)
WebWorkSource =
    element webwork {
        attribute source {text}?,
        attribute seed {xsd:integer}?
    }
WWDescription =
    element description {
        (
            TextSimple |
            SimpleLine+
        )
    }
# A "webwork" with @copy is an empty, lightweight reference
# to another authored problem (perhaps reseeded); the
# assembly resolves it to a duplicate
WebWorkAuthored =
    element webwork {
        (
            UniqueID?,
            LabelID?,
            Component?,
            attribute seed {xsd:integer}?,
            WWDescription?,
            WWMacros?,
            element pg-code {text}?,
            (
                (StatementExerciseWW, HintWW?, SolutionWW?)
            |
                (IntroductionText?, TaskWW+, ConclusionText?)
            )
        ) |
        (
            UniqueID?,
            LabelID?,
            Component?,
            attribute copy {text},
            attribute seed {xsd:integer}?,
            empty
        ) |
        text
    }
BlockStatementWW =
            Paragraph |
            Preformatted |
            Tabular |
            ImageWW
StatementExerciseWW =
    element statement {
        (BlockStatementWW|WWInstruction)+
    }
TaskWW =
    element task {
        MetaDataTitleOptional,
        (
            (StatementExerciseWW, HintWW?, SolutionWW?) |
            (IntroductionText?, TaskWW+, ConclusionText?)
        )
    }
WWMacros =
    element pg-macros {
        element macro-file {text}+
    }
WWVariable =
    ## The WeBWorK "var" element appears in the RELAX-NG schema as a child of many elements, but almost always as a descendant of a "p" element or a "cell" element.  As an element that is only relevant for a WeBWorK problem, occurrences of "var" must be within a "webwork" element.  The validation-plus stylesheet will check on these two situations.
    element var {
        (attribute name {text},
        attribute evaluator {text}?,
        attribute width {text}?,
        # the variable holds PGML markup to be processed
        attribute data {"pgml"}?,
        attribute category {
                            "angle" | "decimal" | "exponent"
                          | "formula" | "fraction" | "inequality"
                          | "integer" | "interval" | "logarithm"
                          | "limit" | "number" | "point"
                          | "syntax" | "quantity" | "vector"
                          }?,
        attribute form {"popup"|"buttons"|"checkboxes"|"none"|"array"}?) |
        (attribute form {"essay"},
        attribute width {text}?)
    }
WWInstruction =
    element instruction {TextShort}
HintWW =
    element hint {
        (BlockStatementWW)+
    }
SolutionWW =
    element solution {
        (BlockStatementWW)+
    }

Section 43 Literate Programming

Literate programming is a technique for documenting programs, with code fragments rearranged to create a syntactically correct program. A root fragment is indicated by @filename which could have an @xml:id, otherwise the @xml:id is required.

⟨67 Literate programming⟩ ≡

Fragment =
    element fragment {
        (
            attribute xml:id {text}
        |
            (
                attribute filename {text},
                attribute xml:id {text}?
             )
        ),
        Title,
        (
            element code {text} |
            element fragref {
                attribute ref {text}
            }
        )+
    }

Section 44 Frequently Used

Frequently used items, with no natural place to associate them.

⟨68 Frequent constructions⟩ ≡

Attribution 69⟩
Metadata 70⟩
Used on the end of prefaces to “sign” them, and on block quotes.

⟨69 Attribution⟩ ≡

Attribution =
    element attribution {
        Component?,
        (TextLong | LongLine+)
    }
There is a handful of elements which describe an item, but do not necessarily get processed as content. Titles are an obvious example, and index entries are another. Here we isolate a few common patterns to use for consistency throughout.
Notes:
  • Language tags go on the root element to affect variants of names of objects, like theorems.
  • The xinlude mechanism may pass language tags down through the root element of included files to make them universally available.
  • The component attribute allows versions to be controlled by a publisher file.
  • These are not unordered specifications since they contain several attributes, and we enforce a title, subtitle, <shorttitle>, <plaintitle>, creator, caption, idx order.
  • MetaDataTarget is for items that are targets of cross-references, but without even optional titles. Since they will be knowled, they can appear in an index. But without the potential to be titled, we do not set them up as possible root elements of a file to xinclude.
  • MetaDataTitle has a required <title>.
  • MetaDataAltTitle has a required <title>, and allows optional <shorttitle> and <plaintitle>.
  • A <plaintitle> means no markup whatsoever in the content, this is what “plain” means.
  • MetaDataLinedTitle and MetaDataLinedSubtitle are variants of the AltTitle version for use on larger divisions with <line> elements used to suggest line breaks in titles; the latter also allows an optional <subtitle>.
  • MetaDataCaption implicitly has an optional title.
  • Titles may contain external references (url) or internal cross-references (xref), but implementers need not make them active (i.e., they maybe text only), since titles are prone to migrating to other locations.

⟨70 Metadata⟩ ≡

UniqueID =
    attribute xml:id {text}
LabelID =
    attribute label {text}
Component =
    attribute component {text}
Title =
    element title {Component?, TextLong}
LinedTitle =
    element title {Component?, LongLine+}
Subtitle =
    element subtitle {Component?, TextLong}
LinedSubtitle =
    element subtitle {Component?, LongLine+}
ShortTitle =
    element shorttitle {Component?, TextShort}
PlainTitle =
    element plaintitle {Component?, text}
Creator =
    element creator {Component?, TextShort}
XMLLang = attribute xml:lang {text}
MetaDataTarget =
    UniqueID?,
    LabelID?,
    Component?,
    Index*
MetaDataTitle =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Title,
    Index*
MetaDataAltTitle =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Title,
    ShortTitle?,
    PlainTitle?,
    Index*
MetaDataLinedTitle =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    (Title | LinedTitle),
    ShortTitle?,
    PlainTitle?,
    Index*
MetaDataLinedSubtitle =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    (Title | LinedTitle),
    (Subtitle | LinedSubtitle)?,
    ShortTitle?,
    PlainTitle?,
    Index*
MetaDataNoTitle =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Index*
MetaDataTitleOptional =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Title?,
    Index*
MetaDataAltTitleOptional =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    (Title, ShortTitle?, PlainTitle?)?,
    Index*
MetaDataTitleCreatorOptional =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Title?,
    Creator?,
    Index*
MetaDataCaption =
    UniqueID?,
    LabelID?,
    Component?,
    XMLLang?,
    Title?,
    Caption,
    Index*

Section 45 Miscellaneous

Provisional items, with uncertain futures.

⟨71 Miscellaneous or uncertain⟩ ≡



Section 46 Organizational Devices

A list generator is a convenient device. It can create appendices, or smaller table-of-contents at the start of divisions.
Notation can be automatically generated. We restrict its locations to appendices.

⟨72 List generator⟩ ≡

ListGenerator =
    element list-of {
        attribute elements {text},
        attribute scope {text}?,
        attribute divisions {text}?,
        attribute empty {"yes" | "no"}?
    }
NotationList =
    element notation-list {empty}

Section 47 Front Matter

Articles and books have material at the start, which gets organized in interesting ways. minilicense is very restrictive, shortlicense allows references (e.g. URLs). bibinfo is like a very small database whose content migrates to a titlepage and colophon if titlepage/titlepage-items or colophon/colophon-items are present. For HTML, titlepage means the page for the frontmatter , and for these items migrate to the half-title and title pages. Since it generally makes no sense as the target of a cross-reference, titlepage does not allow an @xml:id attribute.
Some notes about new additions to bibinfo.
  • version might be useful for books (could be draft or preview for example) but is mainly for articles, where it’s content could be “authors submitted version” or “revision 2.” A book might have both a version and an edition, but editions don’t really apply to articles.
  • JATS has permissions but we have copyright. It seems like these have the same content.
  • We introduce keywords (equivalent to JATS kwd-group) which contains a sequence of keyword elements. There can be multiple keywords elements, as this can be used for subject classifications, or author keywords, etc. Attributes on the keywords element can be used to distinguish these: @authority can be “msc” for Math Subject Classification, in which cas the <keywords> can also have @variant to specify the year version of the MSC. The default @authority is “author.”
  • To describe the financial support for the work, we use support. This can go in bibinfo directly or tied directly to an author or authors.
  • Slideshows already have a <event>, which we make official here and use to describe a conference for which the work was prepared. JATS uses conference for this, and structures it with elements such as date, accronym, and similar, but we do not go that far.

⟨73 Front matter⟩ ≡

ArticleFrontMatter =
    element frontmatter {
        MetaDataTitleOptional,
        Bibinfo,
        TitlePage,
        Abstract?
    }
BookFrontMatter = element frontmatter {
        MetaDataTitleOptional,
        Bibinfo,
        TitlePage,
        ColophonFront?,
        Biography*,
        Dedication?,
        Acknowledgement?,
        Preface*
    }
Bibinfo =
    element bibinfo {
        (
            (Author+, Editor*)
            |
            (Editor+)
        )?,
        ((Credit | ColophonCredit)* &
        Date? &
        Edition? &
        Event? &
        Keywords* &
        Support? &
        Website? &
        Copyright?)
    }
TitlePage =
    element titlepage {
        element titlepage-items {empty}
    }
Email = element email {text}
PersonName = element personname {TextSimple}
Affiliation =
    element affiliation {
        Position? &
        Department? &
        Institution? &
        Location?
    }
Position = element position {TextSimple | ShortLine+}
Department = element department {TextSimple | ShortLine+}
Institution = element institution {TextSimple | ShortLine+}
Support = element support {TextParagraph}
Location = element location {TextSimple | ShortLine+}
Keywords = element keywords {
    attribute authority {text}?,
    attribute variant {text}?,
    Title?,
    Keyword+
}
Keyword = element keyword {
    attribute primary {"yes"|"no"}?,
    TextSimple
}
Edition = element edition {text}
# "event" names the occasion of a presentation: the slideshow
# conversions (Beamer, reveal.js) read it, and an article's
# title page carries it as a line following the title.
Event = element event {
    TextLong
}
Author =
    element author {
        attribute corresponding {"yes" | "no"}?,
        attribute xml:id {text}?,
        PersonName,
        (
            (Position? & Department? & Institution? & Location?) |
            Affiliation+
        )?,
        Email?,
        Biography?,
        Support?
    }
Editor =
    element editor {
        PersonName,
        (
            (Position? & Department? & Institution? & Location?) |
            Affiliation+
        )?,
        Email?
    }
Credit =
    element credit {
        Title,
        Author+
    }
Date =
    element date {
        mixed {(Character | Generator)*}
    }
Abstract =
    element abstract {
        MetaDataTarget,
        BlockText+
    }
ColophonCredit = element credit {
        element role {TextShort},
        element entity {TextLong}
    }
ShortLicense = element shortlicense {TextLong}
Website = element website {Url}
Copyright =
    element copyright {
        element year {TextShort},
        element holder {text},
        element minilicense {TextShort}?,
        ShortLicense?
    }
ColophonFront =
    element colophon {
        MetaDataTarget,
        element colophon-items {empty}
    }
Biography =
    element biography {
        MetaDataTitleOptional,
        (BlockStatementNoNumber | ParagraphsNoNumber)+
    }
Dedication =
    element dedication {
        MetaDataTitleOptional,
        (Paragraph|ParagraphLined)+
    }
Acknowledgement =
    element acknowledgement {
        MetaDataTitleOptional,
        (BlockStatementNoNumber | ParagraphsNoNumber)+
    }
Preface =
    element preface {
        MetaDataTitleOptional,
        (
            (
                (BlockStatementNoNumber | ParagraphsNoNumber)+,
                Attribution*
            )
            |
            (
                (BlockStatementNoNumber | ParagraphsNoNumber )*,
                Contributors,
                (BlockStatementNoNumber | ParagraphsNoNumber)*
            )
        )
    }

Section 48 Front matter (experimental)

A few simple tweaks to frontmatter elements.
We give an alternative definition of the two elements in the ColophonFront that are different.

⟨74 Front matter (dev)⟩ ≡

ShortLicense_X =
    element shortlicense {
        TextLong &
        Footnote*
    }
ShortLicense |= ShortLicense_X
Website_X = element website {
    element name {TextShort},
    element address {text}
}
Website |= Website_X

Section 49 Contributors

A single contributors element may be placed into a preface and is a list of contributor. It can be optionally preceded, or followed, by all the usual things that can go into any preface. An AuthorByline is a special instance of acknowledging a contributor on a division.

⟨75 Contributor⟩ ≡

Contributor =
    element contributor {
        MetaDataTarget,
        PersonName,
        (
            (
            Department?,
            Institution?,
            Location?
            )
            |
            Affiliation+
        )?,
        Email?
    }
Contributors =
    element contributors {
        Contributor+
    }
AuthorByline =
    element author {(TextSimple|Xref)}

Section 50 Back Matter

Articles and books have material at the end, structured as a sequence of appendix. A solutions division should be numbered and rendered as if it was one of the appendix, and so can mix-in in any order.

⟨76 Back matter⟩ ≡

ArticleBackMatter =
    element backmatter {
        MetaDataTitleOptional,
        (ArticleAppendix|Solutions)*,
        Glossary?,
        References?,
        IndexDivision?,
        ColophonBack?
    }
BookBackMatter =
    element backmatter {
        MetaDataTitleOptional,
        (BookAppendix|Solutions)*,
        Glossary?,
        References?,
        IndexDivision?,
        ColophonBack?
    }
ColophonBack =
    element colophon {
        MetaDataTarget,
        (BlockText | SideBySideNoNumber | SideBySideGroupNoNumber)+
    }

Section 51 Document Information

The docinfo section is like a small database for the document.

⟨77 Document information⟩ ≡

DocInfo =
    element docinfo {
        Component?,
        XMLLang?,
        Configuration+
    }
Preambles 78⟩
macros 79⟩
Project initialism 80⟩
Element renaming 81⟩
Source directories 82⟩
Author biographies 83⟩
Authored-attribute defaults 84⟩
DoenetML version 85⟩
Page logo 86⟩
Blurb 87⟩
Document ID 88⟩
We add some items which will become parts of preambles to support math in syntax, <latex-image>, and <asymptote>. packages, and their cousins, MathJax extensions, can be specified to support mathematics elements (<m> and friends). Images specified by or Asymptote syntax sometimes need extra information in their preambles.

⟨78 Preambles⟩ ≡

Configuration |=
        element math-package {
            attribute latex-name {text},
            attribute mathjax-name {text}
        }*
Configuration |=
    element latex-image-preamble {text}
Configuration |=
    element asymptote-preamble {text}
Configuration |=
    element pf:prefigure-preamble {
        external "pf-preamble-adapter.rnc"
    }
Macros for are shared across implementations. This should move under some general section, the name is too vague.

⟨79 macros⟩ ≡

Configuration |=
    element macros {text}
An initialism is a useful short version of a book title.

⟨80 Project initialism⟩ ≡

Configuration |=
    element initialism {text}
Some elements can be renamed. This should be a rare event. Since the content of this element can (optionally) be specified in different languages, the @xml:lang attribute is appropriate.
 1 

⟨81 Element renaming⟩ ≡

Configuration |=
    element rename {
        attribute element {text},
        attribute xml:lang {text}?,
        xsd:token { minLength = "1" }
    }
The directories an author curates run with the source, as paths relative to the location of the main source file. The @external attribute names the directory of externally-produced files an author provides (images, and other assets); swap that directory and the document’s content changes, so it is a property of the source. The @data attribute names the directory of data files consumed when building certain images and programs. The destination for files PreTeXt generates is a publisher convenience, and remains a publication file entry.

⟨82 Source directories⟩ ≡

Configuration |=
    element directories {
        attribute external {text}?,
        attribute data {text}?
    }
An author biography (or several) might be a paragraph or two each, or each one might be several pages. This style can be controlled.

⟨83 Author biographies⟩ ≡

Configuration |=
    element author-biographies {
        attribute length {"short" | "long"}
    }
Document-wide defaults for attributes an author writes on individual elements are gathered in a single defaults element. Each child is the plural of the element whose authored attributes it defaults: an attribute given here is employed by any image, program, Parsons problem, or xref which does not carry that attribute itself. The plural names also ensure this configuration is never mistaken for the content elements themselves.

⟨84 Authored-attribute defaults⟩ ≡

Configuration |=
    element defaults {
        element images {
            attribute width {text}?,
            attribute margins {text}?
        }?
        &
        element programs {
            attribute language {text}?,
            attribute compiler-args {text}?,
            attribute download {"yes"|"no"}?,
            attribute linenumbers {"yes"|"no"}?,
            attribute linker-args {text}?,
            attribute interpreter-args {text}?,
            attribute timeout {text}?
        }?
        &
        element parsons {
            attribute language {text}?
        }?
        &
        element slides {
            attribute valign {"top" | "middle" | "bottom"}?
        }?
        &
        element xrefs {
            attribute text { XrefTextStyle }
        }?
    }
DoenetML content is authored against a particular version of the DoenetML viewer, so the version is a property of the source. It is recorded here and consulted when the viewer is loaded.

⟨85 DoenetML version⟩ ≡

Configuration |=
    element doenetml {
        attribute version {text}
    }
A logo image can be placed at absolute coordinates of the printed page, such as the letterhead of a letter or memorandum. The image’s lower-left corner sits at the coordinates given by @llx and @lly, in points. A @width in fixed units (such as in or cm) will scale the image, and @pages elects placement on the first page only (the default) or on every page.
A textbook can have a blurb (roughly what you would expect on the back of the book), and optionally a @shelf that tells Runestone how to categorize the book.

⟨87 Blurb⟩ ≡

Configuration |=
    element blurb {
        attribute shelf {text}?,
        text
    }
A stable identifier for the document, with an optional @edition, supports services which track a project across builds and releases (such as Runestone).

⟨88 Document ID⟩ ≡

Configuration |=
    element document-id {
        attribute edition {text}?,
        text
    }

Section 52 PreFigure Schema Adapters

The PreFigure project publishes its own RELAX-NG compact schema (pf_schema.rnc), which we copy into this directory nearly verbatim (carrying only a handful of minimal upstream bug fixes needed for the grammar to load). That schema defines every PreFigure element in no namespace, with a start pattern of the diagram element. PreTeXt, however, expects PreFigure content in the https://prefigure.org namespace, signalled by the @xmlns on a prefigure element.
This adapter reconciles the two. Declaring a default namespace and then including the upstream schema remaps every one of its elements into the PreFigure namespace, all without restructuring the upstream file. The included grammar’s start pattern (the diagram element) becomes this adapter’s start pattern, which is exactly what the externalRef in ImageCode reaches. Keeping our edits to the upstream schema minimal means a future PreFigure release can be re-synced by copying a single file.

⟨89 PreFigure Schema Adapter⟩ ≡

Root of file: pf-adapter.rnc
default namespace = "https://prefigure.org"
grammar {
    include "pf_schema.rnc"
}
A prefigure-preamble in the docinfo carries PreFigure content (graphical and group elements) without an enclosing diagram. Since the upstream start pattern is the diagram element, that content is not reachable through the externalRef above, which exposes only a grammar’s start pattern. A second adapter includes the same upstream grammar but overrides its start to expose exactly the drawing-content patterns, again reusing the upstream definitions verbatim. Definition elements are deliberately omitted as diagram-specific, rather than preamble, material.

⟨90 PreFigure Preamble Adapter⟩ ≡

Root of file: pf-preamble-adapter.rnc
default namespace = "https://prefigure.org"
grammar {
    include "pf_schema.rnc" {
        start =
            ( GraphicalElements* & GroupElements* )
    }
}

Section 53 Hierarchical Structure

We collect all the specifications, roughly in a top-down order, so the generated schema files have a rational ordering to them, even if the order presented here is different.

⟨91 Hierarchical Structure⟩ ≡

Root of file: pretext.rnc
namespace pf = "https://prefigure.org"
grammar {
Start elements 1⟩
Gross structure 2⟩
Document types 3⟩
Slideshows 4⟩
Divisions 5⟩
Front matter 73⟩
Back matter 76⟩
Paragraphs division 6⟩
Specialized divisions 7⟩
Printout 9⟩
Blocks 32⟩
Common components of blocks 33⟩
Introductions, conclusions, headnotes 34⟩
Objectives and outcomes 38⟩
Block quotes 39⟩
Verbatim displays 40⟩
Lists 41⟩
Definitions 42⟩
Theorems, and similar 43⟩
Axioms, and similar 45⟩
Examples, and similar 65⟩
Projects, and similar 46⟩
Open problems, and similar 47⟩
Remarks, and similar 48⟩
Computation, and similar 49⟩
Asides, and similar 50⟩
Assemblages 51⟩
Captioned and titled displays 52⟩
Side-by-side layouts 54⟩
Images 55⟩
Tabular display 53⟩
Sage code 56⟩
Interactives 57⟩
Video and audio 59⟩
Exercises 61⟩
Poems 60⟩
Bibliography 63⟩
Glossary 64⟩
Contributor 75⟩
WeBWorK 66⟩
Literate programming 67⟩
Miscellaneous or uncertain 71⟩
Frequent constructions 68⟩
Paragraphs 11⟩
Running text 10⟩
Footnotes 36⟩
Index entries 37⟩
Cross-references 35⟩
Mathematics 31⟩
Verbatim text 23⟩
Text groups 30⟩
Text generators 20⟩
Fill-in blank character 21⟩
SI units 22⟩
Characters 19⟩
List generator 72⟩
Document information 77⟩
}

Section 54 Development Schema

Here we collect all fragments that are still experimental and put them in a rnc file that includes the stable schema.
The development schema must remain a purely additive overlay of the production schema: a bare include of pretext.rnc (never with a replacement body in braces), wholly new named patterns, and additions to production patterns only via |= (combine="choice"), with no override of start. So every document valid under the production schema is valid under the development schema, by construction. Validation depends on this: it reports messages from the development schema as genuine errors, and constructs valid only under the development schema as experimental, a classification that a replacement or restriction here would silently corrupt. Validation checks the invariant on every run and warns of any departure.

⟨92 Development Schema⟩ ≡

Root of file: pretext-dev.rnc
namespace xhtml = "http://www.w3.org/1999/xhtml"
  grammar {

  include "pretext.rnc"
Interactives 58⟩
Front matter (dev) 74⟩
Proofs, and similar 44⟩
Solutions (experimental) 8⟩
Exercises (experimental) 62⟩
}

Appendix A. Fragments

Fragment 1 Start elements
Fragment 2 Gross structure
Fragment 3 Document types
Fragment 4 Slideshows
Fragment 5 Divisions
Fragment 6 Paragraphs division
Fragment 7 Specialized divisions
Fragment 8 Solutions (experimental)
Fragment 9 Printout
Fragment 10 Running text
Fragment 11 Paragraphs
Fragment 12 Delimiter characters
Fragment 13 Dash characters
Fragment 14 Arithmetic characters
Fragment 15 Exotic characters
Fragment 16 Icon characters
Fragment 17 Keyboard characters
Fragment 18 Music characters
Fragment 19 Characters
Fragment 20 Text generators
Fragment 21 Fill-in blank character
Fragment 22 SI units
Fragment 23 Verbatim text
Fragment 24 Abbreviations
Fragment 25 Delimited groups
Fragment 26 Highlighted groups
Fragment 27 Editing groups
Fragment 28 XML syntax groups
Fragment 29 Taxonomic groups
Fragment 30 Text groups
Fragment 31 Mathematics
Fragment 32 Blocks
Fragment 33 Common components of blocks
Fragment 34 Introductions, conclusions, headnotes
Fragment 35 Cross-references
Fragment 36 Footnotes
Fragment 37 Index entries
Fragment 38 Objectives and outcomes
Fragment 39 Block quotes
Fragment 40 Verbatim displays
Fragment 42 Definitions
Fragment 43 Theorems, and similar
Fragment 44 Proofs, and similar
Fragment 45 Axioms, and similar
Fragment 46 Projects, and similar
Fragment 47 Open problems, and similar
Fragment 48 Remarks, and similar
Fragment 49 Computation, and similar
Fragment 50 Asides, and similar
Fragment 51 Assemblages
Fragment 52 Captioned and titled displays
Fragment 53 Tabular display
Fragment 54 Side-by-side layouts
Fragment 55 Images
Fragment 56 Sage code
Fragment 57 Interactives
Fragment 58 Interactives
Fragment 59 Video and audio
Fragment 61 Exercises
Fragment 62 Exercises (experimental)
Fragment 63 Bibliography
Fragment 64 Glossary
Fragment 65 Examples, and similar
Fragment 66 WeBWorK
Fragment 67 Literate programming
Fragment 68 Frequent constructions
Fragment 69 Attribution
Fragment 70 Metadata
Fragment 71 Miscellaneous or uncertain
Fragment 72 List generator
Fragment 73 Front matter
Fragment 74 Front matter (dev)
Fragment 75 Contributor
Fragment 76 Back matter
Fragment 77 Document information
Fragment 78 Preambles
Fragment 79 macros
Fragment 80 Project initialism
Fragment 81 Element renaming
Fragment 82 Source directories
Fragment 83 Author biographies
Fragment 84 Authored-attribute defaults
Fragment 85 DoenetML version
Fragment 86 Page logo
Fragment 88 Document ID
Fragment 89 PreFigure Schema Adapter
Fragment 90 PreFigure Preamble Adapter
Fragment 91 Hierarchical Structure
Fragment 92 Development Schema