PTTJS - plain text table javascript, формат разработанный из личной необходимости и острой нужды.

Публикую в народ, потому что уверен, что не меня одного волнуют ограничения и проблемы текущих форматов для табличных данных.

Уже написаны JS библиотека с парсером и сериализатором, а также Obsidian плагин.

Цель

Главная цель формата PTTJS - получить текстовый формат таблиц, который позволяет хранить более сложные таблицы, нежели существующие форматы (CSV, MD), но при этом сохранять читаемость и текстовую основу.

Мотивация

Мотивация для разработки копилась в течении долгого времени, ведь Я как и все нормальные люди готов мириться с некоторыми неудобствами. Но недавно терпение кончилось и пришлось взяться за проработку и разработку.

Решающими стали факторы:

  1. Необходимость передавать в LLM более сложные таблицы нежели обычные CSV или MD форматы. Очень часто в документах встречаются сложносоставные таблицы, которые невозможно перевести в текстовый формат. Из-за чего приходится использовать более сложные и тяжелые мультимодальные модели.

  2. Необходимость распознавать сложносоставные таблицы с помощью CV возвращая простой текст, а ни какой-нибудь многовложенный JSON, который ещё нормально не перепроверишь, пока не отрендеришь.

  3. Необходимость обучать LLM работе целиком с таблицей, а не через костыли внутри Google Shets или Microsoft Excel. Дать возможность LLM-ке самой крутить стобцы, строки, формулы, как ей понадобится.

  4. Невозможность открыть большинство, даже простых таблиц, которые, за неимением аналогов, все хранят в XLS(X) или ODT, без специального ПО.

  5. Желание работать с более сложными таблицами в текстовых редакторах и ПО по типу Obsidian.

  6. Личная неприязнь к Google Shets и Microsoft Excel (надеюсь Я не один такой).

проблема указанная в 1 и 2 пунктах, попробуйте нормально распознать и вставить в текст
проблема указанная в 1 и 2 пунктах, попробуйте нормально распознать и вставить в текст

Описание формата

Первая строка всегда отдана под аннотацию:
|PTTJS 1.0|encoding=UTF‑8|\n

Страницы

Каждая таблица лежит на странице:

|(@P1|Название страницы){\n - начало страницы, в скобках: id страницы начинается с символа "@" и название страницы.

}|\n - конец страницы.

Обозначения страницы опциональны, если их нет, вся таблица на одной странице.

Ячейки

Вся таблица состоит из ячеек и обозначения конца строки:

|H([1|1]1|1|@C1)> - начало ячейки, H - пометка, что это заголовок, в круглых скобках: индекс ячейки по X и Y в квадратных скобках, масштаб (scale) ячейки по X и Y и id ячейки (начинается с символа "@").

<|\n - конец строки.

H - опциональная пометка, что ячейка - это заголовок.

Индекс ячейки по X и Y - опционален, при обработке просто заполняется итеративно по ячейкам и строкам, начиная с [0|0]. Можно выгрузить при необходимости не визуальной обработки (в скрипте или c помощью AI).

Масштаб (scale) ячейки по X и Y - опционален и по умолчанию равен 1|1.
Масштаб всегда указывается в верхней левой ячейке.

Id ячейки - опционален, нужен для ссылок и направленных формул.

Даже если в строке нет значений, но после неё есть строки, надо указать в строке хотя бы одну пустую ячейку |><|.

Данные внутри ячеек не должны содержать символов ответственных за указание новой ячейки, конца строки, фигурных скобочек и переноса строки ("\n", "|", ">", "<", "{", "}").

Все эти символы должны быть заменены на URL Encode аналоги:

Новая строка (\n) -> %5Cn
Вертикальная черта (|) -> %7C
> -> %3E,
< -> %3C,
{ -> %7B,
} -> %7D,

чтобы парсер валидно воспринимал данные.

