Skip to content

Latest commit

 

History

History
1097 lines (859 loc) · 52.5 KB

File metadata and controls

1097 lines (859 loc) · 52.5 KB

Спецификация формата Doceo

Версия: 1.0-draft
Статус: Черновик
Дата: 06.05.2026


Содержание

  1. Введение
  2. Термины и определения
  3. Соглашения
  4. Структура файла
  5. Спецификация манифеста
  6. Спецификация содержимого
  7. Модель встроенных узлов
  8. Правила валидации
  9. Версионирование и совместимость
  10. Приложение А – Полный справочник узлов
  11. Приложение Б – Полный пример документа

1. Введение

1.1 Назначение

Настоящий документ определяет формат файлов DOCEO (.doceo) – формат-контейнер для интерактивных образовательных документов. Формат предназначен для создания, распространения и отображения структурированных документов, включающих интерактивные компоненты: вкладки, пошаговые алгоритмы, тесты, галереи и другие элементы.

1.2 Область применения

Настоящая спецификация охватывает:

  • физическую структуру файла .doceo;
  • схему файла manifest.json;
  • схему файла content.json;
  • модель встроенного содержимого;
  • инварианты валидации;
  • политику версионирования.

Настоящая спецификация не регламентирует поведение при отображении, реализацию редактора и серверную обработку.

1.3 Целевая аудитория

Настоящая спецификация предназначена для разработчиков, реализующих совместимые с Doceo ридеры (readers), редакторы (writers), валидаторы и инструменты преобразования.

1.4 Концепция документа

Документ .doceo является автономным (self-contained) артефактом контента без состояния (stateless). По определению для него выполняются следующие свойства.

Отсутствие состояния (Statelessness). Документ не содержит состояния ридера. Он не фиксирует, какая вкладка была выбрана последней, какой аккордеон был открыт, на каком шаге степпера остановился пользователь и какие пункты контрольного списка были отмечены. Всё подобное состояние является временным (ephemeral) и управляется исключительно ридером в течение одного сеанса чтения.

Однозначно определённое отображение (Deterministic rendering). При одном и том же файле .doceo и совместимом ридере начальное отображение MUST быть идентичным при любом количестве чтений, на любых платформах, в любой момент времени. Документ является чистой функцией своего содержимого.

Автономность (Self-containment). Всё содержимое, необходимое для отображения документа, либо встроено в архив, либо указано через явную ссылку URL. Ридер MUST NOT зависеть от какого-либо внешнего состояния, сеанса или сервиса для формирования начального отображения документа.

Совокупность данных свойств гарантирует полную переносимость файла .doceo и возможность верификации его отображения по содержимому в любой момент времени.


2. Термины и определения

Документ (Document) – единственный файл .doceo, представляющий законченную единицу контента.

Архив (Archive) – ZIP-контейнер, физически хранящий файл .doceo.

Манифест (Manifest) – файл manifest.json, расположенный в корне архива. Описывает метаданные документа, реестр ресурсов и подпись целостности.

Содержимое (Content) – файл content.json, расположенный в корне архива. Описывает дерево узлов документа.

Ресурс (Asset) – любой медиафайл (изображение, видео, растровый рисунок холста), хранящийся в архиве и используемый узлами содержимого.

Узел (Node) – единичный элемент дерева узлов документа. Каждый узел имеет уникальный идентификатор id и поле-дискриминатор type.

Семантический контейнер (Semantic Container) – узел, организующий произвольную последовательность дочерних узлов посредством конкретного поведения при отображении или семантической роли. Состав дочерних узлов не ограничен схемой.

Семантическая структура (Semantic Structure) – узел с фиксированным именованным контрактом типизированных полей. Состав дочерних узлов определяется схемой, а не обобщённым массивом children.

Носитель контента (Content Carrier) – терминальный узел, несущий конкретный тип представления информации: форматированный текст, растровое изображение, видео, код, формулу, холст или диаграмму. Носители контента не имеют дочерних блочных узлов.

Текст (Text) – носитель контента, содержащий последовательность встроенных узлов. text никогда не является самостоятельным блочным узлом дерева документа; он присутствует исключительно как значение именованного поля семантических структур.

Встроенный узел (Inline Node) – элемент, существующий внутри носителя контента text. Встроенные узлы образуют модель форматированного текста (rich text).

