Scala中使用Stax2处理XML属性特殊字符转义问题
解决Stax2 XML属性转义错误及实现目标方法
问题背景
期望生成的XML属性包含已转义的换行符:
<elem1 att1="This 
 That" />
但使用常规XMLStreamWriter时,会对&进行二次转义,输出变为:
<elem1 att1="This &#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 
 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:直接写入原始换行字符(推荐)
不需要手动写
,直接在字符串中使用原始换行符\n,Stax2会自动将其转义为符合XML规范的
(或
),避免二次转义问题:
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 
 That"(具体转义格式可能因Stax2实现略有差异,但符合XML规范)。
方案2:自定义EscapingWriterFactory实现精准控制
如果必须保留手动编写的实体引用(如
),需要实现自定义的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 
 That") writer.writeEndElement() writer.writeEndDocument() writer.close() }
注意:此方案需要根据实际需求调整转义逻辑,确保最终生成的XML合法。
内容的提问来源于stack exchange,提问作者Usr654789
相关产品推荐
相关产品推荐