В библиотеке в serializer.js есть функция escapeValue, которая может заменить символы на URL Encode вариант.

В библиотеке в parser.js есть функция unescapeValue, которая может заменить символы обратно из URL Encode варианта в обычный вид.

Скрипты

>>>SCRIPT\n - начало блока скриптов

<<<SCRIPT\n - конец блока скриптов

Блок скриптов всегда находится после всех данных и может работать с данными на разных листах.

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

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

Можно определять свои функции в редакторе.

Стили присваиваются с помощью символа "<=" и описываются в CSS подобном формате (тут также будут срабатывать JS функции для стилизации).

Форматы ячеек присваиваются символом "=>" и указывают на формат данных в ячейках (это тоже JS функции).
Например NUMBER(2,' '), первый параметр это decimals - количество цифр после запятой, а второй - разделитель тысяч, в нашем случае пробел ' '.

Можно добавлять скрипты для всей строки или столбца или вообще всего диапазона с помощью двойного указания индекса через двоеточие ":", например вся первая строка (0:0|0), или весь первый столбец (0|0:0) и т.д.

Скрипты это опциональный блок.

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

При выгрузке - скрипты можно сохранять отдельным файлом, например с суффиксом ".script".

Примеры

Пример простой таблицы, например для хранения данных о машинах:

|PTTJS 1.0|
|H>Номер машины|H>Год выпуска|H>Марка модель<|
|>080XXX02|>2005|>LEXUS RX 350<|
|>787XXX16|>2015|>GEELY GC7<|
|>871XXX05|>1997|>TOYOTA IPSUM<|
|>A602XXX|>1996|>MITSUBISHI PAJERO<|
|>890XXX02|>1997|>TOYOTA LAND CRUISER PRADO<|
|>216XXX13|>2007|>DAEWOO NEXIA<|

Пример простой таблицы, c индексами:

|PTTJS 1.0|encoding=UTF‑8|
|H([0|0])>Номер машины|H([1|0])>Год выпуска|H([2|0])>Марка модель<|
|([0|1])>080XXX02|([1|1])>2007|([2|1])>LEXUS RX 350<|
|([0|2])>787XXX16|([1|2])>2015|([2|2])>GEELY GC7<|
|([0|3])>871XXX05|([1|3])>1997|([2|3])>TOYOTA IPSUM<|
|([0|4])>A602XXX|([1|4])>1996|([2|4])>MITSUBISHI PAJERO<|
|([0|5])>890XXX02|([1|5])>1997|([2|5])>TOYOTA LAND CRUISER PRADO<|
|([0|6])>216XXX13|([1|6])>2007|([2|6])>DAEWOO NEXIA<|

Пример таблицы cо "сложным" заголовком:

|PTTJS 1.0|encoding=UTF‑8|
|H(1|2)>Номер машины|H(2|1)>Данные о машине|H><|
|H>|H>Год выпуска|H>Марка модель<|
|>080XXX02|>2007|>LEXUS RX 350<|
|>787XXX16|>2015|>GEELY GC7<|
|>871XXX05|>1997|>TOYOTA IPSUM<|
|>A602XXX|>1996|>MITSUBISHI PAJERO<|
|>890XXX02|>1997|>TOYOTA LAND CRUISER PRADO<|
|>216XXX13|>2007|>DAEWOO NEXIA<|
визуализация из Obsidian
визуализация из Obsidian

В этом примере объединены первые ячейки первых двух строк, с заголовком "номер машины" и объединены вторая и третья ячейки первой строки с заголовком "Данные о машине".

Объединенные ячейки остаются. Это нужно чтобы не потерять позиционную информацию.

Пример простой таблицы, c индексами и скриптами:

|PTTJS 1.0|encoding=UTF‑8|
|H([0|0])>Номер машины|H([1|0])>Год выпуска|H([2|0])>Марка модель<|
|([0|1])>080XXX02|([1|1])>2007|([2|1])>LEXUS RX 350<|
|([0|2])>787XXX16|([1|2])>2015|([2|2])>GEELY GC7<|
|([0|3])>871XXX05|([1|3])>1997|([2|3])>TOYOTA IPSUM<|
|([0|4])>A602XXX|([1|4])>1996|([2|4])>MITSUBISHI PAJERO<|
|([0|5])>890XXX02|([1|5])>1997|([2|5])>TOYOTA LAND CRUISER PRADO<|
|([0|6])>216XXX13|([1|6])>2007|([2|6])>DAEWOO NEXIA<|
|([0|7])><|
|([0|8])>Средний год выпуска|([1|8])>2003<|

>>>SCRIPT
(1|1,1|6)=>NUMBER(2,' ')
(1|8)=DIV(SUM(1|1,1|6),COUNT(1|1,1|6))
(0|8:8)<=BORDER(each,2,solid,#000)
<<<SCRIPT

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

Ещё указаны форматы ячеек во втором столбце со 2 по 7 строках, это числовой формат.

Также мы присвоили CSS стили диапазону ячеек, а именно обводку каждой ячейки черного цвета и толщиной 2.

Планы

  1. Предстоит доделать срабатывание функций из блока скриптов на объекте Store, который получается после парсинга.

  2. Добавить функции преобразования форматов (XSL(S), ODT, CSV, MD <-> PTTJS)

  3. Добавить библиотеки для других языков.

  4. Сделать легкий веб интерфейс для работы с PTTJS.

Сотрудничество (контрибьютинг)

GitHub репозиторий открыт, можете предлагать изменения.

Заключение

На этом всё, спасибо что уделили время, надеюсь смог помочь решить чьи-то аналогичные проблемы.

Комментарии (14)


  1. ncix
    11.05.2025 17:51

    Без обид, но мне кажется даже жуткий xml в xlsx лучше читается

    |><|

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

    >>>SCRIPT

    Почему именно три угловые скобки? А что если к скрипту понадобятся атрибуты? Язык, версия и т.п.


    Может проще прямо на JS описывать таблицы любой сложности?


    1. kolkoni Автор
      11.05.2025 17:51

      жуткий xml в xlsx

      Как вы читаете бинарный xlsx?

      А зачем эти угловые скобки?
      Почему именно три угловые скобки?

      Ответ простой - Я так решил.

      А что если к скрипту понадобятся атрибуты

      В статье написано.

      даже жуткий xml

      потом поделитесь счетом, сколько ушло денег на скрамливание XML в LLM. При чем тут LLM написано в статье.

      Может проще прямо на JS описывать таблицы любой сложности?

      Нет. И даже в HTML нет.


      1. ncix
        11.05.2025 17:51

        Как вы читаете бинарный xlsx?

        XLSX - это обычный zip-архив, внутри папки, XML-ки, картинки и прочий контент. Табличные данные хранятся в XML.


        1. kolkoni Автор
          11.05.2025 17:51

          И вы прям в текстовом редакторе его открываете и читаете? Или нужно сначала ряд операций провести?
          Можно пример Вашего более читаемого xml в xlsx? Вот ту же таблицу из моего примера.


    1. aborouhin
      11.05.2025 17:51

      жуткий xml в xlsx лучше читается

      Ещё бы в нём строковые значения за каким-то лешим не были вынесены в отдельный файл, в котором их надо подсматривать по индексам из основного файла листа, - вот тогда читался бы он нормально :)


      1. ncix
        11.05.2025 17:51

        поэтому - жуткий ))


  1. aborouhin
    11.05.2025 17:51

    Задачу читабельного текстового представления достаточно сложных таблиц неплохо решает AsciiDoc. В нём из коробки нет форматов данных и формул, но есть механизм расширений, который, насколько я понимаю, позволит это добавить. При этом можно было бы использовать уже существующие инструменты для конвертации из Excel, преобразования в кучу форматов и пр.