---
layout: default
lang: ru
ref: helpers
title: Руководство переводчика и разработчика правил
---
# <img src="../logo.png" style="position: relative; top: 16px; z-index: -1;"> Руководство переводчика и разработчика правил

## Информация для разработчиков правил и переводчиков MathCAT
Эта страница находится в процессе подготовки.

## Начало работы
Если вы планируете участвовать в разработке MathCAT, используйте GitHub:
1. Создайте форк репозитория MathCAT `github.com/NSoiffer/MathCAT`.
2. Клонируйте форк, чтобы получить локальную копию для работы.
3. Переключитесь на созданную для вашей работы ветку, обычно названную кодом языка перевода, и работайте в ней.

Если вы ещё не знакомы с этими действиями, найдите одно из многочисленных руководств. Они достаточно просты, поэтому отсутствие опыта не должно вас останавливать.


## Переводчикам на другие языки
Если вы хотите перевести MathCAT, свяжитесь с @NSoiffer. Он подготовит начальный вариант перевода, который может значительно сэкономить время. В результате будут созданы файлы в каталоге `Rules/Languages/xx`, где `xx` — код языка, например `fr`, `de` или `el`. Перевод выполняется в этом каталоге. Необходимо отредактировать четыре категории файлов:
1. `definitions.yaml`: содержит переводы числительных, в том числе количественных и порядковых. Проверьте начальный перевод и внесите необходимые исправления. Эти числительные используются, например, при озвучивании фразы «три пятых». На некотором этапе образование числительных в языках становится регулярным, поэтому некоторые списки в файле можно сократить, а некоторые следует дополнить. Подробнее см. английские комментарии в файле.
2. Файлы `xxx_Rules.yaml`, сейчас это `ClearSpeak_Rules.yaml` и `SimpleSpeak_Rules.yaml`. Они соответствуют разным стилям речи. Настоятельно рекомендуется сначала выбрать только один стиль. Эти файлы обычно содержат слова, описывающие структуру выражения, например «дробь» и «степень», а также связующие слова. Поскольку стили речи во многом похожи, существует также каталог `SharedRules` с общими файлами правил. Они подключаются в `ClearSpeak_Rules.yaml` и `SimpleSpeak_Rules.yaml` с помощью правил `- include: file_name`. Их тоже необходимо перевести.
<br/>
<br/>
Примечание: диалог настроек MathCAT ищет файлы с именами вида `XXX_Rules.yaml` и добавляет их в раскрывающийся список для языка. Использовать имена SimpleSpeak и ClearSpeak необязательно. Если вы хотите перевести только один стиль, например SimpleSpeak, но не хотите удалять `ClearSpeak_Rules.yaml`, переименуйте его, например, в `ClearSpeak_Rules.yaml.untranslated`.
<br/>
<br/>
Эти файлы содержат автоматически созданные начальные переводы. Хотя текст уже переведён, используется ключ `t:`, а не `T:` с прописной буквы. Каждый перевод необходимо проверить и только после этого изменить ключ на вариант с прописной буквы. Подробнее об автоматическом переводе см. ниже.

    * В некоторых языках нет смысла произносить эквиваленты слов «the» и, возможно, «of» во фразе «the square root of x». В таком случае замените их пустыми строками.
    * В некоторых языках меняется порядок слов. Переставляйте слова свободно, но внимательно следите за отступами: в YAML они значимы.
    * В некоторых языках могут потребоваться слова, отсутствующие в английской версии, до или после существующих фраз. Добавляйте их при необходимости. Условное добавление выполняется с помощью `test`. Если нужна помощь, свяжитесь с @NSoiffer.
    * Паузы между словами и фразами могут значительно улучшить понятность речи. Исходные паузы выбраны для английского языка. Настройте их в соответствии со звучанием синтезаторов вашего языка. Паузы легко добавлять, удалять и изменять. Их длительность масштабируется в соответствии с текущей скоростью речи.
3. Файлы Unicode: `unicode.yaml` и `unicode-full.yaml`. Они содержат такие символы, как `<` и `∫`.
    * Начните с перевода `unicode.yaml`. В нём находится подавляющее большинство используемых математических символов. Сейчас список основан на практическом опыте, но в дальнейшем планируется уточнить его по статистике из реальных книг. В `unicode.yaml` около 270 символов, примерно 50 из которых — греческие буквы. Как и в файлах правил речи, здесь есть автоматически созданные начальные переводы. Проверьте их и измените `t:` на `T:`. Подробнее об автоматическом переводе см. ниже.
    * В `unicode-full.yaml` тысячи строк. Вернитесь к нему после завершения остальных переводов и работайте столько, сколько сможете: большинство этих символов встречаются только в сложной математике и даже там используются редко. Наиболее важны:
         * некоторые стрелки, начиная с 0x2190;
         * символы из блока математических операторов 0x2200–0x22ff;
         * некоторые диакритические знаки 0x2d8–0x2dd;
         * некоторые простые чёрные и белые фигуры, начиная с 0x25a0 и 0x2b1a.
4. Файлы навигации `navigate.yaml` и `overview.yaml`. Переводите только `navigate.yaml`: `overview.yaml` ещё не готов к использованию. Многие слова в `navigate.yaml` многократно повторяются, поэтому удобно применять глобальный поиск и замену. В дальнейшем файл планируется переработать и выделить повторяющиеся слова.

__ПРИМЕЧАНИЕ__: Сейчас почти завершён переход правил на использование `intent`. Сложная логика распознавания абсолютных величин, определителей и других конструкций будет перенесена в независимый от языка каталог `intent`. Это упростит перевод, поскольку правилу потребуется сопоставлять только тег `absolute-value` или `determinant`. Тесты также следует вынести в независимый от языка каталог `intent`.

### Пометка переведённого текста
Эти файлы имеют формат YAML, описанный ниже.
Во всех этих файлах переводимый текст задаётся ключом YAML `t`, а в редких случаях — `ot`, `ct`, `spell`, `pronounce` и `IfThenElse`. После проверки перевода запишите ключ прописными буквами, например `T` или `IFTHENELSE`, чтобы обозначить переведённый фрагмент.

Например, в `unicode.yaml` есть два правила:
```
 - "=": [t: "equals"]                            # 0x3d
 - ">":                                          # 0x3e
     - test: 
         if: "$Verbosity!='Terse'"
         then: [t: "is"]
     - t: "greater than"
```
При переводе на французский язык слова после `t:` заменяются примерно так:
```
 - "=": [T: "égale"]                            # 0x3d

 - ">":                                         # 0x3e
     - test: 
         if: "$Verbosity!='Terse'"
         then: [T: "est"]
     - T: "supérieur à"
```

Примечание: иногда значение `IfThenElse` не требует перевода, но ключ всё равно следует изменить, чтобы было видно, что строка проверена. В следующем примере перевод не нужен, поскольку части `then` и `else`, соответственно `count(*/*[1])` и `$LineCountTry`, не являются словами:
```
 - LineCount: "IfThenElse($LineCountTry=0, count(*/*[1]), $LineCountTry)"
```

Подробнее о содержимом файлов правил см. ниже.

### Примечание о переведённых файлах
Для получения начального перевода файлов Unicode используются переводы MathPlayer и SRE, а также Google Translate.
Если переводы SRE и MathPlayer совпадают либо перевод есть только в одном из них и совпадает с Google Translate, в комментарий в конце строки добавляется только исходный английский вариант. Например:
```
 - "!": [t: "factorielle"]                      	#  0x21	(en: 'factorial')
```

Если переводы MathPlayer и SRE различаются, выбирается вариант, совпадающий с Google Translate, а другой вариант включается в комментарий. Например:
```
        else: [t: "parenthèse gauche"]          	# 	(en: 'left paren', MathPlayer: 'parenthèse ouvrante')
```
Если не совпадает ни один перевод, выбирается один из вариантов, а остальные приводятся в комментарии. Например:
```
            else: [t: "parenthèse gauche"]      	# 	(en: 'open paren', MathPlayer: 'parenthèse ouvrante', google: 'parenthèse ouverte')
```
Наконец, если перевод отсутствует, используется Google Translate и добавляется комментарий `google translation`. Вероятность неудачного перевода в таком случае заметно выше, поэтому внимательно проверяйте эти строки. Пример, где доступен только Google Translate:
```
          then: [t: "ligne verticale"]          	# 	(en: 'vertical line', google translation)
```


### Проверка перевода
Если вы используете NVDA, начатый перевод можно сразу проверить. Предполагается, что дополнение MathCAT уже установлено:
1. Скопируйте новый каталог перевода в `%AppData%\nvda\addons\MathCAT\globalPlugins\MathCAT\Rules\Languages`.
2. Запустите NVDA и откройте меню настроек MathCAT: параметры NVDA, затем «Настройки MathCAT...».
3. Выберите новый язык в раскрывающемся списке `Languages`.
4. Проверьте озвучивание. Хорошим источником примеров служат страницы Википедии.
5. Если произошла ошибка, часто выражающаяся в отсутствии речи, откройте журнал NVDA из подменю «Сервис». Ошибка должна быть указана там. Объяснение сообщений об ошибках приведено ниже.
6. MathCAT должен заметить изменение файла и перезагрузить его. Сейчас это не работает для файлов, подключённых с помощью `include`, например для файлов каталога `Shared`. После изменения такого файла перезагрузите MathCAT через «Сервис: Перезагрузить плагины» в NVDA или перезапустите NVDA.

Перевод диалога настроек выполняется отдельно от перевода речи. Им занимаются добровольцы, которые переводят и другие дополнения. Подробнее см. [эту рассылку](https://groups.io/g/nvda-translations).

### Автоматические тесты перевода
Тестирование очень важно. MathCAT написан на Rust и содержит множество автоматических тестов, использующих встроенную систему тестирования Rust. Чтобы писать и проверять собственные тесты, [скачайте и установите Rust](https://www.rust-lang.org/tools/install). Знать Rust необязательно: достаточно заменить некоторые английские строки ожидаемыми строками на вашем языке.

Предположим, что вы переводите на французский язык с кодом `fr`.

Сначала откройте файл `languages.rs` в каталоге тестов и добавьте строку `mod fr;` после `mod en;` или аналогичной строки другого языка.

В каталоге `tests\Languages` находятся файл `en.rs` и каталог `en`.
1. Скопируйте `en.rs` в `fr.rs`.
2. Скопируйте каталог `en` в `fr`.
3. Если вы выбрали только один стиль речи, например SimpleSpeak, отредактируйте `fr.rs`: удалите строки, начиная с `mod ClearSpeak {` и заканчивая соответствующей строкой `}`. Удалите подкаталог `ClearSpeak` из каталога `fr`.
4. Желательно перевести все файлы, но на начальном этапе можно ограничиться несколькими. В `fr.rs` закомментируйте каждый непереведённый файл, добавив `//` в начало строки. Например, если вы не перевели файл SimpleSpeak `geometry.yaml`, строка должна выглядеть так: `// mod geometry;`.
5. Начните редактировать файлы: сначала глобально замените `"en"` на `"fr"`, а затем замените английские строки подходящими французскими или другими строками.

Пример теста:
```
#[test]
fn common_fraction_half() {
    let expr = "<math>
                    <mfrac> <mn>1</mn> <mn>2</mn> </mfrac>
                </math>";
    test("en", "SimpleSpeak", expr, "1 half");
}
```
Для французского языка строка `test` будет выглядеть так:
```
    test("fr", "SimpleSpeak", expr, "un demi");
```

После перевода нескольких тестов запустите автоматическую проверку.
Сначала убедитесь, что английские тесты работают:
```
cargo test Languages::en
```
Затем запустите свои тесты. В нашем примере:
```
cargo test Languages::fr
```
MathCAT добавляет паузы. В ожидаемых строках тестов они записываются символами `,` и `;`. При необходимости добавьте или удалите эти символы в ожидаемом результате. Если паузы звучат неуместно, добавьте или удалите `pause: xxx` в соответствующем месте одного из файлов `Rules\fr`.

__Совет__: возможно, быстрее всего сначала запустить тесты на своём языке, не меняя ожидаемые результаты. Все тесты завершатся с ошибкой, но в сообщениях будет показана речь, созданная MathCAT на вашем языке. _Если она верна_, просто замените ей английский текст. После обработки всех ошибок снова запустите тесты. В идеальном случае ошибок больше не будет.


### Поддержание перевода в актуальном состоянии
Раздел предстоит написать...

Со временем планируется создать инструмент, который будет:
1. предупреждать об отсутствующих переводах;
2. предупреждать о правилах из каталога `en`, которые не были скопированы в другой язык, вероятно из-за добавления новых английских правил.

Такие инструменты будут искать переведённый и непереведённый текст.


## Переводчикам Брайля
Если вы хотите добавить поддержку новой системы записи Брайля, скорее всего, придётся начать с нуля, если только она не похожа на уже существующую.
Создайте три файла `.yaml` в каталоге `Rules\Braille\your-braille-language` по образцу файлов из других каталогов Брайля:
1. `xxx_Rules.yaml`, где `xxx` — имя новой системы записи Брайля. Файл содержит правила преобразования MathML в Брайль.
2. `unicode.yaml` — перевод наиболее распространённых символов Брайля. Используйте `Nemeth\unicode.yaml` как отправную точку. Замените `t: xxx` подходящими значениями для вашей системы. Вероятно, для символов, зависящих от контекста, потребуется удалить часть логики или добавить собственную. Например, в коде Немета запятая представляется по-разному внутри числа и вне его.
3. `unicode-full.yaml` — переводы остальных символов.

Два отдельных файла Unicode нужны потому, что небольшой файл с распространёнными символами ускоряет запуск. Цель этого файла — охватить 99,99 % используемых символов.

Для UEB и кода Немета потребовалось написать на Rust код очистки. Если для вашего перевода Брайля тоже нужна очистка, создайте issue, чтобы мы вместе могли реализовать необходимый код.

Перевод Брайля можно сразу проверить. Используйте приведённые выше инструкции для перевода языка, где предлагается скопировать файлы в `%AppData%\nvda\addons\MathCAT\globalPlugins\MathCAT\Rules\Languages`, но замените `Languages` на `Braille`. Остальные действия почти не отличаются.

Для автоматического тестирования следуйте приведённым выше инструкциям. Текущие тесты взяты из руководств по коду Немета и UEB. Можно поступить так же. Примеры тестов Брайля находятся в каталогах Nemeth и UEB.

## Понимание сообщений об ошибках MathCAT
Если ошибка вызвана проблемой в правиле, при непосредственном запуске MathCAT сообщение выводится в консоль терминала, а при использовании NVDA — в журнал NVDA.

Сообщения об ошибках могут показаться сложными. Ниже приведены пример и объяснение.

Библиотека, которую MathCAT использует для чтения файлов YAML, не сохраняет номера строк, поэтому MathCAT не может сообщить строку с ошибкой.
Вместо этого указываются имя файла, а также значения `name` и `tag` правила в этом файле.
Затем рекурсивно перечисляются разделы правила, в которых обнаружена ошибка.

Например, ниже приведено сообщение об ошибке, созданное намеренной заменой `test:` на `textx:`:
```
caused by: in file "...\\MathCAT\\Rules\\Languages\\en\\ClearSpeak_Rules.yaml"
caused by: value for 'replace' in rule (fraction: fraction-over-simple). Replacements:
  - test:
      if: "$ClearSpeak_Fractions='FracOver'"
      then:
        - testx:
            if: "$Verbosity!='Terse'"
            then: [ot: the]
        - t: fraction
  - x: "*[1]"
  - t: over
  - x: "*[2]"
  - test:
      if: "$ClearSpeak_Fractions='OverEndFrac' or ($ClearSpeak_Fractions='EndFrac' and not( ($ClearSpeak_Fractions='Auto' or $ClearSpeak_Fractions='Ordinal' or $ClearSpeak_Fractions='EndFrac') and *[1][*[1][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or text()<20)]   and *[2][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or (2<= text() and text()<=10))] ] and *[2][*[1][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or text()<20)]   and *[2][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or (2<= text() and text()<=10))] ] ) )"
      then:
        - pause: short
        - t: end fraction
        - pause: short
caused by: replacement #1 of 5
caused by: replacement #1 of 2
caused by: Unknown 'replace' command (testx) with value:  if: "$Verbosity!='Terse'" then: [ot: the]
```
Первые две строки содержат имя файла, а также значения `tag` и `name`. Соответствующее правило:
```
- name: fraction-over-simple
  tag: fraction
  match:
  - "($ClearSpeak_Fractions='Over' or $ClearSpeak_Fractions='FracOver' or $ClearSpeak_Fractions='OverEndFrac') or"
  - "( not($ClearSpeak_Fractions='General' or $ClearSpeak_Fractions='GeneralEndFrac') and"
  - "  (IsNode(*[1],'simple') and IsNode(*[2],'simple')) )" # simple fraction in ClearSpeak spec
  replace:
  - test:
      if: "$ClearSpeak_Fractions='FracOver'"
      then:
      - testx:
          if: "$Verbosity!='Terse'"
          then: [{ot: "the"}]
      - t: "fraction"
  - x: "*[1]"
  - t: "over"
  - x: "*[2]"
  - test:
      # very ugly!!! -- replicate nested ordinal fraction as they are an exception
      if: "$ClearSpeak_Fractions='OverEndFrac' or ($ClearSpeak_Fractions='EndFrac' and not( ($ClearSpeak_Fractions='Auto' or $ClearSpeak_Fractions='Ordinal' or $ClearSpeak_Fractions='EndFrac') and *[1][*[1][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or text()<20)]   and *[2][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or (2<= text() and text()<=10))] ] and *[2][*[1][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or text()<20)]   and *[2][self::m:mn][not(contains(., '.')) and ($ClearSpeak_Fractions='Ordinal' or (2<= text() and text()<=10))] ] ) )"
      then:
      - pause: short
      - t: "end fraction"
      - pause: short
```

Следующая часть сообщения, `caused by: replacement #1 of 5`, указывает, что проблема находится в первой замене, то есть первом элементе с `-`.
Строка `caused by: replacement #1 of 2` означает, что ошибка находится в первой части этой замены.
Последняя строка сообщает о неизвестной команде замены: `Unknown 'replace' command (testx) with value`. Теперь проблему можно исправить.
Часто сообщение об ошибке удобнее читать снизу вверх.


## Разработчикам Rust
Раздел предстоит написать...

`build.rs` и файлы в `src`.

## Тестирование
Независимо от того, разрабатываете вы код или пишете правила, крайне важно писать и запускать тесты. Они позволяют убедиться, что новый код работает и не нарушает существующую функциональность.

Каталог `tests` похож на каталог `Rules`. Если вы переводчик, см. инструкции в соответствующем разделе выше.

Rust поддерживает тестирование с помощью команды `cargo test`. Подробнее о тестах и покрытии тестами см. в [руководстве разработчика](developers.md).


## Файлы
MathCAT считывает важную информацию из следующих файлов:
* Rules
  * `intent.yaml` — правила, определяющие намерение автора по MathML. Они используются разными стилями речи на разных языках, чтобы не дублировать процесс определения смысла, и добавляют к MathML атрибут `intent`.
  * `definitions.yaml` — различные списки для канонизации MathCAT, то есть определения правильной структуры, и сопоставления правил. Например, `TrigFunctionNames` содержит имена тригонометрических функций, такие как `tan` и `lim`.
  * `prefs.yaml` — значения по умолчанию для доступных настроек. MathCAT также ищет этот файл в зависящем от платформы пользовательском каталоге, чтобы отдельные пользователи могли задавать собственные значения.
    * Windows: `%AppData%\prefs.yaml`.
    * Linux: `$XDG_CONFIG_HOME` или `$HOME/.config`.
  * `definitions.yaml` — независимые от языка определения, например имена тригонометрических функций.
* Rules/[lang]
  * `Unicode.yaml` — длинный список способов произнесения встречающихся символов Unicode. Не используется для строк из нескольких символов.
  * `XXX_rules.yaml` — правила озвучивания математических выражений. MathCAT сканирует каждый подкаталог `Rules`, ищет файлы с суффиксом `_rules.yaml` и добавляет их в список доступных пользователю вариантов. Часть `XXX` должна соответствовать стилю речи. Например, `ClearSpeak_rules.yaml` и `MathSpeak_rules.yaml` добавляют варианты стиля речи ClearSpeak и MathSpeak.
  * `definitions.yaml` — зависящие от языка определения, например способы произнесения порядковых числительных: «первый», «половина» и т. д.
  * `navigate.yaml` — правила, определяющие результат каждой команды навигации и соответствующий текст для озвучивания.

Подкаталог `lang` должен соответствовать двухбуквенному коду языка или комбинации языка и региона согласно [стандарту ISO](https://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes). Например, в каталоге `Rules` есть подкаталог `en`. Если требуется региональный вариант речи, можно добавить подкаталог региона, например `gb`, который будет использоваться для языка `en-gb`.

MathCAT сначала читает правила основного языка, а затем правила региона. Региональные правила заменяют существующие правила или добавляются к ним в соответствующих языковых файлах `Unicode.yaml` и `XXX_rules.yaml`.

MathCAT ищет каталог `Rules` в следующих местах:
1. В каталоге, заданном переменной среды `MathCATRulesDir`.
2. В соседнем с исполняемым файлом подкаталоге Rules. В Windows это обычно `C:\Program Files\MathCAT\Rules`.

# Формат файлов
Файлы, как следует из их суффикса, имеют [формат YAML](https://lzone.de/cheat-sheet/YAML). Если вы ещё не знакомы с YAML, это надмножество JSON с более удобными для чтения и записи вариантами синтаксиса.

## Введение в YAML
Основные типы YAML:
* скалярные типы, например целые и вещественные числа, а также строки. Строки можно заключать в одинарные или двойные кавычки, а в некоторых случаях оставлять без кавычек;
* массивы, которые внутри строки записываются как `["a", "b", "c"]`;
* словари или отображения, которые внутри строки записываются как `{key: value, foo: bar}`.

Комментарии начинаются с символа `#` и продолжаются до конца строки. Блочных комментариев в YAML нет.

В более подробном синтаксисе YAML для массивов вместо скобок используются отступы, поэтому приведённый выше массив выглядит так:
```
 - a
 - b
 - c
```
Обратите внимание, что в такой форме строки не требуется заключать в кавычки, хотя для некоторых текстов кавычки необходимы.

Словарь в подробной форме выглядит так:
```
   key: value
   foo: bar
```

Ниже приведён более реалистичный пример из определений Unicode с несколькими вариантами записи.
Обратите внимание на отступы: все элементы, сдвинутые вправо относительно предыдущей строки, являются вложенными элементами массива или словаря.
```
# Два способа задать простую замену для символа '∞'.
# Для краткости и ясности предпочтительна первая форма.
 - "∞": [t: "infinity"]                          # 0x221e
 - '∞':
    - t: infinity                                # 0x222e

# Несколько способов задать более сложное определение с проверкой.
# Компактная форма использует синтаксис JSON для значения.
- 0x003C: [test: {if:Verbosity!='terse', then: [t: is]}, t: "less than"]

# Эта форма подчёркивает, что выполняются два действия: проверка и вывод "less than".
- 0x003C:
    - test: [{if: Verbosity!='terse', then: {t: is}}]
    - t: "less than"

# Эта форма немного подробнее, но ясно показывает части проверки.
- 0x003C:
    - test: 
        if: Verbosity!='terse'
        then: [t: is]
    - t: less than

# Самая подробная форма YAML.
- 0x003C:
    - test: 
        if: Verbosity!='terse'
        then:
        - t: is
    - t: less than
```
Все формы допустимы, но второй и третий варианты предпочтительны как хороший компромисс между краткостью и ясностью.

Примечание: все файлы YAML начинаются с `---`, обозначающего начало документа.

## Основные части правила речи

```
# rule:
#     name: <string> # имя правила; сочетание name и tag должно быть уникальным
#     tag: <string>
#     variables: [{name: value}, ...]
#      - name -- строка; value -- выражение XPath, возвращающее строку, число или логическое значение
#      - внутри правила значение доступно как $name
#      - значение переменной устанавливается _до_ проверки "match", поэтому его можно использовать в match
#      - переменная действует в течение сопоставления
#     match: <string>  # выражение XPath для сопоставления
#      - может быть одной строкой или
#      - массивом строк, которые объединяются для удобства чтения
#     replace: [replacements], где replacements содержит один из следующих вариантов
#      - t: некоторый текст
#          'T' означает, что текст переведён
#      - ct: объединить текст без пробела перед ним
#           'CT' означает, что текст переведён
#      - ot: необязательный текст; не использовать, если результат содержит повторяющиеся слова
#           'OT' означает, что текст переведён
#           Например, не следует получать "t raised to the the fraction with ...."
#           Если сделать "the" необязательным в правиле дроби, повторения не будет
#      - x: некоторое выражение XPath в виде строки
#      - test: обычные значения if/then/else с двумя особенностями:
#                вместо 'then:' и 'else:' можно использовать 'then_test:' и 'else_test:'
#                  Это позволяет избежать дополнительного уровня 'test:'
#                можно задать любое количество пар if/else_if;
#                  они проверяются по порядку до получения истинного значения
#            Значение "test:" может быть массивом ключей if/else_if/else
#            или для удобства единственным набором if/then/else.
#              Если используется массив, первым элементом должен быть 'if',
#              промежуточными и иногда последним -- 'else_if',
#              а необязательным последним элементом -- 'else' или 'else_test'
#         if: <string> некоторое выражение XPath
#         then: [replacements]
#         then_test: [replacements] используется вместо 'then:', чтобы не добавлять 'test:' после 'then:'
#         else: [replacements] # необязательно
#         else_test: # необязательно; используется вместо 'else:', чтобы не добавлять 'test:' после 'else:'
#      - with:
#         variables: [name: value, ...] значения переменных, заданные во время выполнения этого предложения
#         replace: [replacements]
#      - intent:
#          name: string  имя правила intent
#          children: дочерние узлы правила intent
#      - insert:
#          nodes: XPath, вычисляемый в узлы
#          replace: [replacements] значения, вставляемые между всеми узлами
#      - translate: XPath позволяет озвучить выражение в середине правила; используется WhereAmI для навигации
#      - set_variables: [var: value, ...] определения глобальных переменных
#         Они доступны программе после выполнения правил. Сейчас используются для навигации, меняющей состояние.
#      - pause: строка или число  # "short", "medium", "long", "auto" или число миллисекунд
#      - rate: строка/число или словарь с одной или двумя записями
#         value: вещественное число с необязательным %
#         replace: [replacements] # значения TTS должны ограничивать область содержимого
#      - volume: строка/число или словарь с одной или двумя записями
#         value: вещественное число с необязательным %
#         replace: [replacements] # значения TTS должны ограничивать область содержимого
#      - pitch: строка/число или словарь с одной или двумя записями
#         value: вещественное число с необязательным %
#         replace: [replacements] # значения TTS должны ограничивать область содержимого
#      - gender: строка/число или словарь с одной или двумя записями
#         value: "male" # или "female"
#         replace: [replacements] # значения TTS должны ограничивать область содержимого
#      - voice: строка/число или словарь с одной или двумя записями
#         value: строка
#         replace: [replacements] # значения TTS должны ограничивать область содержимого
#      - spell: строка с XPath, обычно одной буквой, произносимой как буква: `"'a'"`
#      - bookmark: некоторый XPath в виде строки; возвращает 'id' для синхронного выделения
```

Примечание: для `pause` значение `auto` вычисляет длительность паузы с учётом сложности окружающих частей. Чем они сложнее, тем длиннее пауза, но не более установленного предела. Слушателю необходимо дать время осмыслить и разделить две части, если одна или обе достаточно сложны.

Помимо именованных правил, файл правил речи поддерживает подключение других файлов правил речи. Благодаря этому разные стили могут использовать общую функциональность. Подключение записывается вместо правила речи:
```
  - include: file_name
```
Файл может содержать любое количество подключений. Их содержимое обрабатывается так, как будто оно находилось в исходном файле. Подключаемый файл может находиться в текущем каталоге обрабатываемого файла правил или в каталоге по относительному пути.

## Файлы Unicode

Файлы Unicode — упрощённые версии правил речи. Они облегчают описание правил для символов Unicode и заметно ускоряют работу. Правила листовых элементов, например `mo`, переопределяют определения из файлов Unicode. Однако в целом правила речи для символов Unicode следует помещать в файл Unicode.

Как и правила речи, файлы Unicode имеют формат YAML. Основное отличие состоит в том, что для определения правила используется только символ. Задавать имя правила, имя тега и выражение сопоставления не требуется. Значением правила может быть любое допустимое значение `replace:` из правил речи.

Большинство правил очень просты. Например:
```
 - "+": [t: plus]                                # 0x2b
```
Это правило преобразует символ `+` в строку `plus`.

Более сложное правило:
```
 - "[":                                          # 0x5b
    - test:
        if: $SpeechStyle = 'ClearSpeak'
        then: [t: open bracket]
        else: [t: left bracket]                            
```
Результат этого правила зависит от текущей настройки стиля речи.

Файлы Unicode, как и файлы правил речи, можно совместно использовать с помощью `- include: file_name`.


## Файлы Prefs

Примечание: настройки, например настройки ClearSpeak, представлены словарём внутри записи `ClearSpeak` файла YAML. Это затрудняет задание и чтение значения.
Поэтому имя преобразуется в строку с разделителем `_`.
Например, _имя_ настройки `Fraction` раздела `ClearSpeak` — `ClearSpeak_Fraction`.
Именно его следует использовать для задания значения через API и доступа к значению в `ClearSpeak_Rules.yaml`.


## Файлы Definition


## XPath
Во многих частях правил речи используется XPath. Это распространённый и хорошо документированный способ выбора частей XML-документа. В интернете есть множество руководств. Если вы ещё не знакомы с XPath, рекомендуется прочитать одно из них. В MathCAT используется немного расширенная реализация XPath 1.0.

В MathCAT обычно применяются лишь некоторые возможности XPath, а также несколько специальных функций. Ниже кратко описаны распространённые варианты использования XPath.

| Запись | Значение |
| ----- | ---- |
| `*` | Соответствует всем дочерним узлам. |
| `[...]` | Выбирает узлы из текущего сопоставления. | 
| `*[1]` | Выбирает первый дочерний узел. |
| `*[self::m:mn]` | Выбирает все дочерние элементы `mn`. Префикс `m` показывает, что элемент находится в пространстве имён MathML. |
| `*[1][self::m:mn]` | Выбирает первый дочерний узел, если он является элементом `mn`. |
| `*[1][self::m:mo][text()='-']` | Выбирает первый дочерний узел, если он является элементом `mo` с содержимым `-`. Можно записать `*[1][text()='-']`, поскольку другие узлы, скорее всего, не содержат `-`, но такое содержимое допустимо для `mtext`, поэтому безопаснее указать имя элемента. |
| `count(*[2]/*)` | Количество дочерних узлов второго дочернего узла. |
| `count(preceding-sibling::*)+1` | Количество одноуровневых узлов перед текущим элементом плюс один. |

MathCAT добавляет специальные функции, упрощающие написание правил:

| Функция | Значение |
| ----- | ---- |
| `IsNode(nodes, type)` | Возвращает true, если все узлы имеют одинаковый тип. Допустимые типы:<br>`"simple"` — заданный набор элементов ClearSpeak;<br>`"leaf"` — один из листовых элементов MathML;<br>`"2D"` — двумерный узел, например `mfrac` или `mroot`;<br>`"modified"` — узел с индексом или элементом над ним либо под ним;<br>`"scripts"` — узел с подстрочным и (или) надстрочным индексом;<br>`"common_fraction"` — целочисленные числитель и знаменатель. |
| `ToOrdinal` | |
| `ToCommonFraction` | |
| `IsBracketed(openChar, closeChar, requiresComma)` | |
| `BaseNode(node)` | Рекурсивно возвращает основу узла с индексами. |
| `IsInDefinition(node, name)` | Возвращает true, если узел входит в список `name`, заданный в `definitions.yaml`. |
| `IfThenElse(test, then-part, else-part)` | Возвращает `then-part`, если условие истинно, иначе возвращает `else-part`. Все аргументы являются XPath. |
| `DistanceFromLeaf(node, left_side, treat_2d_elements_as_tokens)` | Возвращает расстояние от текущего узла до крайнего левого или правого листа: для символа 0, для токена 1. Если `left_side` равно `true`, обход выполняется по крайним левым дочерним узлам до листа. Если `treat_2d_elements_as_tokens` равно `true`, двумерные обозначения, например дроби, считаются листами. |
| `EdgeNode(node, "left"/"right", stopNodeName)` | Возвращает узел остановки, если исходный узел находится у левого или правого края указанного предка. Значением `stopNodeName` также может быть `"2D"`. Если соответствие не найдено, возвращается исходный узел. Если `stopNodeName=="math"`, учитывается пунктуация, поскольку она не является частью математического выражения. |
| `DEBUG(xpath)` | Добавляет полезные для диагностики сведения в отладочный вывод. |

Следующие функции используются правилами кода Немета:

| Функция | Значение |
| ----- | ---- |
| `NestingChars` | Используется правилами `mfrac`, `msqrt` и `mroot`, чтобы повторять символы нужное количество раз. |
| `BrailleChars` | Используется элементами-токенами для сложной перестановки индикаторов кода Немета, например прописных букв и начертания шрифта. |
