2009-07-07 8 views
5

scala.xml.XML.loadFile() 메서드를 통해 XML 파일을로드하는 데 scala를 사용하고 있습니다. 내가 사용하고있는 문서는 이미 정의 된 네임 스페이스를 가지고 있으며 네임 스페이스를 scala를 사용하는 다른 것으로 변경하려고합니다. 예를 들어 xmlns가 "http://foo.com/a"이며 접두사가 "a"인 경우 - 문서의 네임 스페이스와 접두사를 각각 "http://foo.com/b"및 "b"로 변경하고 싶습니다.스칼라로 XML 네임 스페이스 변경

쉽게 볼 수있는 것처럼 보입니다. 여기서 뭔가 분명하지 않은 것 같습니다. 참조 된 loadFile() 메서드에서 반환 Elem에서 네임 스페이스를 가져 오는 데 문제가 없습니다.

답변

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을 처리하기 전에 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

여기에 사소한 버그가 있습니다. 속성은 또한 한정된 이름을 가질 수 있습니다. 그것도 확인해야합니다.