I'm writing a C# console app (.NET 6) running on a Windows machine that processes XSLT transformations as a batch: It reads parameter sets (which are then passed as params to the respective stylesheet) from an XML file and performs transformations using SaxonCS. (The params always contain the initial template and a path to the source file which is being read into a variable via doc($path-to-source) in said initial template).
For each transformation, an object is instantiated that represents an XSL transformation. Among other things like Console output etc., regarding the transformation, it does this:
// Instantiate SaxonCS Processor processor = new();
XsltCompiler compiler = processor.NewXsltCompiler();
compiler.BaseUri = new Uri(pathToXsl);
XsltTransformer transformer = compiler.Compile(File.OpenRead(pathToXsl)).Load();
// Set params used by the stylesheet, using ExternalParams (a Dictionary populated earlier
// with values read from the configuration file).
foreach (var parameter in ExternalParams) { transformer.SetParameter(parameter.Key, parameter.Value); }
transformer.InitialTemplate = new QName(parametrizedInitialTemplate);
// perform transformation
XdmDestination result = new(); // effectively unused
transformer.Run(result);
transformer.Close();
result = null; // result is not needed: The processor already serialized it because of xsl:result-document()
This works great - until I try to use a file which is the result of an earlier transformation, having been written using xsl:result-document href="{filepath}"
in the stylesheet, as the input for another transformation later on. This then gives me a
System.IO.IOException: 'The process cannot access the file '..(some file path)..' because it is being used by another process.'
In other words:
So I somehow fail to release resources / to close the resulting output file once the transformation is finished.
Running the exact same configuration file and console app, but calling Saxon 9-HE (Java) in its own Process works perfectly fine (but very slowly); there are no file access problems then:
Process proc = new();
ProcessStartInfo startInfo = new()
{
Arguments = @$"-cp {saxonJarPath} net.sf.saxon.Transform {string.Join(" ", XslParams)} -xsl:{XslPath} -it:{InitialTemplate}",
FileName = "java",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
proc.StartInfo = startInfo;
proc.Start();
proc.WaitForExit();
This is obviously not an ideal solution, and I would really like to speed the whole thing up and get rid of the Java dependency - that's why I'm trying to make it work with SaxonCS.
Unfortunately, I can't do "real pipelining" (as shown in one of the code examples that come with Saxon), thus directly using the result as input for the next, because the whole thing has to be configured externally (and not every result is input for the next transformation).
(Because of the explanations regarding ResultDocumentHandler, I tried processor.SetProperty(Saxon.Api.Feature.ALLOW_MULTITHREADING, false);
, but it didn't help.)
So: How do I prevent a file which has been produced via xsl:result-document from being locked even when the transformation has finished?
I think explicitly setting up
transformer.BaseOutputUri = new Uri(pathToXsl);
transformer.ResultDocumentHandler = (href, baseUri) => {
var serializer = processor.NewSerializer();
var fs = File.Open(new Uri(baseUri, href).LocalPath, FileMode.Create, FileAccess.Write);
serializer.OutputStream = fs;
serializer.OnClose(() => { fs.Close(); });
return serializer;
};
avoids the problem.