Marshalling/unmarshalling XML в Scala
Я рассматриваю различные подходы к сортировке/распаковке данных между Scala и XML, и я заинтересован в получении обратной связи с сообществом (желательно, основываясь на знаниях и опыте из первых рук).
В настоящее время мы используем JAXB, и это нормально, но я надеюсь на чистое решение Scala. Я рассматриваю следующие подходы:
Вопросы:
- Каковы ваши впечатления от подходов/библиотек, которые я перечислил?
- Каковы относительные преимущества и недостатки каждого?
- Есть ли другие подходы или библиотеки Scala, которые я должен учитывать?
Edit:
Я добавил несколько заметок о моих ранних впечатлениях о компиляторах сортировщиков в своем собственном ответе на этот вопрос, но мне все еще очень интересно обращать внимание на кого-то, кто действительно знает различные подходы в глубину. Я надеюсь, что это будет несколько всеобъемлющее сравнение, которое поможет разработчикам выбрать правильный подход для своих нужд.
Ответы
Ответ 1
Я рекомендую использовать встроенные функции XML Scala. Я только что применил десериализацию структуры документа, которая выглядит так:
val bodyXML = <body><segment uri="foo"><segment uri="bar" /></segment></body>
Обратите внимание, что сегменты могут быть вложены друг в друга.
Сегмент реализуется следующим образом:
case class Segment(uri: String, children: Seq[Segment])
Чтобы десериализовать XML, вы делаете это:
val mySegments = topLevelSegments(bodyXML)
... и реализация topLevelSegments
- это всего лишь несколько строк кода. Обратите внимание на рекурсию, которая прорывается через структуру XML:
def topLevelSegments(bodyXML: Node): Seq[Segment] =
(bodyXML \ "segment") map { nodeToSegment }
def nodeToSegment = (n: Node) => Segment((n \ "@uri")(0) text, childrenOf(n))
def childrenOf(n: Node): Seq[Segment] = (n \ "segment") map { nodeToSegment }
Надеюсь, что это поможет.
Ответ 2
Для сравнения я внедрил пример Дэвида с использованием комбинаторов сортировщиков из GData Scala Клиент:
def segment: Pickler[Segment] =
wrap(elem("segment",
attr("uri", text)
~ rep(segment))) { // rep = zero or more repetitions
// convert (uri ~ children) to Segment(uri, children), for unpickling
Segment.apply
} {
// convert Segment to (uri ~ children), for pickling
(s: Segment) => new ~(s.uri, s.children toList)
}
def body = elem("body", rep(segment))
case class Segment(uri: String, children: List[Segment])
Этот код - это все, что необходимо для указания обоих направлений перевода между Segment
и XML, тогда как аналогичный объем кода определяет только одно направление перевода при использовании библиотеки Scala XML. На мой взгляд, эту версию также легче понять (как только вы узнаете DSL). Конечно, как отметил Дэвид в комментарии, этот подход требует дополнительной зависимости и другого DSL, с которым разработчики должны быть знакомы.
Перевод XML в сегменты так же просто, как
body.unpickle(LinearStore.fromFile(filename)) // returns a PicklerResult[List[Segment]]
и перевод другого способа выглядит как
xml.XML.save(filename, body.pickle(segments, PlainOutputStore.empty).rootNode)
Что касается библиотеки комбинаторов, она, похоже, находится в приличной форме и компилируется в Scala 2.8.1. Мое первоначальное впечатление заключается в том, что в библиотеке отсутствует несколько тонкостей (например, комбинатор oneOrMore
), которые можно было бы устранить довольно легко. У меня не было времени, чтобы понять, насколько хорошо он справляется с плохим вводом, но пока он выглядит достаточно для моих нужд.
Ответ 3
Написание символа scala.xml.Node для строки не имеет большого значения. PrettyPrinter
должен позаботиться о вас. scala.xml.XML.save()
будет записываться в файл и scala.xml.XML.write()
выводится на Writer
.