You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中使用Stax2处理XML属性特殊字符转义问题

解决Stax2 XML属性转义错误及实现目标方法

问题背景

期望生成的XML属性包含已转义的换行符:

<elem1 att1="This &#x0A; That" />

但使用常规XMLStreamWriter时,会对&进行二次转义,输出变为:

<elem1 att1="This &amp;#x0A; That" />

尝试使用Stax2解决时,编写了如下Scala代码:

import org.codehaus.stax2.{XMLOutputFactory2}
import org.scalatest.funsuite.AnyFunSuite
import java.io.{File, FileOutputStream}
import javax.xml.stream.{XMLOutputFactory, XMLStreamWriter}

class testStreamXML extends AnyFunSuite{
  val file = new File("stax2test.xml")
  val fileOutputStream = new FileOutputStream(file)
  val outputFactory: XMLOutputFactory2 = XMLOutputFactory.newInstance().asInstanceOf[XMLOutputFactory2]
  //outputFactory.setProperty(XMLOutputFactory2.P_ATTR_VALUE_ESCAPER, true)

  val writer= outputFactory.createXMLStreamWriter(fileOutputStream)

  writer.writeStartDocument()
  writer.writeStartElement("elem1")
  writer.writeAttribute("att1", "This &#x0A; That")
  writer.writeEndElement()
  writer.writeEndDocument()
}

但开启P_ATTR_VALUE_ESCAPER属性并设置为true/false时,出现类型转换错误:

An exception or error caused a run to abort: class java.lang.Boolean cannot be cast to class org.codehaus.stax2.io.EscapingWriterFactory (java.lang.Boolean is in module java.base of loader 'bootstrap'; org.codehaus.stax2.io.EscapingWriterFactory is in unnamed module of loader 'app') 
java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class org.codehaus.stax2.io.EscapingWriterFactory (java.lang.Boolean is in module java.base of loader 'bootstrap'; org.codehaus.stax2.io.EscapingWriterFactory is in unnamed module of loader 'app')
    at com.ctc.wstx.api.WriterConfig.setProperty(WriterConfig.java:401)
    at com.ctc.wstx.api.CommonConfig.setProperty(CommonConfig.java:100)
    at com.ctc.wstx.stax.WstxOutputFactory.setProperty(WstxOutputFactory.java:153)
    at testStreamXML3.<init>(testStreamXML3.scala:10)
    ...

错误原因

XMLOutputFactory2.P_ATTR_VALUE_ESCAPER属性不接受布尔值,它要求传入的是org.codehaus.stax2.io.EscapingWriterFactory的实例,用于自定义属性值的转义逻辑。直接传入true/false会触发类型转换异常。

解决方案

方案1:直接写入原始换行字符(推荐)

不需要手动写&#x0A;,直接在字符串中使用原始换行符\n,Stax2会自动将其转义为符合XML规范的&#x0A;(或&#xA;),避免二次转义问题:

import org.codehaus.stax2.{XMLOutputFactory2}
import org.scalatest.funsuite.AnyFunSuite
import java.io.{File, FileOutputStream}
import javax.xml.stream.{XMLOutputFactory, XMLStreamWriter}

class testStreamXML extends AnyFunSuite{
  val file = new File("stax2test.xml")
  val fileOutputStream = new FileOutputStream(file)
  val outputFactory: XMLOutputFactory2 = XMLOutputFactory.newInstance().asInstanceOf[XMLOutputFactory2]

  val writer= outputFactory.createXMLStreamWriter(fileOutputStream)

  writer.writeStartDocument()
  writer.writeStartElement("elem1")
  // 直接使用原始换行符
  writer.writeAttribute("att1", "This \n That")
  writer.writeEndElement()
  writer.writeEndDocument()
  writer.close() // 记得关闭writer确保内容写入
}

此代码生成的XML会自动包含att1="This &#x0A; That"(具体转义格式可能因Stax2实现略有差异,但符合XML规范)。

方案2:自定义EscapingWriterFactory实现精准控制

如果必须保留手动编写的实体引用(如&#x0A;),需要实现自定义的EscapingWriterFactory,让它不对已有的实体引用中的&进行二次转义:

import org.codehaus.stax2.{XMLOutputFactory2}
import org.codehaus.stax2.io.{EscapingWriterFactory, DefaultEscapingWriterFactory}
import org.scalatest.funsuite.AnyFunSuite
import java.io.{File, FileOutputStream, Writer, OutputStreamWriter}
import javax.xml.stream.{XMLOutputFactory, XMLStreamWriter}

class CustomAttrEscaperFactory extends DefaultEscapingWriterFactory {
  override def createEscapingWriterFor(writer: Writer, enc: String, attrValue: Boolean): Writer = {
    if (attrValue) {
      // 自定义属性值转义逻辑:保留已有的实体引用
      new Writer {
        private val target = writer
        override def write(cbuf: Array[Char], off: Int, len: Int): Unit = {
          val str = new String(cbuf, off, len)
          // 临时替换实体引用标记,避免二次转义
          val processed = str.replace("&#", "__TEMP_ENTITY__")
          target.write(processed.toCharArray, 0, processed.length)
        }
        override def flush(): Unit = target.flush()
        override def close(): Unit = {
          // 恢复实体引用标记
          target.write("".replace("__TEMP_ENTITY__", "&#"))
          target.close()
        }
      }
    } else {
      super.createEscapingWriterFor(writer, enc, attrValue)
    }
  }
}

class testStreamXML extends AnyFunSuite{
  val file = new File("stax2test.xml")
  val fileOutputStream = new FileOutputStream(file)
  val outputFactory: XMLOutputFactory2 = XMLOutputFactory.newInstance().asInstanceOf[XMLOutputFactory2]
  
  // 设置自定义的EscapingWriterFactory
  outputFactory.setProperty(XMLOutputFactory2.P_ATTR_VALUE_ESCAPER, new CustomAttrEscaperFactory())

  val writer= outputFactory.createXMLStreamWriter(fileOutputStream)

  writer.writeStartDocument()
  writer.writeStartElement("elem1")
  writer.writeAttribute("att1", "This &#x0A; That")
  writer.writeEndElement()
  writer.writeEndDocument()
  writer.close()
}

注意:此方案需要根据实际需求调整转义逻辑,确保最终生成的XML合法。


内容的提问来源于stack exchange,提问作者Usr654789

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:01:29