2009-07-07 2 views
5

Я использую scala для загрузки XML-файла из файла с помощью метода scala.xml.XML.loadFile(). Документы, с которыми я работаю, уже имеют пространства имен, и я хочу изменить пространство имен на другое, используя scala. Например, документ имеет xmlns «http://foo.com/a» с префиксом «a» - я хотел бы изменить пространство имен и префикс для документа на «http://foo.com/b» и «b» соответственно.Изменение пространства имен XML с помощью Scala

Кажется легким, и я чувствую, что мне не хватает чего-то очевидного здесь. У меня нет проблемы с получением пространства имен из обратного Elem из ссылочного метода loadFile().

ответ

9

Вот оно. Поскольку 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] 
} 
0

Незначительная ошибка здесь. Атрибуты также могут иметь квалифицированные имена. Вам нужно также проверить их.

Смежные вопросы