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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 16:57:20