Дискриминантное объединение (Discriminated Union) – объект JSON, тип которого однозначно определяется значением поля type.

NodeId – 8-символьный буквенно-цифровой идентификатор, сгенерированный алгоритмом nanoid, уникальный в рамках одного документа.

Легальное изменение (Legal Modification) – любое изменение документа, выполненное через совместимый редактор (conforming writer) от имени автора документа. Прямое изменение содержимого архива за пределами совместимого редактора является нелегальным изменением. Поле updated_at MUST обновляться исключительно при легальных изменениях.

Обратная совместимость (Backward Compatibility) – гарантия того, что ридер, реализующий версию N настоящей спецификации, корректно обрабатывает документы, созданные в соответствии с любой версией M, где M ≤ N, при условии игнорирования неизвестных полей и неизвестных типов узлов.


3. Соглашения

3.1 Нотация

Определения типов в настоящей спецификации используют TypeScript-подобную нотацию для обеспечения ясности. Данная нотация является декларативной и не подразумевает какого-либо конкретного языка реализации.

type NodeId = string        // 8-символьный nanoid
type HexColor = string      // например, "#ff0000"

interface ExampleNode {
  id:       NodeId          // обязательное поле
  type:     "example"       // обязательное поле, литеральный дискриминатор
  label?:   Text            // необязательное поле (помечено символом ?)
  level:    1 | 2 | 3       // объединение литералов
}

3.2 Обязательность полей

Маркер Значение
Без маркера Поле является обязательным. Его отсутствие делает документ невалидным.
Суффикс ? Поле является необязательным. Его отсутствие допустимо.

3.3 Ключевые слова

Ключевые слова «MUST», «MUST NOT», «REQUIRED», «SHALL», «SHALL NOT», «SHOULD», «SHOULD NOT», «RECOMMENDED», «MAY» и «OPTIONAL» в настоящем документе интерпретируются в соответствии с RFC 2119.

3.4 Неизвестные поля

Совместимый ридер MUST игнорировать неизвестные поля любого узла. Данное требование необходимо для обеспечения прямой совместимости (forward compatibility) с будущими версиями настоящей спецификации.

3.5 Неизвестные типы узлов

Совместимый ридер, обнаруживший узел с неизвестным значением поля type, MUST NOT завершать работу с ошибкой. Вместо неизвестного узла ридер SHOULD отображать нейтральную заглушку.


4. Структура файла

4.1 Формат контейнера

Файл .doceo представляет собой ZIP-архив в соответствии со спецификацией формата ZIP (PKWARE). Архив MUST использовать расширение файла .doceo.

Архив MUST содержать следующие файлы в своём корне:

document.doceo
├── manifest.json
└── content.json

Архив MAY содержать директорию media/:

document.doceo
├── manifest.json
├── content.json
└── media/
    ├── <имя_файла_ресурса>
    └── ...

4.2 Ограничения

  • Файл manifest.json MUST присутствовать в архиве.
  • Файл content.json MUST присутствовать в архиве.
  • Все файлы ресурсов, указанные в поле assets файла manifest.json, MUST присутствовать в архиве по пути, заданному в поле path соответствующей записи.
  • Ни один файл ресурса не может присутствовать в архиве без соответствующей записи в массиве assets файла manifest.json.

4.3 Медиафайлы

Поддерживаемые типы медиафайлов:

Категория Форматы
Растровое изображение .webp, .png, .jpg, .jpeg, .gif
Видео .mp4
Растровый рисунок холста .png

5. Спецификация манифеста

5.1 Схема

Файл manifest.json MUST быть реализацией следующего интерфейса Manifest.

interface Manifest {
  version:          string       // версия формата, например "1.0"
  meta:             DocumentMeta
  content_filename: string       // всегда "content.json" в версии 1.0
  assets:           Asset[]
  content_sha256:   string       // SHA-256 файла content.json в шестнадцатеричной кодировке
  signature?:       string       // подпись HMAC-SHA256 (см. раздел 5.5)
}

interface DocumentMeta {
  id:         string             // UUIDv7
  title:      string
  author_id:  string             // UUIDv7 пользователя платформы
  created_at: string             // ISO 8601, UTC
  updated_at: string             // ISO 8601, UTC
}

