Мне лично нравится использовать схему, которая фокусируется на уровне назад совместимости, которую могут ожидать пользователи проекта/продукта:
Прежде 1.0:
После 1.0:
Используя совместимость как центральная точка в номере версии облегчает для пользователей, особенно если te продуктом является библиотека, чтобы судить, могут ли они ожидать smoothe и безопасный обновление или нет.
Вот оно. Поскольку NamespaceBinding является вложенным (у каждого ns есть родительский элемент, кроме TopScope), нам нужно выполнить рекурсию, чтобы исправить это. Кроме того, у каждого ns есть URI и префикс, и нам нужно изменить оба.
Функция ниже изменит только один конкретный URI и префикс, и она проверит все пространства имен, чтобы увидеть, нужно ли изменить префикс или URI. Он изменит префикс или URI независимо друг от друга, что может быть не тем, что нужно. Хотя это не большая проблема.
Что касается остального, просто сопоставление с образцом на Elem для рекурсии в каждую часть XML. А, да, префикс элементов тоже меняет. Опять же, если это не то, что нужно, это легко изменить.
Код предполагает, что нет необходимости рекурсивно переходить к «другим» частям XML - остальные, как правило, являются текстовыми элементами. Также, предполагается, что в другом месте нет пространства имен. Я не специалист по XML, поэтому могу ошибаться в обоих пунктах. И снова, это должно быть легко изменить - просто следуйте шаблону.
def changeNS(el: Elem,
oldURI: String, newURI: String,
oldPrefix: String, newPrefix: String): Elem = {
def replace(what: String, before: String, after: String): String =
if (what == before) after else what
def fixScope(ns: NamespaceBinding): NamespaceBinding =
if(ns == TopScope)
TopScope
else new NamespaceBinding(replace(ns.prefix, oldPrefix, newPrefix),
replace(ns.uri, oldURI, newURI),
fixScope(ns.parent))
def fixSeq(ns: Seq[Node]): Seq[Node] = for(node <- ns) yield node match {
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(replace(prefix, oldPrefix, newPrefix),
label,
attribs,
fixScope(scope),
fixSeq(children) : _*)
case other => other
}
fixSeq(el.theSeq)(0).asInstanceOf[Elem]
}
Однако это дает неожиданный результат. Область действия добавляется ко всем элементам. Это потому, что NamespaceBinding не определяет метод equals, поэтому использует ссылочное равенство. Я открыл для этого заявку 2138 , которая уже закрыта, поэтому у Scala 2.8 не будет этой проблемы.
Между тем, следующий код будет работать правильно. Он хранит кеш пространств имен. Он также раскладывает NamespaceBinding в список перед его обработкой.
def changeNS(el: Elem,
oldURI: String, newURI: String,
oldPrefix: String, newPrefix: String): Elem = {
val namespaces = scala.collection.mutable.Map.empty[List[(String, String)],NamespaceBinding]
def replace(what: String, before: String, after: String): String =
if (what == before) after else what
def unfoldNS(ns: NamespaceBinding): List[(String, String)] = ns match {
case TopScope => Nil
case _ => (ns.prefix, ns.uri) :: unfoldNS(ns.parent)
}
def foldNS(unfoldedNS: List[(String, String)]): NamespaceBinding = unfoldedNS match {
case knownNS if namespaces.isDefinedAt(knownNS) => namespaces(knownNS)
case (prefix, uri) :: tail =>
val newNS = new NamespaceBinding(prefix, uri, foldNS(tail))
namespaces(unfoldedNS) = newNS
newNS
case Nil => TopScope
}
def fixScope(ns: NamespaceBinding): NamespaceBinding =
if(ns == TopScope)
ns
else {
val unfoldedNS = unfoldNS(ns)
val fixedNS = for((prefix, uri) <- unfoldedNS)
yield (replace(prefix, oldPrefix, newPrefix), replace(uri, oldURI, newURI))
if(!namespaces.isDefinedAt(unfoldedNS))
namespaces(unfoldedNS) = ns // Save for future use
if(fixedNS == unfoldedNS)
ns
else
foldNS(fixedNS)
}
def fixSeq(ns: Seq[Node]): Seq[Node] = for(node <- ns) yield node match {
case Elem(prefix, label, attribs, scope, children @ _*) =>
Elem(replace(prefix, oldPrefix, newPrefix),
label,
attribs,
fixScope(scope),
fixSeq(children) : _*)
case other => other
}
fixSeq(el.theSeq)(0).asInstanceOf[Elem]
}
Небольшая ошибка. Атрибуты также могут иметь уточненные имена. Их тоже нужно проверить.