0

I have downloaded the latest 9ee version of saxon api for dotnet. I've got a simple program that transforms an xml from an xslt. it works for simple xslts, but I cannot seem to see how to enable schema validation (of the xslt document).

here is my C# (thanks to How to use XSLT 3.0 using Saxon-HE 9.8 in .NET)

    public static string TransformXml(string xmlData, string xslData)
    {
        var xsltProcessor = new Processor(true);

        var documentBuilder = xsltProcessor.NewDocumentBuilder();
        documentBuilder.BaseUri = new Uri("file://");
        var xdmNode = documentBuilder.Build(new StringReader(xmlData));

        var xsltCompiler = xsltProcessor.NewXsltCompiler();
        var xsltExecutable = xsltCompiler.Compile(new StringReader(xslData));
        var xsltTransformer = xsltExecutable.Load();
        xsltTransformer.InitialContextNode = xdmNode;

        var results = new XdmDestination();

        xsltTransformer.Run(results);
        return results.XdmNode.OuterXml;
    }

here is my xslt (taken largely from "mastering xslt" Doug Tidwell)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">
    
    <xsl:output method="text" />
    
<xsl:import-schema schema-location="file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/po1.xsd" />

    <xsl:template match="schema-element(PurchaseOrder)">
      <xsl:for-each select="item">
            <xsl:value-of select="@id" />: <xsl:value-of select="title" />
            <xsl:text>&#xa;</xsl:text>
        </xsl:for-each>
    </xsl:template>
    
    <xsl:template match="*">
        <xsl:message terminate="yes">Source document is not a purchase order
        </xsl:message>
    </xsl:template>
    
</xsl:stylesheet>

when I run it, I get "Source document is not purchase order"....

If I remove the "schema-element" function, then I get the desired

100: Water for Elephants
101: Glass Castle: A Memoir
200: 5 Amp Electric plug

my hunch is the xslt is correct, but I'm missing some setting on API (to be fair the saxon examples, don't include this sort of thing)


edit

actually to be honest, I want to be able to do 2 things, 1 validate an xslt against an input schema (import-schema) AND execute an XSLT against an xslt with these constructs, as 2 seperate operations.


edit

so taking the feedback from Michael Kay in the comments.

I've gone back to the saxon examples, and borrowed the validator example.

Rather than validating an instance document, I've tried to validate the XSLT itself.

        var samplesDir = new Uri(AppDomain.CurrentDomain.BaseDirectory);

        Processor processor = new Processor(true);
        processor.SetProperty("http://saxon.sf.net/feature/timing", "true");
        processor.SetProperty("http://saxon.sf.net/feature/validation-warnings", "false"); //Set to true to suppress the exception
        SchemaManager manager = processor.SchemaManager;
        manager.XsdVersion = "1.1";
        manager.ErrorList = new List<StaticError>();
        Uri schemaUri = new Uri(samplesDir, "po1.xsd");

        try
        {
            manager.Compile(schemaUri);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.WriteLine("Schema compilation failed with " + manager.ErrorList.Count + " errors");
            foreach (StaticError error in manager.ErrorList)
            {
                Console.WriteLine("At line " + error.LineNumber + ": " + error.Message);
            }
            return;
        }


        // Use this to validate an instance document

        SchemaValidator validator = manager.NewSchemaValidator();

        XmlReaderSettings settings = new XmlReaderSettings();
        settings.DtdProcessing = DtdProcessing.Ignore;
        String inputFileName = new Uri(samplesDir, "po.xsl").ToString();
        XmlReader xmlReader = XmlReader.Create(inputFileName, settings);
        validator.SetSource(xmlReader);
        Console.WriteLine("Validating input file " + inputFileName);
        validator.ErrorList = new List<ValidationFailure>();
        XdmDestination psvi = new XdmDestination();
        validator.SetDestination(psvi);

        try
        {
            validator.Run();
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.WriteLine("Instance validation failed with " + validator.ErrorList.Count + " errors");
            foreach (ValidationFailure error in validator.ErrorList)
            {
                Console.WriteLine("At line " + error.GetLineNumber() + ": " + error.GetMessage());
            }
            return;
        }

the result on the console is

Loading schema document file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po1.xsd
Using parser org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser
Found registry key at HKEY_LOCAL_MACHINE\Software\Saxonica\SaxonEE-N\Settings
Software installation path: c:\Program Files\Saxon\SaxonEE9.9N
Finished loading schema document file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po1.xsd
Validating input file file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po.xsl
One validation error was reported: Cannot validate <Q{.../Transform}stylesheet>: no element declaration available
Instance validation failed with 1 errors
At line 2: Cannot validate <Q{.../Transform}stylesheet>: no element declaration available
MrD at KookerellaLtd
  • 2,412
  • 1
  • 15
  • 17

2 Answers2

3

There are two approaches.

You can use xsl:import-schema to load the schema, but then it's only the XSLT processor that knows about the schema, so the input document needs to be built and validated under the control of XSLT. If you set SchemaValidationMode on the XsltTransformer, and supply the input to the XsltTransformer as an unparsed Stream, then it will be parsed with schema validation.

The alternative is to use the DocumentBuilder as you are doing, and make sure that it does the validation. To do this, you need to load the schema using SchemaManager.Compile, then create a SchemaValidator from this schema, and supply this to the DocumentBuilder.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
0

To be fair Michael Kay answered the question I'm just putting this in for completeness, and this is pretty much the minimal example.

This will fail (with an exception) during "compile", if the XSLT is referencing an invalid (I used "PurchaseOrder1") node that isnt referenced in the schema.

        var samplesDir = new Uri(AppDomain.CurrentDomain.BaseDirectory);
        // Create a Processor instance.
        Processor processor = new Processor(true);

        // Create a transformer for the stylesheet.
        //Xslt30Transformer transformer = processor.NewXsltCompiler().Compile(new Uri(samplesDir, "poNoValidation.xsl")).Load30();
        Xslt30Transformer transformer = processor.NewXsltCompiler().Compile(new Uri(samplesDir, "po.xsl")).Load30();

        // Create a serializer, with output to the standard output stream
        Serializer serializer = processor.NewSerializer();
        serializer.SetOutputWriter(Console.Out);

        // Transform the source XML and serialize the result document
        transformer.SchemaValidationMode = SchemaValidationMode.Strict;
        transformer.ApplyTemplates(File.OpenRead("po.xml"), serializer);
MrD at KookerellaLtd
  • 2,412
  • 1
  • 15
  • 17