interface Asset {
  path:   string                 // относительный путь в архиве, например "media/img_a3f1.webp"
  sha256: string                 // SHA-256 содержимого файла в шестнадцатеричной кодировке
}

5.2 Описание полей

version – версия спецификации формата Doceo, которой соответствует данный документ. Ридер MUST проверять данное поле до начала обработки документа. Если версия превышает поддерживаемую ридером, ридер SHOULD предупреждать пользователя.

meta.id – идентификатор UUIDv7, присваиваемый документу в момент его создания. Идентификатор сохраняется при редактировании и повторных публикациях. После первоначального присвоения он MUST NOT изменяться.

meta.author_id – идентификатор UUIDv7 пользователя платформы, создавшего документ. Преобразование данного идентификатора в удобочитаемое имя выходит за рамки настоящей спецификации.

meta.created_at и meta.updated_at – строки даты и времени в формате ISO 8601 в часовом поясе UTC. Поле updated_at MUST обновляться при каждом легальном изменении (Legal Modification) документа (см. раздел 2).

content_filename – имя файла содержимого в архиве. В версии 1.0 данное значение MUST равняться "content.json". Поле предусмотрено в целях обеспечения прямой совместимости.

assets – реестр всех медиафайлов, присутствующих в архиве. Каждая запись содержит относительный путь к файлу и его контрольную сумму SHA-256. Ридер MAY использовать данный реестр для проверки целостности ресурсов до начала отображения.

content_sha256 – контрольная сумма SHA-256 файла content.json. Данное поле включается в полезную нагрузку подписи (см. раздел 5.5).

5.3 Пример

{
  "version": "1.0",
  "meta": {
    "id": "01952f3a-7b4c-7d3e-9f1a-2c4b6d8e0a2f",
    "title": "Введение в виртуальные окружения Python",
    "author_id": "01945c2b-1a3d-7e4f-8b2c-3d5a7f9e1c0b",
    "created_at": "2026-05-06T10:30:00Z",
    "updated_at": "2026-05-06T14:15:00Z"
  },
  "content_filename": "content.json",
  "assets": [
    {
      "path": "media/img_a3f1.webp",
      "sha256": "e3b0c44298fc1c149afbf4c8996fb924..."
    }
  ],
  "content_sha256": "9f86d081884c7d659a2feaa0c55ad015...",
  "signature": "HS256:5d41402abc4b2a76b9719d911017c592..."
}

5.4 Отсутствие подписи

Поле signature является OPTIONAL. Документ без подписи является валидным и допускает обработку. Отсутствие подписи означает, что документ не был опубликован через сервер платформы Doceo и не прошёл верификацию.

Ридер SHOULD различать три состояния и информировать о них пользователя:

Состояние Условие
Без подписи (Unsigned) Поле signature отсутствует
Подтверждён (Verified) Подпись присутствует и действительна
Изменён (Tampered) Подпись присутствует, но недействительна

5.5 Алгоритм подписи

Подпись представляет собой значение HMAC-SHA256, вычисляемое сервером платформы Doceo с использованием секретного ключа, хранящегося на сервере.

Полезная нагрузка (payload) подписи является конкатенацией следующих элементов:

  1. Каноническая JSON-сериализация объекта manifest.json с исключённым полем signature.
  2. Значение поля content_sha256.
  3. Контрольные суммы SHA-256 каждого файла ресурса в порядке их следования в массиве assets.

Значение поля signature имеет префикс с идентификатором алгоритма: HS256:<hmac-в-шестнадцатеричной-кодировке>.


6. Спецификация содержимого

6.1 Корень документа

Файл content.json MUST быть реализацией следующего интерфейса ContentDocument.

interface ContentDocument {
  root: BlockNode[]
}

root – последовательность блочных узлов верхнего уровня. Данный массив MUST содержать только семантические контейнеры и семантические структуры. Носители контента MUST NOT присутствовать непосредственно в root.

Авторитетная версия формата объявляется в файле manifest.json в поле version. Файл content.json не содержит поля версии.

6.2 Классификация узлов

Все блочные узлы относятся к одной из трёх категорий.

Семантический контейнер (Semantic Container) – узел, содержащий произвольную последовательность дочерних блочных узлов в массиве children. Характер дочерних узлов определяется поведением при отображении, а не фиксированной схемой. Семантические контейнеры допустимы в root и в любом массиве children.

Семантическая структура (Semantic Structure) – узел с фиксированным именованным контрактом типизированных полей. Некоторые семантические структуры содержат children, однако состав их дочерних узлов семантически ограничен (например, tabs (вкладки) принимает только узлы tab_item). Семантические структуры допустимы в root, если не указано иное.

Носитель контента (Content Carrier) – терминальный узел, несущий конкретный тип представления информации. Носители контента не имеют дочерних блочных узлов. Носители контента MUST NOT присутствовать в root непосредственно; они MUST присутствовать как значение именованного поля семантической структуры или как дочерний узел семантического контейнера.

Специальный носитель контента text (текст) никогда не является самостоятельным блочным узлом. Он присутствует исключительно как значение именованного поля семантических структур.

6.3 Семантические контейнеры

Все семантические контейнеры содержат следующие базовые поля:

interface SemanticContainerBase {
  id:       NodeId
  type:     string
  children: BlockNode[]
}

Далее описываются семантические контейнеры, присутствующие в формате .doceo версии 1.0.

6.3.1 container

Нейтральный блок группировки без семантической роли или поведения.

interface ContainerNode {
  id:       NodeId
  type:     "container"
  children: BlockNode[]
}

6.3.2 list

Нумерованный, маркированный или контрольный список.

interface ListNode {
  id:        NodeId
  type:      "list"
  list_type: "ordered" | "unordered" | "checklist"
  children:  BlockNode[]
}

Дочерние узлы SHOULD быть узлами paragraph (абзац). Узел list MAY выступать дочерним узлом другого узла list для образования вложенного списка.

При значении list_type равном "checklist" каждый дочерний узел представляет один проверяемый пункт. Состояние отмеченности пунктов контрольного списка является временным и управляется исключительно ридером. Оно не сохраняется в документе.

6.3.3 accordion

Сворачиваемый блок. Отображает заголовок и переключает видимость дочерних узлов. При необходимости нескольких независимых сворачиваемых секций SHOULD использоваться несколько узлов accordion.

interface AccordionNode {
  id:       NodeId
  type:     "accordion"
  label:    Text
  children: BlockNode[]
}

6.3.4 tabs

Контейнер с вкладками. В каждый момент времени виден ровно один дочерний узел, выбираемый читателем. Дочерние узлы MUST быть узлами tab_item (элемент вкладки).

interface TabsNode {
  id:       NodeId
  type:     "tabs"
  children: TabItemNode[]
}

interface TabItemNode {
  id:       NodeId
  type:     "tab_item"
  label:    Text
  children: BlockNode[]
}

Узлы tab_item MUST NOT присутствовать за пределами узла tabs.

6.3.5 stepper

Последовательный пошаговый блок. В каждый момент времени виден ровно один дочерний узел. Навигация ограничена смежными шагами (предыдущий / следующий). Дочерние узлы MUST быть узлами step_item (шаг степпера).

interface StepperNode {
  id:       NodeId
  type:     "stepper"
  children: StepItemNode[]
}

interface StepItemNode {
  id:       NodeId
  type:     "step_item"
  label:    Text
  children: BlockNode[]
}

Узлы step_item MUST NOT присутствовать за пределами узла stepper.

6.3.6 gallery

Циклическое последовательное отображение дочерних узлов. Навигация перемещается вперёд и назад по дочерним узлам в циклическом режиме. Дочерними узлами MAY быть узлы container (контейнер), оборачивающие единственный узел image (изображение).

interface GalleryNode {
  id:       NodeId
  type:     "gallery"
  children: BlockNode[]
}

6.3.7 callout

Семантически выделенный блок, используемый для примечаний, предупреждений, советов или сообщений об опасности.

interface CalloutNode {
  id:           NodeId
  type:         "callout"
  callout_type: "info" | "warning" | "danger" | "tip"
  children:     BlockNode[]
}

6.3.8 quote

Блочная цитата.

interface QuoteNode {
  id:       NodeId
  type:     "quote"
  children: BlockNode[]
}

6.4 Семантические структуры

6.4.1 paragraph

Блок основного текста. Единственная семантическая обёртка для форматированного текста в потоке документа.

interface ParagraphNode {
  id:      NodeId
  type:    "paragraph"
  content: Text
}

6.4.2 heading

Заголовок раздела с иерархическим уровнем.

interface HeadingNode {
  id:      NodeId
  type:    "heading"
  level:   1 | 2 | 3 | 4 | 5 | 6
  content: Text
}

6.4.3 figure

Медиаэлемент в паре с подписью.

interface FigureNode {
  id:      NodeId
  type:    "figure"
  media:   ImageLeaf | VideoLeaf | CanvasLeaf
  caption: Text
}

6.4.4 table

Двумерная структура данных с необязательной подписью.

interface TableNode {
  id:       NodeId
  type:     "table"
  caption?: Text
  head:     TableSection
  body:     TableSection
}

interface TableSection {
  rows: TableRow[]
}

interface TableRow {
  id:    NodeId
  cells: TableCell[]
}

interface TableCell {
  id:        NodeId
  is_header: boolean
  content:   Text
}

Узлы row (строка) и cell (ячейка) MUST NOT присутствовать за пределами узла table (таблица).

6.4.5 quiz

Интерактивный блок самопроверки.

interface QuizNode {
  id:            NodeId
  type:          "quiz"
  input_type:    "radio" | "checkbox" | "text"
  question:      QuizQuestion
  options?:      QuizOption[]    // обязательно при input_type равном "radio" или "checkbox"
  correct?:      string[]        // идентификаторы вариантов; обязательно при input_type равном "radio" или "checkbox"
  correct_text?: CorrectText     // обязательно при input_type равном "text"
}

interface QuizQuestion {
  children: BlockNode[]
}

interface QuizOption {
  id:       string
  children: (Text | ImageLeaf)[]
}

interface CorrectText {
  match:   "exact" | "regex"
  pattern: string
  flags?:  string             // флаги регулярного выражения, например "i"
}

Массив QuizOption.children MUST содержать не более одного элемента типа Text и не более одного элемента типа ImageLeaf. Объект QuizOption без дочерних элементов является невалидным.

6.4.6 divider

Тематический разрыв между разделами содержимого. Не содержит полей с данными.

interface DividerNode {
  id:   NodeId
  type: "divider"
}

Узел divider (разделитель) является семантической структурой и допустим в root и массивах children.

6.5 Носители контента

6.5.1 text

Единственный носитель форматированного встроенного содержимого. Узел text никогда не является самостоятельным блочным узлом. Он присутствует исключительно как значение именованного поля семантических структур.

interface Text {
  type:    "text"
  inlines: InlineNode[]
}

6.5.2 image

Единственный носитель растрового или векторного изображения.

interface ImageLeaf {
  id:   NodeId
  type: "image"
  src:  string    // путь к ресурсу, например "media/img_a3f1.webp"
  alt:  string    // непустой альтернативный текст
}

6.5.3 video

Видеоресурс, встроенный в архив как ресурс или указанный по URL.

interface VideoLeaf {
  id:            NodeId
  type:          "video"
  src_type:      "file" | "url"
  src:           string          // путь к ресурсу или URL
  aspect_ratio?: string          // например "16:9"
}

6.5.4 code

Блок исходного кода.

interface CodeLeaf {
  id:       NodeId
  type:     "code"
  language: string    // идентификатор языка, например "python", "bash"
  code:     string
}

6.5.5 formula

Математическое выражение блочного уровня в нотации LaTeX.

interface FormulaLeaf {
  id:    NodeId
  type:  "formula"
  latex: string
}

6.5.6 canvas

Растровый рисунок, созданный автором документа.

interface CanvasLeaf {
  id:     NodeId
  type:   "canvas"
  width:  number    // в пикселях
  height: number    // в пикселях
  src:    string    // путь к ресурсу PNG
}

6.5.7 diagram

Диаграмма, определённая на формальном текстовом синтаксисе.

interface DiagramLeaf {
  id:     NodeId
  type:   "diagram"
  syntax: "mermaid"
  code:   string
}

7. Модель встроенных узлов

Встроенные узлы существуют исключительно внутри объектов Text. Они образуют рекурсивное дерево типизированных сегментов.

type InlineNode = SpanInline
               | InlineCodeInline
               | InlineFormulaInline
               | CopySnippetInline
               | LinkInline
               | AnchorLinkInline
               | SpoilerInline

