Я хочу загрузить файл YAML, возможно отредактировать данные, а затем сбросить их снова. Как я могу сохранить форматирование? - PullRequest
1 голос
/ 27 марта 2020

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

Предположим, у меня есть файл YAML, подобный этому:

first:
  - foo: {a: "b"}
  - "bar": [1, 2, 3]
second: |   # some comment
  some long block scalar value

Я хочу загрузить этот файл в собственную структуру данных, возможно изменить или добавить некоторые значения и вывести его снова. Однако, когда я выкидываю его, исходное форматирование не сохраняется:

  • Скаляры отформатированы по-разному, например, "b" теряет кавычки, значение second не является литеральным скалярным блоком и т. д. c.
  • Коллекции форматируются по-разному, например, значение отображения foo записывается в стиле блока вместо данного стиля потока, аналогично значение последовательности "bar" записывается в стиль блока
  • Порядок ключей сопоставления (например, first / second) изменяется
  • Комментарий пропущен
  • Уровень отступа отличается, например, элементы в first больше не имеют отступов.

Как сохранить форматирование исходного файла?

1 Ответ

2 голосов
/ 27 марта 2020

Предисловие: В этом ответе я упоминаю некоторые популярные реализации YAML. Эти упоминания никогда не являются исчерпывающими, поскольку я не знаю всех реализаций YAML.

Я буду использовать термины YAML для структур данных: Atomi c текстовое содержимое (четные числа) - это скалярная . Последовательности элементов, известные в других местах как массивы или списки, представляют собой последовательности . Коллекция пар ключ-значение, известная в другом месте как словарь или ха sh, представляет собой отображение .

Если вы используете Python, рассмотрите возможность использования ruamel (возможно, переключение с PyYAML), так как он реализует циклическое переключение на собственные структуры, и большая часть этого ответа к нему не относится.

Фон

Процесс загрузки YAML также является процессом потери информации. Давайте посмотрим на процесс загрузки / выгрузки YAML, как указано в спецификации c:

Когда вы загружаете файл YAML, вы выполняете некоторые или все шаги в направлении Load , начиная с Presentation (Символьный поток) . Реализации YAML обычно продвигают свои API самого высокого уровня, которые загружают файл YAML вплоть до Native (Структура данных) . Это справедливо для большинства распространенных реализаций YAML, например, модуля YAML PyYAML / ruamel, SnakeYAML, go -yaml и Ruby. Другие реализации, такие как libyaml и yaml- cpp, обеспечивают десериализацию только до представления (Node Graph) из-за ограничений их языков реализации.

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

К счастью, эта диаграмма описывает только логический процесс загрузки YAML; соответствующая реализация YAML не должна рабски соответствовать ей. Большинство реализаций на самом деле сохраняют данные дольше, чем нужно. Это верно для PyYAML / ruamel, SnakeYAML, go -yaml, yaml- cpp, libyaml и других. Во всех этих реализациях стиль *1049* скаляров, последовательностей и отображений запоминается вплоть до уровня (Node Graph) level.

С другой стороны, комментарии отбрасываются довольно быстро, поскольку они не принадлежат событию или узлу (здесь исключение составляет ruamel, который связывает комментарии со следующим событием). Некоторые реализации YAML (libyaml, SnakeYAML) предоставляют доступ к потоку токенов, который находится даже на более низком уровне, чем Дерево событий . Этот поток токенов содержит комментарии, однако его можно использовать только для подсветки синтаксиса, поскольку API не содержат методов для потребления потока токенов снова.

Так что же делать?

Загрузка и выгрузка

Если вам нужно только загрузить свой файл YAML и затем снова выгрузить его, используйте один из низкоуровневых API вашей реализации, чтобы загружать YAML только до Представление (Node Graph) или Сериализация (Дерево событий) уровень. Функции API для поиска: compose / parse и serialize / present соответственно.

Предпочтительно использовать Дерево событий вместо Node Graph , поскольку некоторые реализации уже забывают исходный порядок ключей сопоставления (из-за внутреннего использования hashmaps), когда композиторский . Этот вопрос , например, детализирует события загрузки / выгрузки с помощью SnakeYAML.

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

Также обратите внимание, что стиль хранения не идеален и не может быть действительно. Например, возьмите этот скаляр:

"1 \x2B 1"

Эта загрузка в виде строки "1 + 1" после разрешения escape-последовательности. Даже в потоке событий информация о escape-последовательности уже потеряна во всех известных мне реализациях. Событие только помнит, что это был скаляр с двойными кавычками, поэтому его обратная запись приведет к:

"1 + 1"

Аналогично, скаляр со сложенными блоками (начиная с >) обычно не будет помнить, где разрывы строк в исходном вводе были сложены пробелы.

Итак, для подведения итогов, загрузка в Дерево событий и повторный сброс обычно сохраняют:

  • Стиль: не цитируемые / цитируемые / блочные скаляры, коллекции потоков / блоков (последовательности и отображения)
  • Порядок ключей в отображениях
  • Теги YAML

Обычно вы теряете :

  • Информация о escape-последовательностях и разрывах строк в скалярах потока
  • Отступы и интервалы без содержимого
  • Комментарии

