Skip to content

Commit

Permalink
feat(inputters): Support front/main/back matter divisions in master d…
Browse files Browse the repository at this point in the history
…ocuments
  • Loading branch information
Omikhleia authored and Didier Willis committed Jan 14, 2025
1 parent 01dcb79 commit a0e0b03
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 77 deletions.
84 changes: 84 additions & 0 deletions examples/manual-masterdoc/bookdivisions.dj
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Book divisions

In §[](#masterdoc-content-basic), we explored how to declare a book consisting of chapters or parts.
For books with chapters only:


```yaml
content:
chapters:
⟨...⟩
```

Or for books with parts:

```yaml
content:
parts:
⟨...⟩
```

Actually, this is equivalent to the following full syntax:

```yaml
content:
mainmatter:
⟨parts or chapters⟩
```

That is, your content is assumed to be part of the main matter by default.
Some books, however, require a more complex structure, dividing content into front matter, main matter, and back matter.
This is particularly common in academic works, where the front matter might include a preface, dedication, and table of contents.

## Front, main and back divisions

Each division begins on an odd-numbered page.
Divisions influence both the format and numbering of pages:

- The front matter uses roman numerals for page numbers by default.
- The main and back matter use arabic numerals for page numbers by default.

These defaults can be overridden to suit specific requirements, with adequate styles.
Page numbering resets whenever the format changes between divisions (e.g., front matter from i--viii, main matter from 1--148).

Divisions also influence the numbering of parts and chapters:

- In the front matter and back matter, parts and chapters are unnumbered by definition.
- In the main matter, parts and chapters are numbered by default, though this behavior can be customized with styles.

All default styles can be overridden to suit specific requirements, but we will cover this in a later section.
All divisions can include parts.
However, it is up to you to maintain consistency with your structural expectations.

```yaml
content:
frontmatter:
⟨parts or chapters⟩
mainmatter:
⟨parts or chapters⟩
backmatter:
⟨parts or chapters⟩
```

Below is an example of how to declare a book that incorporates these divisions.

```yaml
content:
frontmatter:
chapters:
- preface.dj
- dedication.dj
mainmatter:
parts:
- caption: Part 1
content:
- chapt1-1.dj
- chapt1-2.dj
- caption: Part 2
content:
- chapt2-1.dj
- chapt2-2.dj
backmatter:
chapters:
- postface.dj
```
1 change: 1 addition & 0 deletions examples/manual-masterdoc/masterdoc.dj
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The `sile` object provides intructions for processing the master document with S
- The `packages` object lists extra packages that SILE must use.
It can be used to load add-on packages needed by your content but not already provided by the document class and its supporting packages.

{#masterdoc-content-basic}
## Up to the content

Last but not least, the `chapters` array (or `parts`, see further below) lists the files to include in the work.
Expand Down
1 change: 1 addition & 0 deletions examples/sile-resilient-manual.silm
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ parts:
content:
- manual-masterdoc/masterdoc.dj
- manual-masterdoc/bookmatters.dj
- manual-masterdoc/bookdivisions.dj
- manual-masterdoc/bibliographies.dj
- file: manual-parts/part-layouts.dj
content:
Expand Down
231 changes: 154 additions & 77 deletions inputters/silm.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- Some YAML master file inputter for SILE
--
-- @copyright License: MIT (c) 2023 Omikhleia, Didier Willis
-- @copyright License: MIT (c) 2023, 2025 Omikhleia, Didier Willis
-- @module inputters.silm
--
local ast = require("silex.ast")
Expand Down Expand Up @@ -134,102 +134,145 @@ local BibliographySchema = {
}
}

local MasterSchema = {
local MetaDataSchema = {
type = "object",
additionalProperties = true, -- Allow unknown property for extensibility
properties = {
masterfile = {
type = "number",
title = { type = "string" },
subtitle = { type = "string" },
subject = { type = "string" },
keywords = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
authors = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
metadata = {
translators = { -- oneOf
type = "string",
items = { type = "string" }
},
publisher = { type = "string" },
pubdate = {
type = "object",
-- tinyyaml converts dates to osdate objects
-- We don't care about hours, minutes, etc.
additionalProperties = true,
properties = {
title = { type = "string" },
subtitle = { type = "string" },
subject = { type = "string" },
keywords = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
authors = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
translators = { -- oneOf
type = "string",
items = { type = "string" }
},
publisher = { type = "string" },
pubdate = {
type = "object",
-- tinyyaml converts dates to osdate objects
-- We don't care about hours, minutes, etc.
additionalProperties = true,
properties = {
year = { type = "number" },
month = { type = "number" },
day = { type = "number" }
}
},
isbn = { type = "string" },
url = { type = "string" },
copyright = { type = "string" },
legal = { type = "string" },
year = { type = "number" },
month = { type = "number" },
day = { type = "number" }
}
},
font = {
isbn = { type = "string" },
url = { type = "string" },
copyright = { type = "string" },
legal = { type = "string" },
}
}

local FontSchema = {
type = "object",
properties = {
family = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
size = { type = "string" }
}
}

local SileSpecificSchema = {
type = "object",
properties = {
options = {
type = "object",
properties = {
family = {
type = { -- oneOf
{ type = "string" },
{ type = "array", items = { type = "string" } },
},
},
size = { type = "string" }
class = { type = "string" },
papersize = { type = "string" },
layout = { type = "string" },
resolution = { type = "number" },
headers = { type = "string" },
offset = { type = "string" },
}
},
language = { type = "string" },
sile = {
settings = {
type = "object",
-- Allow any setting without validation
additionalProperties = true,
properties = {
options = {
type = "object",
properties = {
class = { type = "string" },
papersize = { type = "string" },
layout = { type = "string" },
resolution = { type = "number" },
headers = { type = "string" },
offset = { type = "string" },
}
},
settings = {
type = "object",
-- Allow any setting without validation
additionalProperties = true,
properties = {
}
},
packages = {
type = "array",
items = { type = "string" }
}
}
},
packages = {
type = "array",
items = { type = "string" }
}
}
}

local PartPropertiesSchema = {
type = "object",
properties = {
parts = ContentSchema,
}
}

local ChapterPropertiesSchema = {
type = "object",
properties = {
chapters = ContentSchema,
}
}

local PartOrChapterSchema = {
type = { -- oneOf
PartPropertiesSchema,
ChapterPropertiesSchema,
}
}

local StructuredContentSchema = {
type = { -- oneOf
PartPropertiesSchema,
ChapterPropertiesSchema,
{ type = "object",
properties = {
frontmatter = PartOrChapterSchema,
mainmatter = PartOrChapterSchema,
backmatter = PartOrChapterSchema,
}
},
}
}

local MasterSchema = {
type = "object",
additionalProperties = true, -- Allow unknown property for extensibility
properties = {
masterfile = {
type = "number",
},
metadata = MetaDataSchema,
font = FontSchema,
language = { type = "string" },
sile = SileSpecificSchema,
book = BookSchema,
bibliography = BibliographySchema,
-- parts and chapters are exclusive, but we don't enforce it here.
-- We will check it later in the inputter
parts = ContentSchema,
chapters = ContentSchema
chapters = ContentSchema,
content = StructuredContentSchema,
}
}

local lastErrors = {}
--- Naive recursive schema validation
---@param obj table parsed YAML object
---@param schema table schema to validate against
Expand Down Expand Up @@ -394,6 +437,29 @@ local function doLevel (content, entries, shiftHeadings, metaopts)
end
end

local function doDivisionContent (content, entry, shiftHeadings, metaopts)
if entry.parts then
doLevel(content, entry.parts, shiftHeadings - 1, metaopts)
elseif entry.chapters then
doLevel(content, entry.chapters, shiftHeadings, metaopts)
end
end

local function doDivision (content, entry, shiftHeadings, metaopts)
if entry.frontmatter then
content[#content+1] = createCommand("frontmatter")
doDivisionContent(content, entry.frontmatter, shiftHeadings, metaopts)
end
if entry.mainmatter then
content[#content+1] = createCommand("mainmatter")
doDivisionContent(content, entry.mainmatter, shiftHeadings, metaopts)
end
if entry.backmatter then
content[#content+1] = createCommand("backmatter")
doDivisionContent(content, entry.backmatter, shiftHeadings, metaopts)
end
end

--- Process back cover content (text included in the back cover)
---@param entry table|string Back cover content entry
---@param metaopts table Metadata options
Expand Down Expand Up @@ -492,6 +558,9 @@ function inputter:parse (doc)
})
end
end
-- TODO QUESTION: if not a root document, should we wrap the includes
-- in a language group? Mixing documents with different languages
-- is probably not a good idea, anyhow...

local sile = master.sile or {}
local metadata = master.metadata or {}
Expand Down Expand Up @@ -591,13 +660,21 @@ function inputter:parse (doc)
end

-- Document content
-- TODO QUESTION: if not a root document, should we wrap the includes
-- in a language group? Mixing documents with different languages
-- is probably not a good idea, anyhow...

if master.parts then
doLevel(content, master.parts, baseShiftHeadings - 1, metadataOptions)
elseif master.chapters then
doLevel(content, master.chapters, baseShiftHeadings, metadataOptions)
elseif master.content then
if master.content.parts then
doLevel(content, master.content.parts, baseShiftHeadings - 1, metadataOptions)
elseif master.content.chapters then
doLevel(content, master.content.chapters, baseShiftHeadings, metadataOptions)
elseif master.content.frontmatter or master.content.mainmatter or master.content.backmatter then
doDivision(content, master.content, baseShiftHeadings, metadataOptions)
else
SU.error("Invalid master document (no division, parts or chapters)")
end
end

if enabledBook then
Expand Down

0 comments on commit a0e0b03

Please sign in to comment.