type Mark = "bold" | "italic" | "underline" | "strikethrough"

7.1 Терминальные встроенные узлы

Терминальные встроенные узлы (Inline Terminals) являются листовыми встроенными узлами. Они несут текстовое содержимое и не имеют встроенных дочерних узлов.

span

Прогон простого или стилизованного текста.

interface SpanInline {
  type:  "span"
  text:  string
  marks: Mark[]
}

Массив marks (метки выделения) MAY быть пустым. Несколько меток выделения MAY применяться одновременно.

inline_code

Прогон кода в потоке текста. Отображается шрифтом с фиксированной шириной символов. Метки выделения не применяются.

interface InlineCodeInline {
  type: "inline_code"
  code: string
}

inline_formula

Математическое выражение встроенного уровня в нотации LaTeX.

interface InlineFormulaInline {
  type:  "inline_formula"
  latex: string
}

copy_snippet

Текстовый сегмент, снабжённый элементом управления «копировать в буфер обмена». Копируемое содержимое всегда является простым текстом (plain text).

interface CopySnippetInline {
  type: "copy_snippet"
  text: string
}

7.2 Узлы-обёртки

Узлы-обёртки (Inline Wrappers) несут семантическое поведение и содержат другие встроенные узлы в качестве дочерних.

link

Гиперссылка на внешний URL.

interface LinkInline {
  type:    "link"
  href:    string        // абсолютный URL
  content: InlineNode[]
}

anchor_link

Гиперссылка на блочный узел в рамках того же документа, идентифицируемый по его id.

interface AnchorLinkInline {
  type:      "anchor_link"
  target_id: NodeId
  content:   InlineNode[]
}

spoiler

Встроенный сегмент, скрытый по умолчанию и раскрываемый по взаимодействию пользователя.

interface SpoilerInline {
  type:    "spoiler"
  content: InlineNode[]
}

8. Правила валидации

Следующие инварианты определяют валидный документ Doceo. Совместимый валидатор MUST сообщать о нарушении каждого неудовлетворённого инварианта.

Идентификатор Область Правило
INV-001 Документ Каждое значение id MUST быть уникальным в рамках документа
INV-002 root root MUST содержать только семантические контейнеры и семантические структуры. Носители контента MUST NOT присутствовать в root
INV-003 text text MUST NOT присутствовать как самостоятельный блок в root или массивах children. Он MUST присутствовать только как значение именованного поля
INV-004 Встроенные узлы Встроенные узлы MUST существовать только внутри объектов Text. Они MUST NOT присутствовать как блочные узлы
INV-005 tabs Массив children узла tabs MUST содержать только узлы tab_item
INV-006 stepper Массив children узла stepper MUST содержать только узлы step_item
INV-007 tab_item Узлы tab_item MUST NOT присутствовать за пределами узла tabs
INV-008 step_item Узлы step_item MUST NOT присутствовать за пределами узла stepper
INV-009 figure Поле media узла figure MUST быть одним из: image, video, canvas
INV-010 table Узлы row MUST NOT присутствовать за пределами узла table. Узлы cell MUST NOT присутствовать за пределами узла row
INV-011 quiz При значении input_type равном "radio" или "checkbox" поля options и correct MUST присутствовать
INV-012 quiz При значении input_type равном "text" поле correct_text MUST присутствовать
INV-013 anchor_link Значение target_id узла anchor_link MUST ссылаться на существующий id узла в рамках того же документа
INV-014 Ресурсы Каждый путь к ресурсу, указанный в content.json, MUST иметь соответствующую запись в поле assets файла manifest.json
INV-015 Ресурсы Каждая запись в поле assets файла manifest.json MUST соответствовать файлу, присутствующему в архиве
INV-016 quiz Массив QuizOption.children MUST содержать не более одного элемента типа Text и не более одного элемента типа ImageLeaf. Объект QuizOption без дочерних элементов является невалидным

9. Версионирование и совместимость

9.1 Идентификатор версии

Версия формата представляет собой строку вида MAJOR.MINOR, например "1.0".

  • Инкремент MINOR указывает исключительно на аддитивные изменения: новые необязательные поля, новые типы узлов, новые встроенные типы, новые значения перечислений. Обратная совместимость сохраняется.
  • Инкремент MAJOR указывает на несовместимые изменения: удаление полей, переименование полей, изменение семантики существующих типов узлов.

