Версия: 1.0-draft
Статус: Черновик
Дата: 06.05.2026
- Введение
- Термины и определения
- Соглашения
- Структура файла
- Спецификация манифеста
- Спецификация содержимого
- Модель встроенных узлов
- Правила валидации
- Версионирование и совместимость
- Приложение А – Полный справочник узлов
- Приложение Б – Полный пример документа
Настоящий документ определяет формат файлов DOCEO (.doceo) – формат-контейнер для интерактивных образовательных документов. Формат предназначен для создания, распространения и отображения структурированных документов, включающих интерактивные компоненты: вкладки, пошаговые алгоритмы, тесты, галереи и другие элементы.
Настоящая спецификация охватывает:
- физическую структуру файла
.doceo; - схему файла
manifest.json; - схему файла
content.json; - модель встроенного содержимого;
- инварианты валидации;
- политику версионирования.
Настоящая спецификация не регламентирует поведение при отображении, реализацию редактора и серверную обработку.
Настоящая спецификация предназначена для разработчиков, реализующих совместимые с Doceo ридеры (readers), редакторы (writers), валидаторы и инструменты преобразования.
Документ .doceo является автономным (self-contained) артефактом контента без состояния (stateless). По определению для него выполняются следующие свойства.
Отсутствие состояния (Statelessness). Документ не содержит состояния ридера. Он не фиксирует, какая вкладка была выбрана последней, какой аккордеон был открыт, на каком шаге степпера остановился пользователь и какие пункты контрольного списка были отмечены. Всё подобное состояние является временным (ephemeral) и управляется исключительно ридером в течение одного сеанса чтения.
Однозначно определённое отображение (Deterministic rendering). При одном и том же файле .doceo и совместимом ридере начальное отображение MUST быть идентичным при любом количестве чтений, на любых платформах, в любой момент времени. Документ является чистой функцией своего содержимого.
Автономность (Self-containment). Всё содержимое, необходимое для отображения документа, либо встроено в архив, либо указано через явную ссылку URL. Ридер MUST NOT зависеть от какого-либо внешнего состояния, сеанса или сервиса для формирования начального отображения документа.
Совокупность данных свойств гарантирует полную переносимость файла .doceo и возможность верификации его отображения по содержимому в любой момент времени.
Документ (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, при условии игнорирования неизвестных полей и неизвестных типов узлов.
Определения типов в настоящей спецификации используют TypeScript-подобную нотацию для обеспечения ясности. Данная нотация является декларативной и не подразумевает какого-либо конкретного языка реализации.
type NodeId = string // 8-символьный nanoid
type HexColor = string // например, "#ff0000"
interface ExampleNode {
id: NodeId // обязательное поле
type: "example" // обязательное поле, литеральный дискриминатор
label?: Text // необязательное поле (помечено символом ?)
level: 1 | 2 | 3 // объединение литералов
}| Маркер | Значение |
|---|---|
| Без маркера | Поле является обязательным. Его отсутствие делает документ невалидным. |
Суффикс ? |
Поле является необязательным. Его отсутствие допустимо. |
Ключевые слова «MUST», «MUST NOT», «REQUIRED», «SHALL», «SHALL NOT», «SHOULD», «SHOULD NOT», «RECOMMENDED», «MAY» и «OPTIONAL» в настоящем документе интерпретируются в соответствии с RFC 2119.
Совместимый ридер MUST игнорировать неизвестные поля любого узла. Данное требование необходимо для обеспечения прямой совместимости (forward compatibility) с будущими версиями настоящей спецификации.
Совместимый ридер, обнаруживший узел с неизвестным значением поля type, MUST NOT завершать работу с ошибкой. Вместо неизвестного узла ридер SHOULD отображать нейтральную заглушку.
Файл .doceo представляет собой ZIP-архив в соответствии со спецификацией формата ZIP (PKWARE). Архив MUST использовать расширение файла .doceo.
Архив MUST содержать следующие файлы в своём корне:
document.doceo
├── manifest.json
└── content.jsonАрхив MAY содержать директорию media/:
document.doceo
├── manifest.json
├── content.json
└── media/
├── <имя_файла_ресурса>
└── ...- Файл
manifest.jsonMUST присутствовать в архиве. - Файл
content.jsonMUST присутствовать в архиве. - Все файлы ресурсов, указанные в поле
assetsфайлаmanifest.json, MUST присутствовать в архиве по пути, заданному в полеpathсоответствующей записи. - Ни один файл ресурса не может присутствовать в архиве без соответствующей записи в массиве
assetsфайлаmanifest.json.
Поддерживаемые типы медиафайлов:
| Категория | Форматы |
|---|---|
| Растровое изображение | .webp, .png, .jpg, .jpeg, .gif |
| Видео | .mp4 |
| Растровый рисунок холста | .png |
Файл 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 содержимого файла в шестнадцатеричной кодировке
}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).
{
"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..."
}Поле signature является OPTIONAL. Документ без подписи является валидным и допускает обработку. Отсутствие подписи означает, что документ не был опубликован через сервер платформы Doceo и не прошёл верификацию.
Ридер SHOULD различать три состояния и информировать о них пользователя:
| Состояние | Условие |
|---|---|
| Без подписи (Unsigned) | Поле signature отсутствует |
| Подтверждён (Verified) | Подпись присутствует и действительна |
| Изменён (Tampered) | Подпись присутствует, но недействительна |
Подпись представляет собой значение HMAC-SHA256, вычисляемое сервером платформы Doceo с использованием секретного ключа, хранящегося на сервере.
Полезная нагрузка (payload) подписи является конкатенацией следующих элементов:
- Каноническая JSON-сериализация объекта
manifest.jsonс исключённым полемsignature. - Значение поля
content_sha256. - Контрольные суммы SHA-256 каждого файла ресурса в порядке их следования в массиве
assets.
Значение поля signature имеет префикс с идентификатором алгоритма: HS256:<hmac-в-шестнадцатеричной-кодировке>.
Файл content.json MUST быть реализацией следующего интерфейса ContentDocument.
interface ContentDocument {
root: BlockNode[]
}root – последовательность блочных узлов верхнего уровня. Данный массив MUST содержать только семантические контейнеры и семантические структуры. Носители контента MUST NOT присутствовать непосредственно в root.
Авторитетная версия формата объявляется в файле manifest.json в поле version. Файл content.json не содержит поля версии.
Все блочные узлы относятся к одной из трёх категорий.
Семантический контейнер (Semantic Container) – узел, содержащий произвольную последовательность дочерних блочных узлов в массиве children. Характер дочерних узлов определяется поведением при отображении, а не фиксированной схемой. Семантические контейнеры допустимы в root и в любом массиве children.
Семантическая структура (Semantic Structure) – узел с фиксированным именованным контрактом типизированных полей. Некоторые семантические структуры содержат children, однако состав их дочерних узлов семантически ограничен (например, tabs (вкладки) принимает только узлы tab_item). Семантические структуры допустимы в root, если не указано иное.
Носитель контента (Content Carrier) – терминальный узел, несущий конкретный тип представления информации. Носители контента не имеют дочерних блочных узлов. Носители контента MUST NOT присутствовать в root непосредственно; они MUST присутствовать как значение именованного поля семантической структуры или как дочерний узел семантического контейнера.
Специальный носитель контента text (текст) никогда не является самостоятельным блочным узлом. Он присутствует исключительно как значение именованного поля семантических структур.
Все семантические контейнеры содержат следующие базовые поля:
interface SemanticContainerBase {
id: NodeId
type: string
children: BlockNode[]
}Далее описываются семантические контейнеры, присутствующие в формате .doceo версии 1.0.
Нейтральный блок группировки без семантической роли или поведения.
interface ContainerNode {
id: NodeId
type: "container"
children: BlockNode[]
}Нумерованный, маркированный или контрольный список.
interface ListNode {
id: NodeId
type: "list"
list_type: "ordered" | "unordered" | "checklist"
children: BlockNode[]
}Дочерние узлы SHOULD быть узлами paragraph (абзац). Узел list MAY выступать дочерним узлом другого узла list для образования вложенного списка.
При значении list_type равном "checklist" каждый дочерний узел представляет один проверяемый пункт. Состояние отмеченности пунктов контрольного списка является временным и управляется исключительно ридером. Оно не сохраняется в документе.
Сворачиваемый блок. Отображает заголовок и переключает видимость дочерних узлов. При необходимости нескольких независимых сворачиваемых секций SHOULD использоваться несколько узлов accordion.
interface AccordionNode {
id: NodeId
type: "accordion"
label: Text
children: BlockNode[]
}Контейнер с вкладками. В каждый момент времени виден ровно один дочерний узел, выбираемый читателем. Дочерние узлы 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.
Последовательный пошаговый блок. В каждый момент времени виден ровно один дочерний узел. Навигация ограничена смежными шагами (предыдущий / следующий). Дочерние узлы 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.
Циклическое последовательное отображение дочерних узлов. Навигация перемещается вперёд и назад по дочерним узлам в циклическом режиме. Дочерними узлами MAY быть узлы container (контейнер), оборачивающие единственный узел image (изображение).
interface GalleryNode {
id: NodeId
type: "gallery"
children: BlockNode[]
}Семантически выделенный блок, используемый для примечаний, предупреждений, советов или сообщений об опасности.
interface CalloutNode {
id: NodeId
type: "callout"
callout_type: "info" | "warning" | "danger" | "tip"
children: BlockNode[]
}Блочная цитата.
interface QuoteNode {
id: NodeId
type: "quote"
children: BlockNode[]
}Блок основного текста. Единственная семантическая обёртка для форматированного текста в потоке документа.
interface ParagraphNode {
id: NodeId
type: "paragraph"
content: Text
}Заголовок раздела с иерархическим уровнем.
interface HeadingNode {
id: NodeId
type: "heading"
level: 1 | 2 | 3 | 4 | 5 | 6
content: Text
}Медиаэлемент в паре с подписью.
interface FigureNode {
id: NodeId
type: "figure"
media: ImageLeaf | VideoLeaf | CanvasLeaf
caption: Text
}Двумерная структура данных с необязательной подписью.
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 (таблица).
Интерактивный блок самопроверки.
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 без дочерних элементов является невалидным.
Тематический разрыв между разделами содержимого. Не содержит полей с данными.
interface DividerNode {
id: NodeId
type: "divider"
}Узел divider (разделитель) является семантической структурой и допустим в root и массивах children.
Единственный носитель форматированного встроенного содержимого. Узел text никогда не является самостоятельным блочным узлом. Он присутствует исключительно как значение именованного поля семантических структур.
interface Text {
type: "text"
inlines: InlineNode[]
}Единственный носитель растрового или векторного изображения.
interface ImageLeaf {
id: NodeId
type: "image"
src: string // путь к ресурсу, например "media/img_a3f1.webp"
alt: string // непустой альтернативный текст
}Видеоресурс, встроенный в архив как ресурс или указанный по URL.
interface VideoLeaf {
id: NodeId
type: "video"
src_type: "file" | "url"
src: string // путь к ресурсу или URL
aspect_ratio?: string // например "16:9"
}Блок исходного кода.
interface CodeLeaf {
id: NodeId
type: "code"
language: string // идентификатор языка, например "python", "bash"
code: string
}Математическое выражение блочного уровня в нотации LaTeX.
interface FormulaLeaf {
id: NodeId
type: "formula"
latex: string
}Растровый рисунок, созданный автором документа.
interface CanvasLeaf {
id: NodeId
type: "canvas"
width: number // в пикселях
height: number // в пикселях
src: string // путь к ресурсу PNG
}Диаграмма, определённая на формальном текстовом синтаксисе.
interface DiagramLeaf {
id: NodeId
type: "diagram"
syntax: "mermaid"
code: string
}Встроенные узлы существуют исключительно внутри объектов Text. Они образуют рекурсивное дерево типизированных сегментов.
type InlineNode = SpanInline
| InlineCodeInline
| InlineFormulaInline
| CopySnippetInline
| LinkInline
| AnchorLinkInline
| SpoilerInline
type Mark = "bold" | "italic" | "underline" | "strikethrough"Терминальные встроенные узлы (Inline Terminals) являются листовыми встроенными узлами. Они несут текстовое содержимое и не имеют встроенных дочерних узлов.
Прогон простого или стилизованного текста.
interface SpanInline {
type: "span"
text: string
marks: Mark[]
}Массив marks (метки выделения) MAY быть пустым. Несколько меток выделения MAY применяться одновременно.
Прогон кода в потоке текста. Отображается шрифтом с фиксированной шириной символов. Метки выделения не применяются.
interface InlineCodeInline {
type: "inline_code"
code: string
}Математическое выражение встроенного уровня в нотации LaTeX.
interface InlineFormulaInline {
type: "inline_formula"
latex: string
}Текстовый сегмент, снабжённый элементом управления «копировать в буфер обмена». Копируемое содержимое всегда является простым текстом (plain text).
interface CopySnippetInline {
type: "copy_snippet"
text: string
}Узлы-обёртки (Inline Wrappers) несут семантическое поведение и содержат другие встроенные узлы в качестве дочерних.
Гиперссылка на внешний URL.
interface LinkInline {
type: "link"
href: string // абсолютный URL
content: InlineNode[]
}Гиперссылка на блочный узел в рамках того же документа, идентифицируемый по его id.
interface AnchorLinkInline {
type: "anchor_link"
target_id: NodeId
content: InlineNode[]
}Встроенный сегмент, скрытый по умолчанию и раскрываемый по взаимодействию пользователя.
interface SpoilerInline {
type: "spoiler"
content: InlineNode[]
}Следующие инварианты определяют валидный документ 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 без дочерних элементов является невалидным |
Версия формата представляет собой строку вида MAJOR.MINOR, например "1.0".
- Инкремент MINOR указывает исключительно на аддитивные изменения: новые необязательные поля, новые типы узлов, новые встроенные типы, новые значения перечислений. Обратная совместимость сохраняется.
- Инкремент MAJOR указывает на несовместимые изменения: удаление полей, переименование полей, изменение семантики существующих типов узлов.
Ридер, реализующий версию N.x, MUST быть способен обрабатывать любой документ с версией N.y, где y ≤ x, при соблюдении следующих правил:
- Неизвестные поля любого узла MUST игнорироваться.
- Неизвестные значения
typeблочных узлов MUST обрабатываться в соответствии с разделом 3.5. - Неизвестные значения
typeвстроенных узлов MUST трактоваться как пустой фрагмент. - Неизвестные значения перечислений (например, неизвестные
callout_type,list_type) MUST трактоваться как значение по умолчанию для соответствующего поля (при его наличии) или игнорироваться.
Ридер, реализующий версию N, SHOULD NOT предпринимать попытку обработки документа с версией M, где M > N, без выдачи предупреждения. Ридер MAY выполнять отображение по возможности (best-effort), игнорируя неизвестные конструкции согласно правилам раздела 9.2.
| Тип | Класс | Допустим в 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[] |
Приведённый пример представляет собой фрагмент руководства по виртуальным окружениям 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": [] }
]
}
}
]
}
]
}
]
}