0

我正在尝试编写一个 XML 解析实用程序,该实用程序允许用户通过提供属性名称、当前属性值和他们希望属性具有的新值来修改 XML 属性。这是我的代码:

def main (args: Array[String]) {
   val xml= <Rule debug="true" expression="testing"/>
   val printable= replaceXMLEntryAttribute(xml, "debug", "true", "false")
   println(printable)
}
/**
   * This method is used to iterate over the entirety of the xml presented and modify the XML attribute desired 
   */
  def replaceXMLEntryAttribute(elem: Elem, attrName: String, curVal: String, desiredVal: String): Elem = {

    def replace(current: Elem): Elem = current.copy(
      child = current.map {
        case e: Elem if isReplacementEntry(current, attrName, curVal) ⇒ generateReplacementXMLAttribute(current)
        case e: Elem ⇒ replace(e)
        case other⇒ other
      }
    )

   def generateReplacementXMLAttribute(node: Elem): Elem = {
      val currentXML= node.toString()
      val newAttr= currentXML.replace(curVal, desiredVal)
      return XML.loadString(newAttr)
    }
    replace(elem)
  }

  private def isReplacementEntry(node: Elem, attributeName: String,  currentAttrValue: String): Boolean = {
   val attr = "@" + attributeName
   val exists = node \\ attr find { _.text == currentAttrValue }
   exists match{
     case None => false
     case _ => true
   }

所需的输出是<Rule debug="false" expression="testing"/> 程序的结果是<Rule debug="true" expression="testing"><Rule expression="testing" debug="false"/></Rule>

我只能猜测并说替换方法在这里搞砸了。

4

2 回答 2

2

Elem.map方法的文档没有任何文字来解释它应该做什么,并且它的类型令人困惑。要获得更具体的类型,我们可以使用 Scala 解释器:

scala> import scala.xml._
scala> val elem: Elem = <numbers><one/><two/><three/></numbers>
scala> :t elem.map(identity)
scala.xml.NodeSeq

奇怪,为什么会产生一个NodeSeq?如果Elem.map映射元素的子元素,返回Elem具有相同标签和属性但新子元素的 a,则返回类型应该是Elem,而不是NodeSeq。为了验证是否Elem.map真的在迭代它的子节点,让我们将遇到的节点累积到一个列表中。

scala> var nodes = Seq[Node]()
scala> elem.map {node =>
     |   nodes :+= node
     |   node
     | }
res: scala.xml.NodeSeq = NodeSeq(<numbers><one/><two/><three/></numbers>)
scala> nodes
res: Seq[scala.xml.Node] = List(<numbers><one/><two/><three/></numbers>)

如果我们在孩子身上迭代,我会预料List(<one/>, <two/>, <three/>)到的,但这不是我们得到的。所以看起来我们正在迭代一个包含元素本身的 1 元素集合,这不是很有用。然而,查看代码,这似乎是有意的:Node,并且通过扩展Elem,是其序列的子类,NodeSeq其序列由单个元素本身组成。

所以,总而言之,你得到意外结果的原因是你从 开始<Rule debug="true" expression="testing"/>,然后映射它以获得结果<Rule debug="true" expression="testing"/>,然后用该结果替换 Rule 的子级,获得<Rule debug="true" expression="testing"><Rule expression="testing" debug="false"/></Rule>

这部分问题的解决方案是使用current.child.map而不是current.map. 但是,由于您只检查 Rule 的零个子节点而不是 Rule 本身,因此永远不会执行 map 的主体,因此 debug 属性保持不变。我建议交换模式匹配和地图:

def replace: Node => Node =
  {
    case e: Elem if isReplacementEntry(e, attrName, curVal) ⇒ generateReplacementXMLAttribute(e)
    case e: Elem ⇒ e.copy(
      child = e.child.map { replace(_) }
    )
    case other⇒ other
  }

在修复类型以使用Nodes 而不是Elems 后,我获得了所需的结果。

于 2015-09-04T01:54:26.743 回答
0

在 github 上查看我的库 Advxml 以替换和编辑 xml 文档!

https://github.com/geirolz/advxml

例子:

import com.github.geirolz.advxml.all._
import scala.xml._
import scala.util._

//import MonadError instance for Try
import cats.instances.try_._

val doc: Elem = 
<Persons>
  <Person Name="Mimmo">
    <Cars>
      <Car Brand="Fiat"/>
    </Cars>
  </Person>
</Persons>

val rule: XmlRule = $(_ \ "Person" \ "Cars")
    ==> Replace(<Cars><Car Brand="Lamborghini"/></Cars>)

val result: Try[NodeSeq] = doc.transform[Try](rule)  
于 2019-08-06T20:21:02.413 回答