com.helger.schematron.xslt.AbstractSchematronXSLTBasedResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ph-schematron Show documentation
Show all versions of ph-schematron Show documentation
Library for validating XML documents with Schematron
/**
* Copyright (C) 2014-2020 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.helger.schematron.xslt;
import java.net.URL;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.EntityResolver;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.ReturnsMutableObject;
import com.helger.commons.collection.impl.CommonsLinkedHashMap;
import com.helger.commons.collection.impl.ICommonsOrderedMap;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.io.file.FileHelper;
import com.helger.commons.io.resource.FileSystemResource;
import com.helger.commons.io.resource.IReadableResource;
import com.helger.commons.state.EValidity;
import com.helger.commons.string.ToStringGenerator;
import com.helger.commons.traits.IGenericImplTrait;
import com.helger.schematron.AbstractSchematronResource;
import com.helger.schematron.SchematronDebug;
import com.helger.schematron.svrl.SVRLMarshaller;
import com.helger.schematron.svrl.jaxb.SchematronOutputType;
import com.helger.schematron.xslt.validator.ISchematronXSLTValidator;
import com.helger.schematron.xslt.validator.SchematronXSLTValidatorDefault;
import com.helger.xml.XMLFactory;
import com.helger.xml.serialize.write.XMLWriter;
import com.helger.xml.transform.DefaultTransformURIResolver;
import com.helger.xml.transform.LoggingTransformErrorListener;
import net.sf.saxon.jaxp.TransformerImpl;
import net.sf.saxon.lib.StandardLogger;
import net.sf.saxon.s9api.XsltTransformer;
import net.sf.saxon.trace.TraceEventMulticaster;
import net.sf.saxon.trace.XSLTTraceListener;
/**
* Abstract implementation of a Schematron resource that is based on XSLT
* transformations.
*
* @author Philip Helger
* @param
* Implementation type
*/
@NotThreadSafe
public abstract class AbstractSchematronXSLTBasedResource > extends
AbstractSchematronResource implements
ISchematronXSLTBasedResource,
IGenericImplTrait
{
private static final Logger LOGGER = LoggerFactory.getLogger (AbstractSchematronXSLTBasedResource.class);
protected ErrorListener m_aCustomErrorListener;
protected URIResolver m_aCustomURIResolver = new DefaultTransformURIResolver ();
protected final ICommonsOrderedMap m_aCustomParameters = new CommonsLinkedHashMap <> ();
private ISchematronXSLTValidator m_aXSLTValidator = new SchematronXSLTValidatorDefault ();
@Nullable
private static String _findBaseURL (@Nonnull final IReadableResource aRes)
{
if (aRes instanceof FileSystemResource)
{
// Use parent file as resolution base
return FileHelper.getAsURLString (((FileSystemResource) aRes).getAsFile ().getParentFile ());
}
// Generic URL
final URL aBaseURL = aRes.getAsURL ();
return aBaseURL != null ? aBaseURL.toExternalForm () : null;
}
public AbstractSchematronXSLTBasedResource (@Nonnull final IReadableResource aSCHResource)
{
super (aSCHResource);
// The URI resolver is necessary for the XSLT to resolve URLs relative to
// the SCH
final String sBaseURL = _findBaseURL (aSCHResource);
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Using '" + sBaseURL + "' as base URL for SCH resource " + aSCHResource);
setURIResolver (new DefaultTransformURIResolver ().setDefaultBase (sBaseURL));
}
@Nullable
public final ErrorListener getErrorListener ()
{
return m_aCustomErrorListener;
}
@Nonnull
public final IMPLTYPE setErrorListener (@Nullable final ErrorListener aCustomErrorListener)
{
m_aCustomErrorListener = aCustomErrorListener;
return thisAsT ();
}
@Nullable
public final URIResolver getURIResolver ()
{
return m_aCustomURIResolver;
}
@Nonnull
public final IMPLTYPE setURIResolver (@Nullable final URIResolver aCustomURIResolver)
{
m_aCustomURIResolver = aCustomURIResolver;
return thisAsT ();
}
@Nonnull
@ReturnsMutableObject
public final ICommonsOrderedMap parameters ()
{
return m_aCustomParameters;
}
/**
* Set the XML entity resolver to be used when reading the XML to be
* validated.
*
* @param aEntityResolver
* The entity resolver to set. May be null
.
* @return this
* @since 4.2.3
*/
@Nonnull
public final IMPLTYPE setEntityResolver (@Nullable final EntityResolver aEntityResolver)
{
internalSetEntityResolver (aEntityResolver);
return thisAsT ();
}
/**
* @return The XSLT provider passed in the constructor. May be
* null
.
*/
@Nullable
public abstract ISchematronXSLTBasedProvider getXSLTProvider ();
/**
* @return The XSLT validator to be used. Never null
.
*/
@Nonnull
public final ISchematronXSLTValidator getXSLTValidator ()
{
return m_aXSLTValidator;
}
@Nonnull
public final IMPLTYPE setXSLTValidator (@Nonnull final ISchematronXSLTValidator aXSLTValidator)
{
ValueEnforcer.notNull (aXSLTValidator, "XSLTValidator");
m_aXSLTValidator = aXSLTValidator;
return thisAsT ();
}
public final boolean isValidSchematron ()
{
final ISchematronXSLTBasedProvider aXSLTProvider = getXSLTProvider ();
return aXSLTProvider != null && aXSLTProvider.isValidSchematron ();
}
@Nonnull
public final EValidity getSchematronValidity (@Nonnull final Node aXMLNode, @Nullable final String sBaseURI) throws Exception
{
ValueEnforcer.notNull (aXMLNode, "XMLNode");
// We don't have a short circuit here - apply the full validation
final SchematronOutputType aSO = applySchematronValidationToSVRL (aXMLNode, sBaseURI);
if (aSO == null)
return EValidity.INVALID;
// And now filter all elements that make the passed source invalid
return m_aXSLTValidator.getSchematronValidity (aSO);
}
@Nullable
public final Document applySchematronValidation (@Nonnull final Node aXMLNode,
@Nullable final String sBaseURI) throws TransformerException
{
ValueEnforcer.notNull (aXMLNode, "XMLNode");
final ISchematronXSLTBasedProvider aXSLTProvider = getXSLTProvider ();
if (aXSLTProvider == null || !aXSLTProvider.isValidSchematron ())
{
// We cannot progress because of invalid Schematron
return null;
}
// Debug print the created XSLT document
if (SchematronDebug.isShowCreatedXSLT ())
LOGGER.info ("Created XSLT document: " + XMLWriter.getNodeAsString (aXSLTProvider.getXSLTDocument ()));
// Create result document
final Document ret = XMLFactory.newDocument ();
// Create the transformer object from the templates specified in the
// constructor
final Transformer aTransformer = aXSLTProvider.getXSLTTransformer ();
// Apply customizations
// Ensure an error listener is present
if (m_aCustomErrorListener != null)
aTransformer.setErrorListener (m_aCustomErrorListener);
else
aTransformer.setErrorListener (new LoggingTransformErrorListener (Locale.US));
// Set the optional URI Resolver
if (m_aCustomURIResolver != null)
aTransformer.setURIResolver (m_aCustomURIResolver);
// Set all custom parameters
if (m_aCustomParameters != null)
for (final Map.Entry aEntry : m_aCustomParameters.entrySet ())
aTransformer.setParameter (aEntry.getKey (), aEntry.getValue ());
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Applying Schematron XSLT on XML [start]");
// Enable this for hardcore Saxon debugging only
if (false)
if (aTransformer.getClass ().getName ().equals ("net.sf.saxon.jaxp.TransformerImpl"))
{
final XsltTransformer aXT = ((TransformerImpl) aTransformer).getUnderlyingXsltTransformer ();
aXT.setMessageListener ( (a, b, c, d) -> LOGGER.info ("MessageListener2: " + a + ", " + b + ", " + c + ", " + d));
aXT.setTraceFunctionDestination (new StandardLogger (System.err));
if (false)
aXT.getUnderlyingController ().setTraceListener (new XSLTTraceListener ());
if (false)
{
final XSLTTraceListener aTL = new XSLTTraceListener ();
aTL.setOutputDestination (new StandardLogger (System.err));
aXT.getUnderlyingController ().setTraceListener (TraceEventMulticaster.add (aTL, null));
}
if (false)
System.out.println ("mode=" + aXT.getInitialMode ());
if (false)
System.out.println ("temp=" + aXT.getInitialTemplate ());
if (false)
System.out.println (aTransformer.getOutputProperties ());
}
// Do the main transformation
{
final DOMSource aSource = new DOMSource (aXMLNode);
aSource.setSystemId (sBaseURI);
aTransformer.transform (aSource, new DOMResult (ret));
}
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Applying Schematron XSLT on XML [end]");
// Debug print the created SVRL document
if (SchematronDebug.isShowCreatedSVRL ())
LOGGER.info ("Created SVRL:\n" + XMLWriter.getNodeAsString (ret));
return ret;
}
@Nullable
public SchematronOutputType applySchematronValidationToSVRL (@Nonnull final Node aXMLSource,
@Nullable final String sBaseURI) throws Exception
{
final Document aDoc = applySchematronValidation (aXMLSource, sBaseURI);
if (aDoc == null)
return null;
// Avoid NPE later on
if (aDoc.getDocumentElement () == null)
throw new IllegalStateException ("Internal error: created SVRL DOM Document has no document node!");
final SVRLMarshaller aMarshaller = new SVRLMarshaller ();
if (GlobalDebug.isDebugMode ())
{
// Set an exception callback that logs the source node as well
aMarshaller.readExceptionCallbacks ()
.set (ex -> LOGGER.error ("Error parsing the following SVRL:\n" + XMLWriter.getNodeAsString (aDoc), ex));
}
return aMarshaller.read (aDoc);
}
@Override
public String toString ()
{
return ToStringGenerator.getDerived (super.toString ()).append ("XSLTValidator", m_aXSLTValidator).getToString ();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy