Как стать автором
Обновить

Формат хранения данных HV как попытка решения проблемы наглядного хранения текстовых полей

Время на прочтение7 мин
Количество просмотров6.6K


Не так давно передо мной встала задача иметь возможность хранить данные в текстовом виде, чтобы с ними работала не только программа, но мог прочитать и отредактировать (а также создать с нуля в текстовом редакторе) человек. Для этого уже существует множество удобных и хороших форматов, например JSON, YAML, XML и так далее. Но в рассмотренных системах попадались моменты, которые, все же, немного не понравились.

Уделю особенное внимание яркому неудобству большинства таких форматов (естественно, на мой взгляд), в том числе и очень мощных и популярных, — проблеме, связанной с хранением текста: как записать текстовое поле, которое может содержать любые текстовые символы, чтобы его содержимое не приходилось менять, и оно не повлияло на парсинг, ведь там могут встретиться и различные подстроки, совпадающие со служебными комбинациями, и различные нестандартные отступы. Например, в XML текст не должен содержать знаков "<" и ">" — они должны быть заменены на "&lt;" и "&gt;" соответственно. Во многих других системах текст требуется заключать в кавычки. Но что делать, если текст уже содержит кавычки? Использовать другие типы кавычек? Экранировать? Все это означает, что надо вносить изменения в текст, и далеко не факт, что после этого будет удобно читать и редактировать его, если предстоит работа с данными в обычном текстовом редакторе, например, блокноте или полем ввода (textarea) в браузере. Еще есть формат YAML, в котором текст в кавычки не требуется заключать, но там очень важно соблюдать правильные отступы, что для хранения многострочных и многоуровневых данных кажется не очень удобным. Также это увеличивает долю символов, не относящихся к данным — несколько служебных пробелов слева на каждой строке существенно увеличивают вес.

Помимо текста, мне нужно было хранить, по сути, еще два базовых типа данных — целое и дробное число, а также объединения (структуры данных (блоки) и массивы). То есть получается 5 типов: число целое, число с плавающей точкой, текст, структура, массив. Не было необходимости в использовании макросов, выражений и прочих расширений — нужны были просто распределенные по различным блокам и массивам числа и тексты. В связи с такой простотой нужен был максимально тривиальный формат, который мог хранить, ко всему прочему, текстовые поля, учитывая моменты, которые оговаривались выше. Также хотелось видеть данные с как можно меньшим количеством управляющих символов, чтобы было проще понять и запомнить синтаксис.

В общем, был создан велосипед с необычной конструкцией руля формат HV (изначальное внутреннее название — «human values»). На нем я покажу практическое решение указанной проблемы, как это решение вижу я. Формат получился незамудренным — что, в принципе, и требовалось — как уже говорилось, поддерживает всего три простых типа данных (целое число, число с плавающей точкой и текст) и два составных типа (структура данных и массив, которые содержат в себе как простые типы, так и составные). Основных управляющих символов всего 3. Есть еще 3 дополнительных управляющих символа, но это для особых случаев форматирования текстовых полей, а также для обозначения комментариев. Эти случаи относятся к поставленному в статье вопросу (об удобном хранении текстовых полей) и будут рассмотрены ниже на примерах.

Поля данных могут быть однострочными (целое число, число с плавающей точкой, однострочный текст) и многострочными (структура, массив, многострочный текст). Сначала пишется имя поля, затем управляющий символ, который указывает, что значение поля занимает либо одну строку, либо несколько. А затем — значение поля. Если несколько строк, то в конце значения указывается завершающая строка. Собственно, это и есть основная суть формата, изложенная в кратком виде.

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

a: 1
b: 2.2
c: abcd


Здесь представлены 3 простых типа данных:
a — целое число, равное 1
b — число с плавающей точкой, равное 2.2
c — текстовое поле, состоящее из одной строки, равной «abcd»

В следующем примере структура данных и массив, который находится в этой структуре:

xxq+
  a: 12.33
  b: -15
  x+
    : ab
    : cd
    : ef
  ^
^


Здесь структура содержит два поля:
a — число с плавающей точкой, равное 12.33
b — целое число, равное -15
x — массив текстовых полей, которые равны «ab», «cd» и «ef»
Для элементов массива имя поля не пишется.

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

xxq+
a:    12.33
b:       -15
x+
: ab
:      cd
:ef
            ^
^


И вариант представление тех же данных, но вообще без пробелов:

xxq+
a:12.33
b:-15
x+
:ab
:cd
:ef
^
^


Итак, самые главные управляющие символы — ":" (если значение занимает одну строку) и "+" (если значение занимает несколько строк).

А теперь, непосредственно, мое видение решения вопроса представления многострочного текста, содержащего разнообразные символы:

t+
  ABCD
  EFGH<12>@@
  ijklmnopq
  "ABC" + "DEF" = "ABCDEF"
  "A('a')" =//= "B"('''')\
  abcd
^


В данном примере текст получается таким:

ABCD
EFGH<12>@@
ijklmnopq
"ABC" + "DEF" = "ABCDEF"
"A('a')" =//= "B"('''')\
abcd


Кавычки, слеши и прочие символы, содержащиеся в тексте, никак не подменяются и не экранируются — в этом нет необходимости. То есть, текст остается полностью оригинальным и не требует дополнительных преобразований.

