View Javadoc
1   /**
2    * Copyright (C) 2014-2018 Philip Helger (www.helger.com)
3    * philip[at]helger[dot]com
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *         http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.helger.schematron.xslt;
18  
19  import java.io.File;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  import javax.annotation.concurrent.NotThreadSafe;
24  import javax.xml.transform.Templates;
25  import javax.xml.transform.Transformer;
26  import javax.xml.transform.TransformerConfigurationException;
27  import javax.xml.transform.dom.DOMResult;
28  
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  import org.w3c.dom.Document;
32  
33  import com.helger.commons.ValueEnforcer;
34  import com.helger.commons.io.file.FilenameHelper;
35  import com.helger.commons.io.file.SimpleFileIO;
36  import com.helger.commons.io.resource.ClassPathResource;
37  import com.helger.commons.io.resource.IReadableResource;
38  import com.helger.schematron.SchematronDebug;
39  import com.helger.schematron.saxon.SchematronTransformerFactory;
40  import com.helger.schematron.xslt.SCHTransformerCustomizer.EStep;
41  import com.helger.xml.serialize.write.XMLWriter;
42  import com.helger.xml.serialize.write.XMLWriterSettings;
43  import com.helger.xml.transform.TransformSourceFactory;
44  import com.helger.xml.transform.XMLTransformerFactory;
45  
46  /**
47   * The XSLT preprocessor used to convert a Schematron XML document into an XSLT
48   * document. This implementation uses JAXP with Saxon to be used as the
49   * respective parser.
50   *
51   * @author Philip Helger
52   */
53  @NotThreadSafe
54  public class SchematronProviderXSLTFromSCH implements ISchematronXSLTBasedProvider
55  {
56    private static final Logger LOGGER = LoggerFactory.getLogger (SchematronProviderXSLTFromSCH.class);
57  
58    /**
59     * The classpath directory where the Schematron 2 XSLT files reside.
60     */
61    public static final String SCHEMATRON_DIRECTORY_XSLT2 = "schematron/20100710-xslt2/";
62  
63    /**
64     * The class path to first XSLT to be applied.
65     */
66    public static final String XSLT2_STEP1 = SCHEMATRON_DIRECTORY_XSLT2 + "iso_dsdl_include.xsl";
67  
68    /**
69     * The class path to second XSLT to be applied.
70     */
71    public static final String XSLT2_STEP2 = SCHEMATRON_DIRECTORY_XSLT2 + "iso_abstract_expand.xsl";
72  
73    /**
74     * The class path to third and last XSLT to be applied.
75     */
76    public static final String XSLT2_STEP3 = SCHEMATRON_DIRECTORY_XSLT2 + "iso_svrl_for_xslt2.xsl";
77  
78    private static volatile Templates s_aStep1;
79    private static volatile Templates s_aStep2;
80    private static volatile Templates s_aStep3;
81  
82    private final IReadableResource m_aSchematronResource;
83    private Document m_aSchematronXSLTDoc;
84    private Templates m_aSchematronXSLTTemplates;
85  
86    @Deprecated
87    public static final boolean isSaveIntermediateFiles ()
88    {
89      return SchematronDebug.isSaveIntermediateXSLTFiles ();
90    }
91  
92    @Deprecated
93    public static final void setSaveIntermediateFiles (final boolean bSaveIntermediateFiles)
94    {
95      SchematronDebug.setSaveIntermediateXSLTFiles (bSaveIntermediateFiles);
96    }
97  
98    @Nonnull
99    @Deprecated
100   public static final File getIntermediateMinifiedSCHFolder ()
101   {
102     return SchematronDebug.getIntermediateMinifiedSCHFolder ();
103   }
104 
105   @Deprecated
106   public static final void setIntermediateMinifiedSCHFolder (@Nonnull final File aIntermediateMinifiedSCHFolder)
107   {
108     SchematronDebug.setIntermediateMinifiedSCHFolder (aIntermediateMinifiedSCHFolder);
109   }
110 
111   @Nonnull
112   @Deprecated
113   public static final File getIntermediateFinalXSLTFolder ()
114   {
115     return SchematronDebug.getIntermediateFinalXSLTFolder ();
116   }
117 
118   @Deprecated
119   public static final void setIntermediateFinalXSLTFolder (@Nonnull final File aIntermediateFinalXSLTFolder)
120   {
121     SchematronDebug.setIntermediateFinalXSLTFolder (aIntermediateFinalXSLTFolder);
122   }
123 
124   public static void cacheXSLTTemplates ()
125   {
126     // prepare all steps
127     if (s_aStep1 == null)
128     {
129       if (LOGGER.isDebugEnabled ())
130         LOGGER.debug ("Creating XSLT step 1 template");
131       s_aStep1 = XMLTransformerFactory.newTemplates (SchematronTransformerFactory.getDefaultSaxonFirst (),
132                                                      new ClassPathResource (XSLT2_STEP1,
133                                                                             SchematronProviderXSLTFromSCH.class.getClassLoader ()));
134     }
135     if (s_aStep2 == null)
136     {
137       if (LOGGER.isDebugEnabled ())
138         LOGGER.debug ("Creating XSLT step 2 template");
139       s_aStep2 = XMLTransformerFactory.newTemplates (SchematronTransformerFactory.getDefaultSaxonFirst (),
140                                                      new ClassPathResource (XSLT2_STEP2,
141                                                                             SchematronProviderXSLTFromSCH.class.getClassLoader ()));
142     }
143     if (s_aStep3 == null)
144     {
145       if (LOGGER.isDebugEnabled ())
146         LOGGER.debug ("Creating XSLT step 3 template");
147       s_aStep3 = XMLTransformerFactory.newTemplates (SchematronTransformerFactory.getDefaultSaxonFirst (),
148                                                      new ClassPathResource (XSLT2_STEP3,
149                                                                             SchematronProviderXSLTFromSCH.class.getClassLoader ()));
150     }
151   }
152 
153   /**
154    * Constructor
155    *
156    * @param aSchematronResource
157    *        SCH resource
158    * @param aTransformerCustomizer
159    *        The customizer for XSLT {@link Transformer} objects. May not be
160    *        <code>null</code>.
161    */
162   public SchematronProviderXSLTFromSCH (@Nonnull final IReadableResource aSchematronResource,
163                                         @Nonnull final SCHTransformerCustomizer aTransformerCustomizer)
164   {
165     m_aSchematronResource = ValueEnforcer.notNull (aSchematronResource, "SchematronResource");
166     ValueEnforcer.notNull (aTransformerCustomizer, "TransformerCustomizer");
167 
168     try
169     {
170       cacheXSLTTemplates ();
171 
172       // perform step 1 (Schematron -> ResultStep1)
173       final DOMResult aResult1 = new DOMResult ();
174       final Transformer aTransformer1 = s_aStep1.newTransformer ();
175       aTransformerCustomizer.customize (EStep.SCH2XSLT_1, aTransformer1);
176       aTransformer1.transform (TransformSourceFactory.create (aSchematronResource), aResult1);
177 
178       if (LOGGER.isDebugEnabled ())
179         LOGGER.debug ("Finished applying XSLT step 1 on " + aSchematronResource);
180 
181       // perform step 2 (ResultStep1 -> ResultStep2)
182       final DOMResult aResult2 = new DOMResult ();
183       final Transformer aTransformer2 = s_aStep2.newTransformer ();
184       aTransformerCustomizer.customize (EStep.SCH2XSLT_2, aTransformer2);
185       aTransformer2.transform (TransformSourceFactory.create (aResult1.getNode ()), aResult2);
186 
187       if (LOGGER.isDebugEnabled ())
188         LOGGER.debug ("Finished applying XSLT step 2 on " + aSchematronResource);
189 
190       if (SchematronDebug.isSaveIntermediateXSLTFiles ())
191       {
192         final String sXML = XMLWriter.getNodeAsString (aResult2.getNode ());
193         SimpleFileIO.writeFile (new File (SchematronDebug.getIntermediateMinifiedSCHFolder (),
194                                           FilenameHelper.getWithoutPath (aSchematronResource.getPath ()) +
195                                                                                                ".min-xslt.sch"),
196                                 sXML,
197                                 XMLWriterSettings.DEFAULT_XML_CHARSET_OBJ);
198       }
199 
200       // perform step 3 (ResultStep2 -> ResultStep3XSL)
201       final DOMResult aResult3 = new DOMResult ();
202       final Transformer aTransformer3 = s_aStep3.newTransformer ();
203       aTransformerCustomizer.customize (EStep.SCH2XSLT_3, aTransformer3);
204       aTransformer3.transform (TransformSourceFactory.create (aResult2.getNode ()), aResult3);
205 
206       if (LOGGER.isDebugEnabled ())
207         LOGGER.debug ("Finished applying XSLT step 3 on " + aSchematronResource);
208 
209       // Save the underlying XSLT document....
210       // Note: Saxon 6.5.5 does not allow to clone the document node!!!!
211       m_aSchematronXSLTDoc = (Document) aResult3.getNode ();
212 
213       if (SchematronDebug.isSaveIntermediateXSLTFiles ())
214       {
215         final String sXML = XMLWriter.getNodeAsString (m_aSchematronXSLTDoc);
216         SimpleFileIO.writeFile (new File (SchematronDebug.getIntermediateFinalXSLTFolder (),
217                                           FilenameHelper.getWithoutPath (aSchematronResource.getPath ()) + ".xslt"),
218                                 sXML,
219                                 XMLWriterSettings.DEFAULT_XML_CHARSET_OBJ);
220       }
221 
222       // compile result of step 3
223       m_aSchematronXSLTTemplates = XMLTransformerFactory.newTemplates (SchematronTransformerFactory.getDefaultSaxonFirst (),
224                                                                        TransformSourceFactory.create (m_aSchematronXSLTDoc));
225     }
226     catch (final Throwable t)
227     {
228       LOGGER.error ("Schematron preprocessor error", t);
229     }
230   }
231 
232   @Nonnull
233   public IReadableResource getSchematronResource ()
234   {
235     return m_aSchematronResource;
236   }
237 
238   public boolean isValidSchematron ()
239   {
240     return m_aSchematronXSLTTemplates != null;
241   }
242 
243   @Nullable
244   public Document getXSLTDocument ()
245   {
246     return m_aSchematronXSLTDoc;
247   }
248 
249   @Nullable
250   public Transformer getXSLTTransformer () throws TransformerConfigurationException
251   {
252     return m_aSchematronXSLTTemplates == null ? null : m_aSchematronXSLTTemplates.newTransformer ();
253   }
254 }