Scala 3大XML节点未读取属性/子节点自动上报方案优化咨询
Scala3 处理XML节点未读取内容自动上报的优化方案
问题背景
我用Scala3处理大型XML文件(复杂CDISC ODM及扩展,需处理引用解析),希望每个XML节点能自动上报未被读取的属性与子节点。目前基于scala.util.Using实现了一套方案,但觉得不够优雅,甚至可能滥用了Using的设计初衷,想咨询更优实现方式。
现有实现代码
object ReportingNode: implicit val releasable: Releasable[ReportingNode] = node => node.assumeNoOtherAttributes() node.assumeNoOtherChildren() class ReportingNode(node: Node): private val description = (node.label + " " + node \@ "OID").trim private var accessedAttributes = Set[String]() private var accessedChildren = Set[String]() @targetName("attribute") def \@(attributeName: String): String = accessedAttributes += attributeName node \@ attributeName @targetName("children") def \(childName: String): NodeSeq = accessedChildren += childName node \ childName private def assumeNoOtherAttributes(): Unit = for (attribute <- node.attributes) if !accessedAttributes.contains(attribute.key) then println(s"WARN $description has unexpected attribute ${attribute.key}") private def assumeNoOtherChildren(): Unit = for (child <- node.child) if !(accessedChildren + "#PCDATA").contains(child.label) then println(s"WARN $description has unexpected child ${child.label}")
使用示例
case class Foo(context: String, name: String, bars: Seq[Bar]) object Foo: def apply(n: Node): Foo = Using.resource(ReportingNode(n))(node => val oid = node \@ "oid" val name = node \@ "name" val bars = (node \ "bar").map(Bar.apply) Foo(oid, name, bars) )
优化思路与实现
1. 修正Using的语义误用
Using的核心是管理资源释放(如文件句柄、数据库连接),你的场景是触发校验逻辑,用Releasable虽能运行,但偏离了设计初衷。推荐两种更贴合的实现:
方式一:显式触发校验
直接在解析完成后手动调用校验,逻辑直观且无语义混淆:
class ReportingNode(node: Node): // 保留原有成员和方法... // 新增显式校验入口 def checkUnused(): Unit = assumeNoOtherAttributes() assumeNoOtherChildren() // 使用示例 object Foo: def apply(n: Node): Foo = val node = ReportingNode(n) val oid = node \@ "oid" val name = node \@ "name" val bars = (node \ "bar").map(Bar.apply) node.checkUnused() // 解析完成后主动触发校验 Foo(oid, name, bars)
方式二:基于AutoCloseable实现自动校验
如果想保留自动触发特性,让ReportingNode实现AutoCloseable,既符合资源管理语义,又能复用Using的自动调用逻辑:
class ReportingNode(node: Node) extends AutoCloseable: private val description = (node.label + " " + node \@ "OID").trim private var accessedAttributes = Set[String]() private var accessedChildren = Set[String]() @targetName("attribute") def \@(attributeName: String): String = accessedAttributes += attributeName node \@ attributeName @targetName("children") def \(childName: String): NodeSeq = accessedChildren += childName node \ childName private def assumeNoOtherAttributes(): Unit = for (attribute <- node.attributes) if !accessedAttributes.contains(attribute.key) then println(s"WARN $description has unexpected attribute ${attribute.key}") private def assumeNoOtherChildren(): Unit = for (child <- node.child) if !(accessedChildren + "#PCDATA").contains(child.label) then println(s"WARN $description has unexpected child ${child.label}") // 实现AutoCloseable接口,自动触发校验 override def close(): Unit = assumeNoOtherAttributes() assumeNoOtherChildren() // 使用示例(和原有用法一致,但语义更合理) object Foo: def apply(n: Node): Foo = Using.resource(ReportingNode(n))(node => val oid = node \@ "oid" val name = node \@ "name" val bars = (node \ "bar").map(Bar.apply) Foo(oid, name, bars) )
2. 增强校验逻辑的灵活性
- 可配置的日志输出:替换硬编码的
println为回调函数,方便接入日志框架(如SLF4J):
class ReportingNode( node: Node, warnHandler: String => Unit = println ): // ...原有成员... private def assumeNoOtherAttributes(): Unit = for (attribute <- node.attributes) if !accessedAttributes.contains(attribute.key) then warnHandler(s"WARN $description has unexpected attribute ${attribute.key}")
- 支持忽略特定字段:针对CDISC ODM的标准扩展字段,可配置忽略列表:
class ReportingNode( node: Node, ignoredAttrs: Set[String] = Set.empty, ignoredChildren: Set[String] = Set("#PCDATA"), warnHandler: String => Unit = println ): // ...原有成员... private def assumeNoOtherAttributes(): Unit = for (attribute <- node.attributes) if !accessedAttributes.contains(attribute.key) && !ignoredAttrs.contains(attribute.key) then warnHandler(s"WARN $description has unexpected attribute ${attribute.key}") private def assumeNoOtherChildren(): Unit = for (child <- node.child) if !accessedChildren.contains(child.label) && !ignoredChildren.contains(child.label) then warnHandler(s"WARN $description has unexpected child ${child.label}")
3. 结合专业XML解析库
针对复杂CDISC ODM场景,可使用Jackson XML等专业库,通过自定义DeserializationProblemHandler实现未识别字段的自动上报:
// 简化示例(Scala兼容) class UnusedFieldReporter extends DeserializationProblemHandler { override fun handleUnknownProperty(ctxt: DeserializationContext, p: JsonParser, deserializer: JsonDeserializer<*>, beanOrClass: Any, propName: String): Boolean { println(s"WARN Unknown property: $propName on ${beanOrClass.javaClass.simpleName}") return true // 继续解析而不报错 } } // 注册到ObjectMapper val xmlMapper = XmlMapper() xmlMapper.addHandler(UnusedFieldReporter()) xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
内容的提问来源于stack exchange,提问作者Marcus
相关产品推荐
相关产品推荐

