Saxon .NET如何通过ResultDocumentHandler获取目标XDM节点处理多输出文档
Saxon .NET 低内存拦截多输出转换的实现方案
核心思路是放弃内存构建XdmNode的承接方式,基于Saxon的事件流管道机制插入中间处理逻辑,全程不落地完整文档树,内存占用可稳定在常数级。
原有方案失效原因
使用ResultDocumentHandler拿不到待写入数据,是因为直接返回文件类Destination时,Saxon会将XML的结构化事件直接推送到文件写入逻辑,中间没有插入处理环节;如果改用XdmDestination承接,会将所有事件组装为完整内存树,大文档场景下内存占用会非常高。
具体实现步骤
- 自定义拦截用
Destination
继承Saxon内置的AbstractDestination类,重写GetReceiver方法。Saxon的输出逻辑基于事件推送模式(逐次推送文档开始、元素开始、属性、文本、元素结束、文档结束事件,和SAX解析逻辑一致),可在这个方法中返回一个代理Receiver,包装最终写文件的目标Receiver,所有事件先经过自定义处理逻辑,再透传给下游写入组件,全程不需要缓存整个文档。 - 统一拦截三类输出
- 主转换输出:不要给
XsltTransformer.Destination直接赋值FileDestination,替换为自定义的拦截Destination,实例化时传入对应主输出路径的FileDestination作为最终下游。 xsl:result-document输出:给XsltTransformer.ResultDocumentHandler委托返回自定义拦截Destination实例,每个实例绑定对应输出路径的FileDestination,确保两个分输出也经过处理层。
- 主转换输出:不要给
- 实现中间转换逻辑
- 如果额外处理是节点增删改、属性调整、内容校验这类细粒度操作,直接在代理
Receiver中重写对应事件方法(StartElement/Attribute/Characters/EndElement等),处理完事件直接调用下游Receiver的对应方法传递即可。 - 如果额外处理需要执行第二段XSLT转换,直接将代理
Receiver接到第二个XsltTransformer的输入端口,第二个转换器的Destination直接绑定最终文件输出Destination即可。Saxon的XSLT处理器原生支持事件流输入,不需要提前构建完整内存节点树,内存开销和直接写文件基本一致。
- 如果额外处理是节点增删改、属性调整、内容校验这类细粒度操作,直接在代理
注意:全程不要使用
XdmDestination作为中间承接组件,该组件会缓存所有事件生成完整内存树,是高内存占用的核心诱因。
核心代码示例
// 代理Receiver示例,可在事件传递过程中插入自定义处理逻辑 public class ProxyReceiver : AbstractReceiver { private readonly IReceiver _downstream; public ProxyReceiver(IReceiver downstream, PipelineConfiguration pipe) : base(pipe) { _downstream = downstream; } public override void StartElement(NodeName elemName, SchemaType? typeCode, Location location, int properties) { // 此处可插入元素开始时的处理逻辑 _downstream.StartElement(elemName, typeCode, location, properties); } public override void Attribute(NodeName attName, SimpleType? typeCode, string value, Location location, int properties) { // 此处可插入属性处理逻辑,比如修改属性值 _downstream.Attribute(attName, typeCode, value, location, properties); } public override void Characters(string value, Location location, int properties) { // 此处可插入文本节点处理逻辑 _downstream.Characters(value, location, properties); } // 其余事件方法(文档开始/结束、元素结束、命名空间等)按同样方式透传即可 } // 自定义拦截Destination public class InterceptDestination : AbstractDestination { private readonly Destination _finalOutputDest; public InterceptDestination(Destination finalOutputDest) { _finalOutputDest = finalOutputDest; } public override IReceiver GetReceiver(PipelineConfiguration pipe) { var downstreamReceiver = _finalOutputDest.GetReceiver(pipe); return new ProxyReceiver(downstreamReceiver, pipe); } protected override void Dispose(bool disposing) { if (disposing) _finalOutputDest.Dispose(); base.Dispose(disposing); } } // 转换流程初始化 var processor = new Processor(); var xsltCompiler = processor.NewXsltCompiler(); var executable = xsltCompiler.Compile(xsltStream); var transformer = executable.Load30(); transformer.SetInputStream(inputXmlStream, new Uri("file:///base-uri")); // 拦截xsl:result-document输出 var outputRoot = @"C:\your\output\path"; transformer.ResultDocumentHandler = args => { var targetPath = Path.Combine(outputRoot, args.Uri.OriginalString); var finalFileDest = new FileDestination(targetPath); return new InterceptDestination(finalFileDest); }; // 拦截主输出 var mainOutputPath = Path.Combine(outputRoot, "main.xml"); transformer.Destination = new InterceptDestination(new FileDestination(mainOutputPath)); // 执行转换,全程流式处理,3份输出都会先经过自定义逻辑再写入磁盘 transformer.Run();
内容的提问来源于stack exchange,提问作者iber999
相关产品推荐
相关产品推荐