Ограничивается текст завершающей строкой. Завершающая строка по умолчанию равна управляющему символу "^". Эта же строка используется для завершения всех многострочных полей, таких как структуры и массивы (показано на примерах выше). Значение будет считываться построчно без учета отступов, пока не встретится завершающая строка. Именно не подстрока, а строка целиком (отступы, как я уже говорил, игнорируются и могут быть любыми).

При записи текстовых полей может возникнуть два вполне резонных вопроса:

1) Что если в исходном тексте встретится строка, которая будет равна завершающей, то есть "^"?
2) Что если отступы в тексте важны и их нельзя игнорировать?

Для разрешения первого случая формат HV позволяет переопределять завершающую строку. Ее просто нужно указать перед значением поля, ну и, соответственно, после:

eee+ END
  hello
  ^
  ^
  ^
  ^
  abcd
END


Текст, содержащийся в поле «eee», такой:

hello
^
^
^
^
abcd


Важный ньюанс — переопределение завершающих строк возможно только для текстовых полей. Остальных многострочные значения (структуры и массивы) всегда завершаются служебным символом "^".

Для разрешения второго случая (отступы имеют значение) HV имеет целых 2 варианта.
Вариант А. Учитывать все отступы справа и слева от текста в каждой строке:

text@
  Это красная строка.
А это обычная строка.
Все отступы от начала строки будут сохранены
   в тексте.
                      Вот так.
^


Вариант Б. Начинать учитывать отступы от первого непробельного символа в каждой строке, причем сам этот первый символ учитываться не будет:

text%
  -А
   *Б
    =В гдеёжзи
^


Текст получится следующим:

А
Б
В гдеёжзи


Интересная особенность - инкапсуляция сериализованных данных в виде текста
Хочу обратить внимание на еще одну особенность, которая хоть и довольно интересная, полезная и почти уникальная, но необходимость в ее применении достаточно редко встречается. Эта особенность автоматически становится доступна благодаря возможности замены завершающей строки, тем самым оставляя оригинальный текст без изменений. Смысл в том, что так можно вставлять одни данные в формате HV в качестве текстового поля в другие данные формата HV. Это не приведет ни к каким к синтаксическим ошибкам при парсинге. Пригодится это может том случае, если имеется несколько обработчиков текстов, находящихся на разных уровнях, и они не знают, в каком формате каждый из них работает — они просто передают текст на следующий уровень.
Например для первого уровня надо передать два массива в формате HV:

a+
  : 1
  : 2
^
b+
  : 3
  : 4
^


Но это надо передать в виде текста через второй уровень:

level_2+
  for_level_1+ &
    a+
      : 1
      : 2
    ^
    b+
      : 3
      : 4
    ^    
  &
^


Поле «for_level_1» является текстовым. Здесь просто заменяется завершающая строка на "&".
Распарсить данные, предназначенные для первого уровня, сразу на втором уровне нельзя по условиям примера — второй уровень не знает, как должен обрабатываться этот текст — может там HV, может JSON, а может просто текст, не предназначенный для парсинга. Это решает первый уровень (по условиям примера).

То есть, в текстовом поле HV можно передать любые сериализованные данные — хоть тот же HV, хоть JSON, XML, YAML и так далее. Возможности безопасной инкапсуляции без редактирования текста я не встретил ни в одном из рассмотренных форматов. Эта фича хоть и редко где может понадобиться, но все-таки.


Итак, основных ключевых символов получилось 3 штуки:

: — значение в одной строке
+ — значение в нескольких строках
^ — конец многострочного значения

И 3 дополнительных:

@ — форматированный многострочный текст
% — размеченный многострочный текст
# — комментарий

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

При первом рассмотрении HV может показаться похожим на YAML — тоже минималистичный, тоже текст без кавычек. Но, поскольку HV создавался с нуля, а не на основе какого-либо существующего формата, то различий с YAML больше, чем сходств. HV нетребователен к отступам. Общая доля служебного текста в HV формате меньше, потому что YAML требует соблюдение отступов и часто использует комбинации, состоящие из 2-х и более символов, например "---", ": |-", ": >", а HV — всегда только одиночные символы. Ну а механизма, который ограничивает текст переопределеяемой завершающей строкой, — я не встретил ни в одном из рассмотренных форматов. А как мне кажется, это достаточно удобный и наглядный механизм.

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

Надеюсь, я правильно смог изложить причины создания формата HV и его особенности. Если что-то все-таки недообъяснил — буду рад ответить на адекватные вопросы.

Для тех, кто хочет ознакомиться получше с форматом HV, на ресурсе http://vaomark.com/z23F0Cz размещено более подробное описание и куча примеров, охватывающих все стороны.

Там же можно скачать актуальный исходный код обработчика HV и модуль тестирования на Python 2.7. Кстати, в скором времени планируется портировать обработчик на C++, Java, PHP и другие языки — все будет доступно все по той же ссылке.

P.S.: Формат HV построен на моем видении решения задачи хранения текстовых полей в сериализованном виде, чтобы значения там находились в оригинальном, неизмененном виде и их можно было удобно читать и изменять в любом простом редакторе. Кто-то посчитает, что получилось удачное решение, кто-то — наоборот; может быть кто-то предложит свое. Кто-то считает, что проблема, затронутая в статье, не такая уж и проблема, что все и так удобно. Хотелось бы узнать Ваше мнение.
Теги:
Хабы:
Всего голосов 20: ↑13 и ↓7+6
Комментарии33

Публикации