使用XPath替换XML文件中的文本,同时保留格式

时间:2013-08-14 01:36:57

标签: java scala

我想替换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查询,即作为字符串。

2 个答案:

答案 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 Sc​​ala 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
  }
}