Разбор конкретных JSON-подобных данных (NextSTEP PList) из Ruby

Я пишу клиента в сторонний API, и они предоставляют данные в странном формате. Сначала это может выглядеть как JSON, но это не так, и я немного озадачен тем, как мне это делать.

Это формат, основанный на значениях ключей (очень похожий на JSON).

  • Ключи отделяются символом "=" от своих значений.
  • Ключи и значения заключены в двойные кавычки.
  • Словари начинаются с "{" и заканчиваются на "}".
  • Массивы начинаются с '(' и заканчиваются на ')'
  • Строки заканчиваются на ";" (Исключается для содержимого массивов) и символа конца строки (\r i think).
  • Иногда кажется, что в строках есть Unicode (например, \U2623 для знака BioHazard).

Что может быть в этом формате? Должен ли я использовать готовый драгоценный камень, чтобы разобрать его, или я должен создать свой собственный парсер?

{ "anArray" = (
  "100",
  "200",
  "300"
  );
  "aDictionary" = {
    "aString" = "Something";
  };
}

РЕДАКТИРОВАТЬ Этот формат, кажется, список свойств Apple, но это не XML и не Binary... Это имеет смысл, поскольку API от веб-службы WebObjects. Я попытаюсь использовать гем CFPropertyList для его анализа, если есть лучшее решение, пожалуйста, дайте мне знать.

РЕДАКТИРОВАТЬ 2 Это список свойств NextSTEP.

2 ответа

Решение

Вот надежный ответ с использованием собственного анализатора на основе StringScanner. Это позволяет использовать пробелы в качестве необязательного, позволяет использовать запятые после последнего элемента в списке и позволяет пропустить точку с запятой после последней пары ключ / значение словаря. Это позволяет внешнему элементу быть словарем, массивом или строкой. И это позволяет действительно любой вид легального содержимого строки, в том числе скобки и фигурные скобки и экранированный текст, как \n,

Видно в действии:

p parse('{ "array" = ( "1", "2", ( "3", "4" ) ); "hash"={ "key"={ "more"="oh}]yes;!"; }; }; }')
#=> {"array"=>["1", "2", ["3", "4"]], "hash"=>{"key"=>{"more"=>"oh}]yes;!"}}}

puts parse('("Escaped \"Quotes\" Allowed", "And Unicode \u2623 OK")')
#=> Escaped "Quotes" Allowed
#=> And Unicode ☣ OK

Код:

require 'strscan'
def parse(str)
  ss, getstr, getary, getdct = StringScanner.new(str)
  getvalue = ->{
    if    ss.scan /\s*\{\s*/   then getdct[]
    elsif ss.scan /\s*\(\s*/   then getary[]
    elsif str = getstr[]       then str
    elsif ss.scan /\s*[)}]\s*/ then nil end
  }
  getstr = ->{
    if str=ss.scan(/\s*"(?:[^"\\]|\\u\d+|\\.)*"\s*/i)
      eval str.gsub(/([^\\](?:\\\\)*)#(?=[{@$])/,'\1\#')
    end
  }
  getary = ->{
    [].tap do |a|
      while v=getvalue[]
        a << v
        ss.scan /\s*,\s*/
      end
    end
  }
  getdct = ->{
    {}.tap do |h|
      while key = getstr[]
        ss.scan /\s*=\s*/
        if value=getvalue[] then h[key]=value; ss.scan(/\s*;\s*/) end
        end
      end
    end
  }
  getvalue[]
end

В качестве альтернативы созданию собственного парсера с нуля в будущем вы также можете обратиться к библиотеке Treetop Ruby.


Изменить: я заменил реализацию getstr выше с тем, который должен предотвратить запуск произвольного кода Ruby внутри eval, Для получения дополнительной информации см. "Оценить строку без интерполяции". Видно в действии:

@secret = "OH NO!"
$secret = "OH NO!"
@@secret = "OH NO!"
puts parse('"\"#{:NOT&&:very}\" bad. \u262E\n#@secret \\#$secret \\\\#@@secret"')

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

def parse(str)
  eval(
    str
      .gsub( /" = (?=[({"])/, '" => ' )      # Dictionary separators become =>
      .gsub( /(?<=[)}"]); (?=[)}"])/, ', ' ) # Dictionary semicolons become ,
      .tr( '()', '[]' )                      # ALL parens become square brackets
  )
end

p parse('{ "anArray" = ( "100", "200", "300" ); "aDictionary" = { "aString" = "Something"; }; }')
#=> {"anArray"=>["100", "200", "300"], "aDictionary"=>{"aString"=>"Something"}}
Другие вопросы по тегам