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

от автора

Не так давно передо мной встала задача иметь возможность хранить данные в текстовом виде, чтобы с ними работала не только программа, но мог прочитать и отредактировать (а также создать с нуля в текстовом редакторе) человек. Для этого уже существует множество удобных и хороших форматов, например 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 построен на моем видении решения задачи хранения текстовых полей в сериализованном виде, чтобы значения там находились в оригинальном, неизмененном виде и их можно было удобно читать и изменять в любом простом редакторе. Кто-то посчитает, что получилось удачное решение, кто-то — наоборот; может быть кто-то предложит свое. Кто-то считает, что проблема, затронутая в статье, не такая уж и проблема, что все и так удобно. Хотелось бы узнать Ваше мнение.

ссылка на оригинал статьи http://habrahabr.ru/post/271501/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *