反XML:取消选择后,使用自定义选择器对Zipper进行的一些修改不会保留

时间:2014-02-27 15:58:55

标签: xml scala selector zipper anti-xml

为了测试一个可以转换XML文档中Text元素的方法,我编写了两个非常简单的选择器,并在生成的Zipper上应用了map / toUpperCase。结果应该是除了通过第一个选择器排除的文本元素之外的所有文本元素都将转换为大写。但它只适用于最下面的Text元素。这是代码:

scala> import com.codecommit.antixml._
import com.codecommit.antixml._

scala> val elemSelector = Selector({case x:Elem if x.name != "note" => x})
elemSelector: com.codecommit.antixml.Selector[com.codecommit.antixml.Elem] = <function1>

scala> val textSelector = Selector({case x:Text => x})
textSelector: com.codecommit.antixml.Selector[com.codecommit.antixml.Text] = <function1>

scala> val xml = XML.fromString("<tei><div><p>this<note>not<foreign lang=\"greek\">that</foreign>not</note></p><p>those<hi>these</hi></p></div></tei>")
xml: com.codecommit.antixml.Elem = <tei><div><p>this<note>not<foreign lang="greek">that</foreign>not</note></p><p>those<hi>these</hi></p></div></tei>

scala> val zipper = xml \\ elemSelector \ textSelector
zipper: com.codecommit.antixml.Zipper[com.codecommit.antixml.Text] = thisthatthosethese

scala> val modified = zipper.map(t => new Text(t.text.toUpperCase))
modified: com.codecommit.antixml.Zipper[com.codecommit.antixml.Text] = THISTHATTHOSETHESE

scala> val result = modified.unselect.unselect
result: com.codecommit.antixml.Zipper[com.codecommit.antixml.Node] = <tei><div><p>this<note>not<foreign lang="greek">THAT</foreign>not</note></p><p>those<hi>THESE</hi></p></div></tei>

因此,在倒数第二个命令中,大写适用于所有目标Text元素,但在拉出拉链后,只转换了四个元素中的两个。我已使用<hi/>代替<hi>these</hi>进行了尝试,然后those获得了大写。知道这里有什么问题吗?

我正在使用scala 2.10.3的arktekk.no fork。

1 个答案:

答案 0 :(得分:1)

您遇到的问题来自取消选择过程中的合并冲突。

为了简化您的问题,我将使用以下数据:

val textSelector = Selector { case x: Text => x }

val xml = XML.fromString("<root><a>foo<b>bar</b></a></root>")

val zipper = xml \\ * \ textSelector

val modified = zipper.map(t => t.copy(text = t.text.toUpperCase))

val result = modified.unselect.unselect
// => <root><a>foo<b>BAR</b></a></root>

当您使用*选择器选择树中的所有元素时,您会在结果集中获得ab Elem。第二个浅选择器仅查看ab的直接子项,并获取Text值。我们从foo获得a,从bar获得b

修改后,第一个unselect包含个人Elem及其更新内容:

<a>FOO<b>bar</b></a>
<b>BAR</b>

现在,下一个unselect需要将b合并回a以形成a的新版本。当前版本的b意味着新的a,以便:

<a>foo<b>BAR</b></a>

在您的冲突中,您可以a与孩子List(FOO, <b>bar</b>)或孩子List(foo, <b>BAR</b>)。 由于没有通用的方法来确定哪个列表更好(它们都同时更新),因此选择取决于实现。在这种情况下,它需要来自树中更深层次的修改。

您可以通过不选择Elem并直接修改Text节点来解决此问题,从而避免任何可能的冲突(因为它们只能在Elem上发生)。所以你写道:

val zipper = xml \\ textSelector

val modified = zipper.map(t => t.copy(text = t.text.toUpperCase))

val result = modified.unselect
// => <root><a>FOO<b>BAR</b></a></root>

如果这不是您的用例的选项,则可以为unselect定义用于此特定情况的自定义合并策略;将以某种方式消除儿童名单不同部分的歧义。即使可能,我怀疑它是否值得付出努力。