Если вы используйте Node Graph вместо дерева событий , вы можете дополнительно потерять порядок ключей в отображениях. Некоторые API, такие как go -yaml, не предоставляют доступ к Дереву событий , поэтому у вас нет другого выбора, кроме как использовать Node Graph .

Изменение данных

Если вы хотите изменить данные и при этом сохранить то, что вы можете от исходного форматирования, вам нужно манипулировать данными, не загружая их в собственную структуру. Обычно это означает, что вы оперируете скалярами, последовательностями и отображениями, и не так, как вы привыкли к строкам , числам , спискам или любым другим структурам цели Язык программирования обеспечивает.

У вас есть возможность обработать Дерево событий или График узлов (при условии, что ваш API дает вам доступ к нему). Какой из них лучше, обычно зависит от того, что вы хотите сделать:

  • Дерево событий обычно предоставляется в виде потока событий. Это может быть лучше для больших данных, так как вам не нужно загружать полные данные в память; вместо этого вы проверяете каждое событие, отслеживаете свою позицию в структуре ввода и соответственно вносите свои изменения. Ответ на на этот вопрос показывает, как добавлять элементы, дающие путь и значение, к указанному файлу YAML с помощью API событий PyYAML.
  • График узлов лучше для высокоструктурированные данные, а также если вы используете якоря и псевдонимы в YAML, потому что они там разрешены. В отличие от событий, когда вам нужно отслеживать текущую позицию самостоятельно, здесь данные представлены в виде полного графика, и вы можете просто перейти к соответствующим разделам (с событиями вам, возможно, потребуется пролистать большие подструктуры, в которых вы не заинтересованы. все).

В любом случае вам нужно немного узнать о разрешении типа YAML для корректной работы с данными. Когда вы загружаете файл YAML в объявленную собственную структуру (типично для языков с системой типа stati c, например, Java или Go), процессор YAML отобразит структуру YAML, если это возможно. Однако, если целевой тип не указан (типично для языков сценариев, таких как Python или Ruby, но также возможно для Java), типы выводятся из содержимого и стиля узла.

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

  • Когда вам нужно выбрать тип скалярного узла или события, например, у вас есть скаляр с контентом 42 и вам необходимо знать, является ли это строка или целое число .
  • Когда вам нужно создать новое событие или узел, который впоследствии должен быть загружен как специфицированный c тип. Например, если вы добавите строку "42", вы должны убедиться, что она не загружена как целое число 42 позже.

Я не буду обсудить все детали здесь; в большинстве случаев достаточно знать, что если строка закодирована как скаляр, но выглядит как-то иначе (например, число), вы должны использовать цитируемый скаляр.

В зависимости от вашего реализации, вы можете связаться с YAML тегами . Редко используется в файлах YAML (они выглядят, например, как !!str, !!map, !!int и т. Д.), Они содержат информацию о типе узла, которую можно использовать в коллекциях с разнородными данными. Что еще более важно, YAML определяет, что всем узлам без явного тега будет назначен один как часть разрешения типа. Это может случиться или не происходить на уровне Node Graph . Таким образом, в данных вашего узла вы можете увидеть тег узла, даже если у исходного узла его нет.

Теги, начинающиеся с двух восклицательных знаков, на самом деле сокращенные , например, !!str - это стенография для tag:yaml.org,2002:str. Вы можете увидеть и то, и другое в своих данных, поскольку реализации обрабатывают их совершенно по-разному.

Для вас важно то, что при создании узла или события вам может потребоваться, а также может потребоваться назначить тег. Если вы не хотите, чтобы выходные данные содержали явный тег, используйте неспецифические теги c ! для непростых скаляров и ? для всего остального на уровне события. На уровне узла обратитесь к документации вашей реализации, чтобы узнать, нужно ли вам предоставлять разрешенные теги. Если нет, то применяется то же правило для неуказанных тегов c. Если документация не упоминает об этом (мало кто из них), попробуйте.

Итак, подведем итог: Вы изменяете данные, загружая либо Дерево событий , либо Узел График , вы добавляете, удаляете или изменяете события или узлы в полученных вами данных, а затем представляете измененные данные снова в виде YAML. В зависимости от того, что вы хотите сделать, это может помочь вам создать данные, которые вы хотите добавить в свой файл YAML, в качестве собственной структуры, сериализовать их в YAML и затем загрузить снова как График узла или Дерево событий . Оттуда вы можете включить его в структуру файла YAML, который вы хотите изменить.

Заключение / TL; DR

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

Этот контраст является причиной, по которой так сложно изменять файлы YAML при сохранении форматирования: формат YAML был разработан как переходный формат данных, для записи одним приложением, а затем для загрузки другим (или тем же) приложением. В этом процессе сохранение форматирования не имеет значения. Это относится, однако, к данным, которые возвращаются в систему управления версиями (вы хотите, чтобы ваш diff содержал только строки с данными, которые вы фактически изменили), и другим ситуациям, когда вы пишете свой YAML вручную, потому что вы хотите придерживайтесь стиля.

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

Если сохранение формата важно для вас, и вы не можете жить с компромиссами, сделанными предложения в этом ответе, YAML не подходит для вас.

...