9.2 Контракт обратной совместимости

Ридер, реализующий версию N.x, MUST быть способен обрабатывать любой документ с версией N.y, где y ≤ x, при соблюдении следующих правил:

  1. Неизвестные поля любого узла MUST игнорироваться.
  2. Неизвестные значения type блочных узлов MUST обрабатываться в соответствии с разделом 3.5.
  3. Неизвестные значения type встроенных узлов MUST трактоваться как пустой фрагмент.
  4. Неизвестные значения перечислений (например, неизвестные callout_type, list_type) MUST трактоваться как значение по умолчанию для соответствующего поля (при его наличии) или игнорироваться.

9.3 Прямая совместимость

Ридер, реализующий версию N, SHOULD NOT предпринимать попытку обработки документа с версией M, где M > N, без выдачи предупреждения. Ридер MAY выполнять отображение по возможности (best-effort), игнорируя неизвестные конструкции согласно правилам раздела 9.2.


10. Приложение А – Полный справочник узлов

Тип Класс Допустим в root/children Ключевые поля
container Семантический контейнер да children[]
list Семантический контейнер да children[], list_type
accordion Семантический контейнер да label, children[]
tabs Семантический контейнер да children: tab_item[]
tab_item Семантический контейнер только в tabs label, children[]
stepper Семантический контейнер да children: step_item[]
step_item Семантический контейнер только в stepper label, children[]
gallery Семантический контейнер да children[]
callout Семантический контейнер да children[], callout_type
quote Семантический контейнер да children[]
paragraph Семантическая структура да content: Text
heading Семантическая структура да content: Text, level
figure Семантическая структура да media, caption: Text
table Семантическая структура да head, body, caption?
row Семантическая структура только в table cells[]
cell Семантическая структура только в row content: Text, is_header
quiz Семантическая структура да question, options[], input_type, correct
divider Семантическая структура да
text Носитель контента только как поле inlines[]
image Носитель контента только в контейнере/поле src, alt
video Носитель контента только в контейнере/поле src_type, src
code Носитель контента только в контейнере/поле language, code
formula Носитель контента только в контейнере/поле latex
canvas Носитель контента только в контейнере/поле width, height, src
diagram Носитель контента только в контейнере/поле syntax, code
span Терминальный встроенный узел только в text text, marks[]
inline_code Терминальный встроенный узел только в text code
inline_formula Терминальный встроенный узел только в text latex
copy_snippet Терминальный встроенный узел только в text text
link Узел-обёртка только в text href, content[]
anchor_link Узел-обёртка только в text target_id, content[]
spoiler Узел-обёртка только в text content[]

11. Приложение Б – Полный пример документа

Приведённый пример представляет собой фрагмент руководства по виртуальным окружениям Python. Он демонстрирует использование всех основных типов узлов, определённых в настоящей спецификации.

