我想替换XML文件中的文本,但保留源文件中的任何其他格式。
E.g。将其解析为DOM,使用XPath替换节点并输出为String可能无法解决问题,因为它将重新格式化整个文件。 (漂亮的打印可能适用于99%的情况,但要求是保留现有格式,即使它不是“漂亮”)
是否有任何Java / Scala库可以在String上执行“查找和替换”,而无需将其解析为DOM树?或者至少能够保留原始格式?
编辑:
我认为maven replacer plugin执行something like this,似乎通过使用setPreserveSpace
(我认为需要尝试)来保留原始的空白格式
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
...
private String writeXml(Document doc) throws Exception {
OutputFormat of = new OutputFormat(doc);
of.setPreserveSpace(true);
of.setEncoding(doc.getXmlEncoding());
StringWriter sw = new StringWriter();
XMLSerializer serializer = new XMLSerializer(sw, of);
serializer.serialize(doc);
return sw.toString();
}
所以问题改为:有没有(直接的)方式这样做没有额外的依赖?
EDIT2:
要求是使用外部提供的XPath查询,即作为字符串。
答案 0 :(得分:1)
您可以尝试scala.xml.pull
或缩放XML。
您可以找到解析文件here的工作代码。
Scales XML可以使用STAX API,它是一个流API。因此,从来没有一个完整的DOM,通常没有太多的预处理就可以实现XML的各个部分。
使用特殊格式的XML文件对其进行测试,看看它是否有效。
我不建议使用简单的文本搜索并用XML替换。很有可能出现不匹配的情况。然后,您将以不可预测的方式更改文档。由此产生的错误通常很难找到。
我使用Scales XML做了一个简短的实验,看起来非常有希望:
scala> import scales.utils._
import scales.utils._
scala> import ScalesUtils._
import ScalesUtils._
scala> import scales.xml._
import scales.xml._
scala> import ScalesXml._
import ScalesXml._
scala> import scales.xml.serializers.StreamSerializer
import scales.xml.serializers.StreamSerializer
scala> import java.io.StringReader
import java.io.StringReader
scala> import java.io.PrintWriter
import java.io.PrintWriter
scala> def xmlsrc=new StringReader("""
| <a attr1="value1"> <b/>This
| is some tex<xt/>
| <!-- A comment -->
| <c><d>
| </d>
| <removeme/>
| <changeme/>
| </c>
| </a>
| """)
xmlsrc: java.io.StringReader
scala> def pull=pullXml(xmlsrc)
pull: scales.xml.XmlPull with java.io.Closeable with scales.utils.IsClosed
scala> writeTo(pull, new PrintWriter(System.out))
<?xml version="1.0" encoding="UTF-8"?><a attr1="value1"> <b/>This
is some tex<xt/>
<!-- A comment -->
<c><d>
</d>
<removeme/>
<changeme/>
</c>
res0: Option[Throwable] = None
scala> def filtered=pull flatMap {
| case Left(e : Elem) if e.name.local == "removeme" => Nil
| case Right(e : EndElem) if e.name.local == "removeme" => Nil
| case Left(e : Elem) if e.name.local == "changeme" => List(Left(Elem("x")), Left(Elem("y"
Right(EndElem("x")))
| case Right(e : EndElem) if e.name.local == "changeme" => List(Right(EndElem("x")))
| case otherwise => List(otherwise)
| }
filtered: Iterator[scales.xml.PullType]
scala> writeTo(filtered, new PrintWriter(System.out))
<?xml version="1.0" encoding="UTF-8"?><a attr1="value1"> <b/>This
is some tex<xt/>
<!-- A comment -->
<c><d>
</d>
<x><y/></x>
</c>
res1: Option[Throwable] = None
该示例首先初始化XML令牌流。然后它打印未经修改的令牌流。您可以看到,保留了注释和格式。然后,它使用monadic Scala API修改令牌流并打印结果。您可以看到大多数格式都被保留,只有更改部分的格式不同。
因此看起来Scales XML可以直接解决您的问题。
答案 1 :(得分:1)
我打算快速编写一些代码来调用scala.xml,以及我有多喜欢它。自从我第一次学习Scala之后,我就没用过它。
您通常会看到空格的文本节点 - 这在PiS中提到,in the "catalog" example here。
我确实记得它在加载时反转了属性 - 我依旧记得不得不fix pretty printing。
但编译器不会反转xml文字的属性。因此,如果要动态提供xpath,可以使用编译器工具箱将源文档编译为文字,并编译xpath字符串,并将/运算符转换为\
。
这只是一个开箱即用的乐趣,但也许它有一个适用性的最佳点,也许你必须只使用标准的Scala发行版。
稍后当我有机会尝试时,我会更新。
import scala.xml._
import java.io.File
object Test extends App {
val src =
"""|<doc>
| <foo bar="red" baz="yellow"> <bar> red </bar> </foo>
| <baz><bar>red</bar></baz>
|</doc>""".stripMargin
val red = "(.*)red(.*)".r
val sub = "blue"
val tmp =
<doc>
<foo bar="red" baz="yellow"> <bar> red </bar> </foo>
<baz><bar>red</bar></baz>
</doc>
Console println tmp
// replace "red" with "blue" in all bar text
val root = XML loadString src
Console println root
val bars = root \\ "bar"
val barbars =
bars map (_ match {
case <bar>{Text(red(prefix, suffix))}</bar> =>
<bar>{Text(s"$prefix$sub$suffix")}</bar>
case b => b
})
val m = (bars zip barbars).toMap
val sb = serialize(root, m)
Console println sb
def serialize(x: Node, m: Map[Node, Node], sb: StringBuilder = new StringBuilder) = {
def serialize0(x: Node): Unit = x match {
case e0: Elem =>
val e = if (m contains e0) m(e0) else e0
sb append "<"
e nameToString sb
if (e.attributes ne null) e.attributes buildString sb
if (e.child.isEmpty) sb append "/>"
else {
sb append ">"
for (c <- e.child) serialize0(c)
sb append "</"
e nameToString sb
sb append ">"
}
case Text(t) => sb append t
}
serialize0(x)
sb
}
}