{
  "root": [
    {
      "id": "a1b2c3d4",
      "type": "heading",
      "level": 1,
      "content": {
        "type": "text",
        "inlines": [
          { "type": "span", "text": "Виртуальные окружения Python", "marks": [] }
        ]
      }
    },
    {
      "id": "b2c3d4e5",
      "type": "paragraph",
      "content": {
        "type": "text",
        "inlines": [
          { "type": "span", "text": "Данное руководство охватывает создание и активацию окружений ", "marks": [] },
          { "type": "inline_code", "code": "venv" },
          { "type": "span", "text": ".", "marks": [] }
        ]
      }
    },
    {
      "id": "c3d4e5f6",
      "type": "callout",
      "callout_type": "info",
      "children": [
        {
          "id": "d4e5f6g7",
          "type": "paragraph",
          "content": {
            "type": "text",
            "inlines": [
              { "type": "span", "text": "Требуется Python ", "marks": [] },
              { "type": "inline_code", "code": "3.3+" },
              { "type": "span", "text": ". Проверьте версию командой ", "marks": [] },
              { "type": "copy_snippet", "text": "python --version" },
              { "type": "span", "text": ".", "marks": [] }
            ]
          }
        }
      ]
    },
    {
      "id": "e5f6g7h8",
      "type": "tabs",
      "children": [
        {
          "id": "f6g7h8i9",
          "type": "tab_item",
          "label": {
            "type": "text",
            "inlines": [{ "type": "span", "text": "Unix / macOS", "marks": [] }]
          },
          "children": [
            {
              "id": "g7h8i9j0",
              "type": "container",
              "children": [
                {
                  "id": "h8i9j0k1",
                  "type": "code",
                  "language": "bash",
                  "code": "source venv/bin/activate"
                }
              ]
            }
          ]
        },
        {
          "id": "i9j0k1l2",
          "type": "tab_item",
          "label": {
            "type": "text",
            "inlines": [{ "type": "span", "text": "Windows", "marks": [] }]
          },
          "children": [
            {
              "id": "j0k1l2m3",
              "type": "container",
              "children": [
                {
                  "id": "k1l2m3n4",
                  "type": "code",
                  "language": "bat",
                  "code": "venv\\Scripts\\activate.bat"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "l2m3n4o5",
      "type": "stepper",
      "children": [
        {
          "id": "m3n4o5p6",
          "type": "step_item",
          "label": {
            "type": "text",
            "inlines": [{ "type": "span", "text": "Создать окружение", "marks": [] }]
          },
          "children": [
            {
              "id": "n4o5p6q7",
              "type": "container",
              "children": [
                {
                  "id": "o5p6q7r8",
                  "type": "code",
                  "language": "bash",
                  "code": "python -m venv venv"
                }
              ]
            }
          ]
        },
        {
          "id": "p6q7r8s9",
          "type": "step_item",
          "label": {
            "type": "text",
            "inlines": [{ "type": "span", "text": "Активировать окружение", "marks": [] }]
          },
          "children": [
            {
              "id": "q7r8s9t0",
              "type": "paragraph",
              "content": {
                "type": "text",
                "inlines": [
                  { "type": "span", "text": "См. ", "marks": [] },
                  {
                    "type": "anchor_link",
                    "target_id": "e5f6g7h8",
                    "content": [{ "type": "span", "text": "вкладки активации выше", "marks": [] }]
                  },
                  { "type": "span", "text": ".", "marks": [] }
                ]
              }
            }
          ]
        },
        {
          "id": "r8s9t0u1",
          "type": "step_item",
          "label": {
            "type": "text",
            "inlines": [{ "type": "span", "text": "Установить зависимости", "marks": [] }]
          },
          "children": [
            {
              "id": "s9t0u1v2",
              "type": "container",
              "children": [
                {
                  "id": "t0u1v2w3",
                  "type": "code",
                  "language": "bash",
                  "code": "pip install -r requirements.txt"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": "u1v2w3x4",
      "type": "divider"
    },
    {
      "id": "v2w3x4y5",
      "type": "quiz",
      "input_type": "radio",
      "question": {
        "children": [
          {
            "id": "w3x4y5z6",
            "type": "paragraph",
            "content": {
              "type": "text",
              "inlines": [
                { "type": "span", "text": "Какая команда активирует окружение на Unix?", "marks": [] }
              ]
            }
          }
        ]
      },
      "options": [
        {
          "id": "opt1",
          "children": [
            { "type": "text", "inlines": [{ "type": "inline_code", "code": "venv\\Scripts\\activate.bat" }] }
          ]
        },
        {
          "id": "opt2",
          "children": [
            { "type": "text", "inlines": [{ "type": "inline_code", "code": "source venv/bin/activate" }] }
          ]
        }
      ],
      "correct": ["opt2"]
    },
    {
      "id": "x4y5z6a7",
      "type": "accordion",
      "label": {
        "type": "text",
        "inlines": [{ "type": "span", "text": "Типичные ошибки и способы их устранения", "marks": [] }]
      },
      "children": [
        {
          "id": "y5z6a7b8",
          "type": "callout",
          "callout_type": "danger",
          "children": [
            {
              "id": "z6a7b8c9",
              "type": "paragraph",
              "content": {
                "type": "text",
                "inlines": [
                  { "type": "span", "text": "При ошибке ", "marks": [] },
                  { "type": "inline_code", "code": "command not found: python" },
                  { "type": "span", "text": " попробуйте использовать ", "marks": [] },
                  { "type": "inline_code", "code": "python3" },
                  { "type": "span", "text": ".", "marks": [] }
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}