View Javadoc
1   /**
2    * Copyright (C) 2014-2017 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.pure.exchange;
18  
19  import javax.annotation.Nonnull;
20  import javax.annotation.Nullable;
21  import javax.annotation.concurrent.Immutable;
22  
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  import org.xml.sax.EntityResolver;
26  
27  import com.helger.commons.ValueEnforcer;
28  import com.helger.commons.io.resource.IReadableResource;
29  import com.helger.commons.string.StringParser;
30  import com.helger.commons.string.ToStringGenerator;
31  import com.helger.schematron.CSchematron;
32  import com.helger.schematron.CSchematronXML;
33  import com.helger.schematron.SchematronDebug;
34  import com.helger.schematron.SchematronHelper;
35  import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
36  import com.helger.schematron.pure.errorhandler.LoggingPSErrorHandler;
37  import com.helger.schematron.pure.model.*;
38  import com.helger.schematron.pure.model.PSDir.EDirValue;
39  import com.helger.schematron.pure.model.PSRichGroup.ESpace;
40  import com.helger.xml.microdom.IMicroDocument;
41  import com.helger.xml.microdom.IMicroElement;
42  import com.helger.xml.microdom.IMicroText;
43  import com.helger.xml.microdom.serialize.MicroWriter;
44  import com.helger.xml.serialize.read.SAXReaderSettings;
45  
46  /**
47   * Utility class for reading all Schematron elements from a resource.
48   *
49   * @author Philip Helger
50   */
51  @Immutable
52  public class PSReader
53  {
54    private static final Logger s_aLogger = LoggerFactory.getLogger (PSReader.class);
55  
56    private final IReadableResource m_aResource;
57    private final IPSErrorHandler m_aErrorHandler;
58    private final EntityResolver m_aEntityResolver;
59  
60    /**
61     * Constructor without an error handler
62     *
63     * @param aResource
64     *        The resource to read the Schematron from. May not be
65     *        <code>null</code>.
66     */
67    public PSReader (@Nonnull final IReadableResource aResource)
68    {
69      this (aResource, null, null);
70    }
71  
72    /**
73     * Constructor with an error handler
74     *
75     * @param aResource
76     *        The resource to read the Schematron from. May not be
77     *        <code>null</code>.
78     * @param aErrorHandler
79     *        The error handler to use. May be <code>null</code>. If the error
80     *        handler is <code>null</code> a {@link LoggingPSErrorHandler} is
81     *        automatically created and used.
82     */
83    public PSReader (@Nonnull final IReadableResource aResource, @Nullable final IPSErrorHandler aErrorHandler)
84    {
85      this (aResource, aErrorHandler, null);
86    }
87  
88    /**
89     * Constructor with an error handler
90     *
91     * @param aResource
92     *        The resource to read the Schematron from. May not be
93     *        <code>null</code>.
94     * @param aErrorHandler
95     *        The error handler to use. May be <code>null</code>. If the error
96     *        handler is <code>null</code> a {@link LoggingPSErrorHandler} is
97     *        automatically created and used.
98     * @param aEntityResolver
99     *        The XML entity resolver to be used. May be <code>null</code>.
100    * @since 4.1.1
101    */
102   public PSReader (@Nonnull final IReadableResource aResource,
103                    @Nullable final IPSErrorHandler aErrorHandler,
104                    @Nullable final EntityResolver aEntityResolver)
105   {
106     ValueEnforcer.notNull (aResource, "Resource");
107     m_aResource = aResource;
108     m_aErrorHandler = aErrorHandler != null ? aErrorHandler : new LoggingPSErrorHandler ();
109     m_aEntityResolver = aEntityResolver;
110   }
111 
112   /**
113    * @return The resource from which the Schematron schema is read. Never
114    *         <code>null</code>.
115    */
116   @Nonnull
117   public IReadableResource getResource ()
118   {
119     return m_aResource;
120   }
121 
122   /**
123    * @return The error handler used. If no error handler was passed in the
124    *         constructor, than a {@link LoggingPSErrorHandler} is automatically
125    *         used.
126    */
127   @Nonnull
128   public IPSErrorHandler getErrorHandler ()
129   {
130     return m_aErrorHandler;
131   }
132 
133   /**
134    * Utility method to get a real attribute value, by trimming spaces, if the
135    * value is non-<code>null</code>.
136    *
137    * @param sAttrValue
138    *        The source attribute value. May be <code>null</code>.
139    * @return <code>null</code> if the input parameter is <code>null</code>.
140    */
141   @Nullable
142   private static String _getAttributeValue (@Nullable final String sAttrValue)
143   {
144     return sAttrValue == null ? null : sAttrValue.trim ();
145   }
146 
147   /**
148    * Emit a warning with the registered error handler.
149    *
150    * @param aSourceElement
151    *        The source element where the error occurred.
152    * @param sMessage
153    *        The main warning message.
154    */
155   private void _warn (@Nonnull final IPSElement aSourceElement, @Nonnull final String sMessage)
156   {
157     ValueEnforcer.notNull (aSourceElement, "SourceElement");
158     ValueEnforcer.notNull (sMessage, "Message");
159 
160     m_aErrorHandler.warn (m_aResource, aSourceElement, sMessage);
161   }
162 
163   /**
164    * Read an &lt;active&gt; element
165    *
166    * @param eActive
167    *        The source micro element. Never <code>null</code>.
168    * @return The created domain object. May not be <code>null</code>.
169    */
170   @Nonnull
171   public PSActive readActiveFromXML (@Nonnull final IMicroElement eActive)
172   {
173     final PSActive ret = new PSActive ();
174     eActive.forAllAttributes ( (sNS, sAttrName, sVal) -> {
175       final String sAttrValue = _getAttributeValue (sVal);
176       if (sAttrName.equals (CSchematronXML.ATTR_PATTERN))
177         ret.setPattern (sAttrValue);
178       else
179         ret.addForeignAttribute (sAttrName, sAttrValue);
180     });
181 
182     eActive.forAllChildren (aActiveChild -> {
183       switch (aActiveChild.getType ())
184       {
185         case TEXT:
186           ret.addText (((IMicroText) aActiveChild).getNodeValue ());
187           break;
188         case ELEMENT:
189           final IMicroElement eElement = (IMicroElement) aActiveChild;
190           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
191           {
192             final String sLocalName = eElement.getLocalName ();
193             if (sLocalName.equals (CSchematronXML.ELEMENT_DIR))
194               ret.addDir (readDirFromXML (eElement));
195             else
196               if (sLocalName.equals (CSchematronXML.ELEMENT_EMPH))
197                 ret.addEmph (readEmphFromXML (eElement));
198               else
199                 if (sLocalName.equals (CSchematronXML.ELEMENT_SPAN))
200                   ret.addSpan (readSpanFromXML (eElement));
201                 else
202                   _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
203           }
204           else
205             ret.addForeignElement (eElement.getClone ());
206 
207           break;
208         case COMMENT:
209           // Ignore comments
210           break;
211         default:
212           _warn (ret, "Unsupported child node: " + aActiveChild);
213       }
214     });
215     return ret;
216   }
217 
218   private void _handleRichGroup (@Nonnull final String sAttrName,
219                                  @Nonnull final String sAttrValue,
220                                  @Nonnull final PSRichGroup aRichGroup)
221   {
222     if (sAttrName.equals (CSchematronXML.ATTR_ICON))
223       aRichGroup.setIcon (sAttrValue);
224     else
225       if (sAttrName.equals (CSchematronXML.ATTR_SEE))
226         aRichGroup.setSee (sAttrValue);
227       else
228         if (sAttrName.equals (CSchematronXML.ATTR_FPI))
229           aRichGroup.setFPI (sAttrValue);
230         else
231           if (sAttrName.equals (CSchematronXML.ATTR_XML_LANG))
232             aRichGroup.setXmlLang (sAttrValue);
233           else
234             if (sAttrName.equals (CSchematronXML.ATTR_XML_SPACE))
235               aRichGroup.setXmlSpace (ESpace.getFromIDOrNull (sAttrValue));
236   }
237 
238   private void _handleLinkableGroup (@Nonnull final String sAttrName,
239                                      @Nonnull final String sAttrValue,
240                                      @Nonnull final PSLinkableGroup aLinkableGroup)
241   {
242     if (sAttrName.equals (CSchematronXML.ATTR_ROLE))
243       aLinkableGroup.setRole (sAttrValue);
244     else
245       if (sAttrName.equals (CSchematronXML.ATTR_SUBJECT))
246         aLinkableGroup.setSubject (sAttrValue);
247   }
248 
249   /**
250    * Read an &lt;assert&gt; or a &lt;report&gt; element
251    *
252    * @param eAssertReport
253    *        The source micro element. Never <code>null</code>.
254    * @return The created domain object. May not be <code>null</code>.
255    */
256   @Nonnull
257   public PSAssertReport readAssertReportFromXML (@Nonnull final IMicroElement eAssertReport)
258   {
259     final PSAssertReport ret = new PSAssertReport (eAssertReport.getLocalName ()
260                                                                 .equals (CSchematronXML.ELEMENT_ASSERT));
261 
262     final PSRichGroup aRichGroup = new PSRichGroup ();
263     final PSLinkableGroup aLinkableGroup = new PSLinkableGroup ();
264     eAssertReport.forAllAttributes ( (sNS, sAttrName, sVal) -> {
265       final String sAttrValue = _getAttributeValue (sVal);
266       if (sAttrName.equals (CSchematronXML.ATTR_TEST))
267         ret.setTest (sAttrValue);
268       else
269         if (sAttrName.equals (CSchematronXML.ATTR_FLAG))
270           ret.setFlag (sAttrValue);
271         else
272           if (sAttrName.equals (CSchematronXML.ATTR_ID))
273             ret.setID (sAttrValue);
274           else
275             if (sAttrName.equals (CSchematronXML.ATTR_DIAGNOSTICS))
276               ret.setDiagnostics (sAttrValue);
277             else
278               if (PSRichGroup.isRichAttribute (sAttrName))
279                 _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
280               else
281                 if (PSLinkableGroup.isLinkableAttribute (sAttrName))
282                   _handleLinkableGroup (sAttrName, sAttrValue, aLinkableGroup);
283                 else
284                   ret.addForeignAttribute (sAttrName, sAttrValue);
285     });
286     ret.setRich (aRichGroup);
287     ret.setLinkable (aLinkableGroup);
288 
289     eAssertReport.forAllChildren (aAssertReportChild -> {
290       switch (aAssertReportChild.getType ())
291       {
292         case TEXT:
293           ret.addText (((IMicroText) aAssertReportChild).getNodeValue ());
294           break;
295         case ELEMENT:
296           final IMicroElement eElement = (IMicroElement) aAssertReportChild;
297           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
298           {
299             final String sLocalName = eElement.getLocalName ();
300             if (sLocalName.equals (CSchematronXML.ELEMENT_NAME))
301               ret.addName (readNameFromXML (eElement));
302             else
303               if (sLocalName.equals (CSchematronXML.ELEMENT_VALUE_OF))
304                 ret.addValueOf (readValueOfFromXML (eElement));
305               else
306                 if (sLocalName.equals (CSchematronXML.ELEMENT_EMPH))
307                   ret.addEmph (readEmphFromXML (eElement));
308                 else
309                   if (sLocalName.equals (CSchematronXML.ELEMENT_DIR))
310                     ret.addDir (readDirFromXML (eElement));
311                   else
312                     if (sLocalName.equals (CSchematronXML.ELEMENT_SPAN))
313                       ret.addSpan (readSpanFromXML (eElement));
314                     else
315                       _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
316           }
317           else
318             ret.addForeignElement (eElement.getClone ());
319 
320           break;
321         case COMMENT:
322           // Ignore comments
323           break;
324         default:
325           _warn (ret, "Unsupported child node: " + aAssertReportChild);
326       }
327     });
328     return ret;
329   }
330 
331   /**
332    * Read a &lt;diagnostic&gt; element
333    *
334    * @param eDiagnostic
335    *        The source micro element. Never <code>null</code>.
336    * @return The created domain object. May not be <code>null</code>.
337    */
338   @Nonnull
339   public PSDiagnostic readDiagnosticFromXML (@Nonnull final IMicroElement eDiagnostic)
340   {
341     final PSDiagnostic ret = new PSDiagnostic ();
342 
343     final PSRichGroup aRichGroup = new PSRichGroup ();
344     eDiagnostic.forAllAttributes ( (sNS, sAttrName, sVal) -> {
345       final String sAttrValue = _getAttributeValue (sVal);
346       if (sAttrName.equals (CSchematronXML.ATTR_ID))
347         ret.setID (sAttrValue);
348       else
349         if (PSRichGroup.isRichAttribute (sAttrName))
350           _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
351         else
352           ret.addForeignAttribute (sAttrName, sAttrValue);
353     });
354     ret.setRich (aRichGroup);
355 
356     eDiagnostic.forAllChildren (aDiagnosticChild -> {
357       switch (aDiagnosticChild.getType ())
358       {
359         case TEXT:
360           ret.addText (((IMicroText) aDiagnosticChild).getNodeValue ());
361           break;
362         case ELEMENT:
363           final IMicroElement eElement = (IMicroElement) aDiagnosticChild;
364           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
365           {
366             final String sLocalName = eElement.getLocalName ();
367             if (sLocalName.equals (CSchematronXML.ELEMENT_VALUE_OF))
368               ret.addValueOf (readValueOfFromXML (eElement));
369             else
370               if (sLocalName.equals (CSchematronXML.ELEMENT_EMPH))
371                 ret.addEmph (readEmphFromXML (eElement));
372               else
373                 if (sLocalName.equals (CSchematronXML.ELEMENT_DIR))
374                   ret.addDir (readDirFromXML (eElement));
375                 else
376                   if (sLocalName.equals (CSchematronXML.ELEMENT_SPAN))
377                     ret.addSpan (readSpanFromXML (eElement));
378                   else
379                     _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
380           }
381           else
382             ret.addForeignElement (eElement.getClone ());
383 
384           break;
385         case COMMENT:
386           // Ignore comments
387           break;
388         default:
389           _warn (ret, "Unsupported child node: " + aDiagnosticChild);
390       }
391     });
392     return ret;
393   }
394 
395   /**
396    * Read a &lt;diagnostics&gt; element
397    *
398    * @param eDiagnostics
399    *        The source micro element. Never <code>null</code>.
400    * @return The created domain object. May not be <code>null</code>.
401    */
402   @Nonnull
403   public PSDiagnostics readDiagnosticsFromXML (@Nonnull final IMicroElement eDiagnostics)
404   {
405     final PSDiagnostics ret = new PSDiagnostics ();
406 
407     eDiagnostics.forAllAttributes ( (sNS, sAttrName, sVal) -> {
408       final String sAttrValue = _getAttributeValue (sVal);
409       ret.addForeignAttribute (sAttrName, sAttrValue);
410     });
411 
412     eDiagnostics.forAllChildElements (eDiagnosticsChild -> {
413       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eDiagnosticsChild.getNamespaceURI ()))
414       {
415         if (eDiagnosticsChild.getLocalName ().equals (CSchematronXML.ELEMENT_INCLUDE))
416           ret.addInclude (readIncludeFromXML (eDiagnosticsChild));
417         else
418           if (eDiagnosticsChild.getLocalName ().equals (CSchematronXML.ELEMENT_DIAGNOSTIC))
419             ret.addDiagnostic (readDiagnosticFromXML (eDiagnosticsChild));
420           else
421             _warn (ret, "Unsupported Schematron element '" + eDiagnosticsChild.getLocalName () + "'");
422       }
423       else
424         ret.addForeignElement (eDiagnosticsChild.getClone ());
425     });
426     return ret;
427   }
428 
429   /**
430    * Read a &lt;dir&gt; element
431    *
432    * @param eDir
433    *        The source micro element. Never <code>null</code>.
434    * @return The created domain object. May not be <code>null</code>.
435    */
436   @Nonnull
437   public PSDir readDirFromXML (@Nonnull final IMicroElement eDir)
438   {
439     final PSDir ret = new PSDir ();
440 
441     eDir.forAllAttributes ( (sNS, sAttrName, sVal) -> {
442       final String sAttrValue = _getAttributeValue (sVal);
443       if (sAttrName.equals (CSchematronXML.ATTR_VALUE))
444         ret.setValue (EDirValue.getFromIDOrNull (sAttrValue));
445       else
446         ret.addForeignAttribute (sAttrName, sAttrValue);
447     });
448 
449     eDir.forAllChildren (aDirChild -> {
450       switch (aDirChild.getType ())
451       {
452         case TEXT:
453           ret.addText (((IMicroText) aDirChild).getNodeValue ());
454           break;
455         case ELEMENT:
456           final IMicroElement eElement = (IMicroElement) aDirChild;
457           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
458           {
459             _warn (ret, "Unsupported Schematron element '" + eElement.getLocalName () + "'");
460           }
461           else
462             ret.addForeignElement (eElement.getClone ());
463 
464           break;
465         case COMMENT:
466           // Ignore comments
467           break;
468         default:
469           _warn (ret, "Unsupported child node: " + aDirChild);
470       }
471     });
472     return ret;
473   }
474 
475   /**
476    * Read an &lt;emph&gt; element
477    *
478    * @param eEmph
479    *        The source micro element. Never <code>null</code>.
480    * @return The created domain object. May not be <code>null</code>.
481    */
482   @Nonnull
483   public PSEmph readEmphFromXML (@Nonnull final IMicroElement eEmph)
484   {
485     final PSEmph ret = new PSEmph ();
486 
487     eEmph.forAllAttributes ( (sNS, sAttrName, sVal) -> {
488       final String sAttrValue = _getAttributeValue (sVal);
489       _warn (ret, "Unsupported attribute '" + sAttrName + "'='" + sAttrValue + "'");
490     });
491 
492     eEmph.forAllChildren (aEmphChild -> {
493       switch (aEmphChild.getType ())
494       {
495         case TEXT:
496           ret.addText (((IMicroText) aEmphChild).getNodeValue ());
497           break;
498         case ELEMENT:
499           final IMicroElement eElement = (IMicroElement) aEmphChild;
500           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
501           {
502             _warn (ret, "Unsupported Schematron element '" + eElement.getLocalName () + "'");
503           }
504           else
505             _warn (ret, "Unsupported namespace URI '" + eElement.getNamespaceURI () + "'");
506 
507           break;
508         case COMMENT:
509           // Ignore comments
510           break;
511         default:
512           _warn (ret, "Unsupported child node: " + aEmphChild);
513       }
514     });
515     return ret;
516   }
517 
518   /**
519    * Read an &lt;extends&gt; element
520    *
521    * @param eExtends
522    *        The source micro element. Never <code>null</code>.
523    * @return The created domain object. May not be <code>null</code>.
524    */
525   @Nonnull
526   public PSExtends readExtendsFromXML (@Nonnull final IMicroElement eExtends)
527   {
528     final PSExtends ret = new PSExtends ();
529 
530     eExtends.forAllAttributes ( (sNS, sAttrName, sVal) -> {
531       final String sAttrValue = _getAttributeValue (sVal);
532       if (sAttrName.equals (CSchematronXML.ATTR_RULE))
533         ret.setRule (sAttrValue);
534       else
535         ret.addForeignAttribute (sAttrName, sAttrValue);
536     });
537 
538     eExtends.forAllChildElements (eChild -> {
539       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eChild.getNamespaceURI ()))
540       {
541         _warn (ret, "Unsupported Schematron element '" + eChild.getLocalName () + "'");
542       }
543       else
544         _warn (ret, "Unsupported namespace URI '" + eChild.getNamespaceURI () + "'");
545     });
546     return ret;
547   }
548 
549   /**
550    * Read an &lt;include&gt; element
551    *
552    * @param eInclude
553    *        The source micro element. Never <code>null</code>.
554    * @return The created domain object. May not be <code>null</code>.
555    */
556   @Nonnull
557   public PSInclude readIncludeFromXML (@Nonnull final IMicroElement eInclude)
558   {
559     final PSInclude ret = new PSInclude ();
560 
561     eInclude.forAllAttributes ( (sNS, sAttrName, sVal) -> {
562       final String sAttrValue = _getAttributeValue (sVal);
563       if (sAttrName.equals (CSchematronXML.ATTR_HREF))
564         ret.setHref (sAttrValue);
565       else
566         _warn (ret, "Unsupported attribute '" + sAttrName + "'='" + sAttrValue + "'");
567     });
568 
569     eInclude.forAllChildElements (eValueOfChild -> {
570       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eValueOfChild.getNamespaceURI ()))
571       {
572         _warn (ret, "Unsupported Schematron element '" + eValueOfChild.getLocalName () + "'");
573       }
574       else
575         _warn (ret, "Unsupported namespace URI '" + eValueOfChild.getNamespaceURI () + "'");
576     });
577     return ret;
578   }
579 
580   /**
581    * Read a &lt;let&gt; element
582    *
583    * @param eLet
584    *        The source micro element. Never <code>null</code>.
585    * @return The created domain object. May not be <code>null</code>.
586    */
587   @Nonnull
588   public PSLet readLetFromXML (@Nonnull final IMicroElement eLet)
589   {
590     final PSLet ret = new PSLet ();
591 
592     eLet.forAllAttributes ( (sNS, sAttrName, sVal) -> {
593       final String sAttrValue = _getAttributeValue (sVal);
594       if (sAttrName.equals (CSchematronXML.ATTR_NAME))
595         ret.setName (sAttrValue);
596       else
597         if (sAttrName.equals (CSchematronXML.ATTR_VALUE))
598           ret.setValue (sAttrValue);
599         else
600           _warn (ret, "Unsupported attribute '" + sAttrName + "'='" + sAttrValue + "'");
601     });
602 
603     eLet.forAllChildElements (eLetChild -> {
604       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eLetChild.getNamespaceURI ()))
605       {
606         _warn (ret, "Unsupported Schematron element '" + eLetChild.getLocalName () + "'");
607       }
608       else
609         _warn (ret, "Unsupported namespace URI '" + eLetChild.getNamespaceURI () + "'");
610     });
611     return ret;
612   }
613 
614   /**
615    * Read a &lt;name&gt; element
616    *
617    * @param eName
618    *        The source micro element. Never <code>null</code>.
619    * @return The created domain object. May not be <code>null</code>.
620    */
621   @Nonnull
622   public PSName readNameFromXML (@Nonnull final IMicroElement eName)
623   {
624     final PSName ret = new PSName ();
625 
626     eName.forAllAttributes ( (sNS, sAttrName, sVal) -> {
627       final String sAttrValue = _getAttributeValue (sVal);
628       if (sAttrName.equals (CSchematronXML.ATTR_PATH))
629         ret.setPath (sAttrValue);
630       else
631         ret.addForeignAttribute (sAttrName, sAttrValue);
632     });
633 
634     eName.forAllChildElements (eNameChild -> {
635       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eNameChild.getNamespaceURI ()))
636       {
637         _warn (ret, "Unsupported Schematron element '" + eNameChild.getLocalName () + "'");
638       }
639       else
640         _warn (ret, "Unsupported namespace URI '" + eNameChild.getNamespaceURI () + "'");
641     });
642     return ret;
643   }
644 
645   /**
646    * Read a &lt;ns&gt; element
647    *
648    * @param eNS
649    *        The source micro element. Never <code>null</code>.
650    * @return The created domain object. May not be <code>null</code>.
651    */
652   @Nonnull
653   public PSNS readNSFromXML (@Nonnull final IMicroElement eNS)
654   {
655     final PSNS ret = new PSNS ();
656 
657     eNS.forAllAttributes ( (sNS, sAttrName, sVal) -> {
658       final String sAttrValue = _getAttributeValue (sVal);
659       if (sAttrName.equals (CSchematronXML.ATTR_URI))
660         ret.setUri (sAttrValue);
661       else
662         if (sAttrName.equals (CSchematronXML.ATTR_PREFIX))
663           ret.setPrefix (sAttrValue);
664         else
665           ret.addForeignAttribute (sAttrName, sAttrValue);
666     });
667 
668     eNS.forAllChildElements (eLetChild -> {
669       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eLetChild.getNamespaceURI ()))
670       {
671         _warn (ret, "Unsupported Schematron element '" + eLetChild.getLocalName () + "'");
672       }
673       else
674         _warn (ret, "Unsupported namespace URI '" + eLetChild.getNamespaceURI () + "'");
675     });
676     return ret;
677   }
678 
679   /**
680    * Read a &lt;p&gt; element
681    *
682    * @param eP
683    *        The source micro element. Never <code>null</code>.
684    * @return The created domain object. May not be <code>null</code>.
685    */
686   @Nonnull
687   public PSP readPFromXML (@Nonnull final IMicroElement eP)
688   {
689     final PSP ret = new PSP ();
690     eP.forAllAttributes ( (sNS, sAttrName, sVal) -> {
691       final String sAttrValue = _getAttributeValue (sVal);
692       if (sAttrName.equals (CSchematronXML.ATTR_ID))
693         ret.setID (sAttrValue);
694       else
695         if (sAttrName.equals (CSchematronXML.ATTR_CLASS))
696           ret.setClazz (sAttrValue);
697         else
698           if (sAttrName.equals (CSchematronXML.ATTR_ICON))
699             ret.setIcon (sAttrValue);
700           else
701             ret.addForeignAttribute (sAttrName, sAttrValue);
702     });
703 
704     eP.forAllChildren (aChild -> {
705       switch (aChild.getType ())
706       {
707         case TEXT:
708           ret.addText (((IMicroText) aChild).getNodeValue ());
709           break;
710         case ELEMENT:
711           final IMicroElement eElement = (IMicroElement) aChild;
712           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
713           {
714             final String sLocalName = eElement.getLocalName ();
715             if (sLocalName.equals (CSchematronXML.ELEMENT_DIR))
716               ret.addDir (readDirFromXML (eElement));
717             else
718               if (sLocalName.equals (CSchematronXML.ELEMENT_EMPH))
719                 ret.addEmph (readEmphFromXML (eElement));
720               else
721                 if (sLocalName.equals (CSchematronXML.ELEMENT_SPAN))
722                   ret.addSpan (readSpanFromXML (eElement));
723                 else
724                   _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
725           }
726           else
727             ret.addForeignElement (eElement.getClone ());
728 
729           break;
730         case COMMENT:
731           // Ignore comments
732           break;
733         default:
734           _warn (ret, "Unsupported child node: " + aChild);
735       }
736     });
737     return ret;
738   }
739 
740   /**
741    * Read a &lt;param&gt; element
742    *
743    * @param eParam
744    *        The source micro element. Never <code>null</code>.
745    * @return The created domain object. May not be <code>null</code>.
746    */
747   @Nonnull
748   public PSParam readParamFromXML (@Nonnull final IMicroElement eParam)
749   {
750     final PSParam ret = new PSParam ();
751 
752     eParam.forAllAttributes ( (sNS, sAttrName, sVal) -> {
753       final String sAttrValue = _getAttributeValue (sVal);
754       if (sAttrName.equals (CSchematronXML.ATTR_NAME))
755         ret.setName (sAttrValue);
756       else
757         if (sAttrName.equals (CSchematronXML.ATTR_VALUE))
758           ret.setValue (sAttrValue);
759         else
760           _warn (ret, "Unsupported attribute '" + sAttrName + "'='" + sAttrValue + "'");
761     });
762 
763     eParam.forAllChildElements (eParamChild -> {
764       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eParamChild.getNamespaceURI ()))
765       {
766         _warn (ret, "Unsupported Schematron element '" + eParamChild.getLocalName () + "'");
767       }
768       else
769         _warn (ret, "Unsupported namespace URI '" + eParamChild.getNamespaceURI () + "'");
770     });
771     return ret;
772   }
773 
774   /**
775    * Read a &lt;pattern&gt; element
776    *
777    * @param ePattern
778    *        The source micro element. Never <code>null</code>.
779    * @return The created domain object. May not be <code>null</code>.
780    */
781   @Nonnull
782   public PSPattern readPatternFromXML (@Nonnull final IMicroElement ePattern)
783   {
784     final PSPattern ret = new PSPattern ();
785 
786     final PSRichGroup aRichGroup = new PSRichGroup ();
787     ePattern.forAllAttributes ( (sNS, sAttrName, sVal) -> {
788       final String sAttrValue = _getAttributeValue (sVal);
789       if (sAttrName.equals (CSchematronXML.ATTR_ABSTRACT))
790         ret.setAbstract (StringParser.parseBool (sAttrValue));
791       else
792         if (sAttrName.equals (CSchematronXML.ATTR_ID))
793           ret.setID (sAttrValue);
794         else
795           if (sAttrName.equals (CSchematronXML.ATTR_IS_A))
796             ret.setIsA (sAttrValue);
797           else
798             if (PSRichGroup.isRichAttribute (sAttrName))
799               _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
800             else
801               ret.addForeignAttribute (sAttrName, sAttrValue);
802     });
803     ret.setRich (aRichGroup);
804 
805     ePattern.forAllChildElements (ePatternChild -> {
806       if (CSchematron.NAMESPACE_SCHEMATRON.equals (ePatternChild.getNamespaceURI ()))
807       {
808         if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_INCLUDE))
809           ret.addInclude (readIncludeFromXML (ePatternChild));
810         else
811           if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_TITLE))
812             ret.setTitle (readTitleFromXML (ePatternChild));
813           else
814             if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_P))
815               ret.addP (readPFromXML (ePatternChild));
816             else
817               if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_LET))
818                 ret.addLet (readLetFromXML (ePatternChild));
819               else
820                 if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_RULE))
821                   ret.addRule (readRuleFromXML (ePatternChild));
822                 else
823                   if (ePatternChild.getLocalName ().equals (CSchematronXML.ELEMENT_PARAM))
824                     ret.addParam (readParamFromXML (ePatternChild));
825                   else
826                     _warn (ret,
827                            "Unsupported Schematron element '" +
828                                 ePatternChild.getLocalName () +
829                                 "' in " +
830                                 ret.toString ());
831       }
832       else
833         ret.addForeignElement (ePatternChild.getClone ());
834     });
835     return ret;
836   }
837 
838   /**
839    * Read a &lt;phase&gt; element
840    *
841    * @param ePhase
842    *        The source micro element. Never <code>null</code>.
843    * @return The created domain object. May not be <code>null</code>.
844    */
845   @Nonnull
846   public PSPhase readPhaseFromXML (@Nonnull final IMicroElement ePhase)
847   {
848     final PSPhase ret = new PSPhase ();
849 
850     final PSRichGroup aRichGroup = new PSRichGroup ();
851     ePhase.forAllAttributes ( (sNS, sAttrName, sVal) -> {
852       final String sAttrValue = _getAttributeValue (sVal);
853       if (sAttrName.equals (CSchematronXML.ATTR_ID))
854         ret.setID (sAttrValue);
855       else
856         if (PSRichGroup.isRichAttribute (sAttrName))
857           _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
858         else
859           ret.addForeignAttribute (sAttrName, sAttrValue);
860     });
861     ret.setRich (aRichGroup);
862 
863     ePhase.forAllChildElements (ePhaseChild -> {
864       if (CSchematron.NAMESPACE_SCHEMATRON.equals (ePhaseChild.getNamespaceURI ()))
865       {
866         if (ePhaseChild.getLocalName ().equals (CSchematronXML.ELEMENT_INCLUDE))
867           ret.addInclude (readIncludeFromXML (ePhaseChild));
868         else
869           if (ePhaseChild.getLocalName ().equals (CSchematronXML.ELEMENT_P))
870             ret.addP (readPFromXML (ePhaseChild));
871           else
872             if (ePhaseChild.getLocalName ().equals (CSchematronXML.ELEMENT_LET))
873               ret.addLet (readLetFromXML (ePhaseChild));
874             else
875               if (ePhaseChild.getLocalName ().equals (CSchematronXML.ELEMENT_ACTIVE))
876                 ret.addActive (readActiveFromXML (ePhaseChild));
877               else
878                 _warn (ret, "Unsupported Schematron element '" + ePhaseChild.getLocalName () + "'");
879       }
880       else
881         ret.addForeignElement (ePhaseChild.getClone ());
882     });
883     return ret;
884   }
885 
886   /**
887    * Read a &lt;rule&gt; element
888    *
889    * @param eRule
890    *        The source micro element. Never <code>null</code>.
891    * @return The created domain object. May not be <code>null</code>.
892    */
893   @Nonnull
894   public PSRule readRuleFromXML (@Nonnull final IMicroElement eRule)
895   {
896     final PSRule ret = new PSRule ();
897 
898     final PSRichGroup aRichGroup = new PSRichGroup ();
899     final PSLinkableGroup aLinkableGroup = new PSLinkableGroup ();
900     eRule.forAllAttributes ( (sNS, sAttrName, sVal) -> {
901       final String sAttrValue = _getAttributeValue (sVal);
902       if (sAttrName.equals (CSchematronXML.ATTR_FLAG))
903         ret.setFlag (sAttrValue);
904       else
905         if (sAttrName.equals (CSchematronXML.ATTR_ABSTRACT))
906           ret.setAbstract (StringParser.parseBool (sAttrValue));
907         else
908           if (sAttrName.equals (CSchematronXML.ATTR_CONTEXT))
909             ret.setContext (sAttrValue);
910           else
911             if (sAttrName.equals (CSchematronXML.ATTR_ID))
912               ret.setID (sAttrValue);
913             else
914               if (PSRichGroup.isRichAttribute (sAttrName))
915                 _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
916               else
917                 if (PSLinkableGroup.isLinkableAttribute (sAttrName))
918                   _handleLinkableGroup (sAttrName, sAttrValue, aLinkableGroup);
919                 else
920                   ret.addForeignAttribute (sAttrName, sAttrValue);
921     });
922     ret.setRich (aRichGroup);
923     ret.setLinkable (aLinkableGroup);
924 
925     eRule.forAllChildElements (eRuleChild -> {
926       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eRuleChild.getNamespaceURI ()))
927       {
928         final String sLocalName = eRuleChild.getLocalName ();
929         if (sLocalName.equals (CSchematronXML.ELEMENT_INCLUDE))
930           ret.addInclude (readIncludeFromXML (eRuleChild));
931         else
932           if (sLocalName.equals (CSchematronXML.ELEMENT_LET))
933             ret.addLet (readLetFromXML (eRuleChild));
934           else
935             if (sLocalName.equals (CSchematronXML.ELEMENT_ASSERT) || sLocalName.equals (CSchematronXML.ELEMENT_REPORT))
936               ret.addAssertReport (readAssertReportFromXML (eRuleChild));
937             else
938               if (sLocalName.equals (CSchematronXML.ELEMENT_EXTENDS))
939                 ret.addExtends (readExtendsFromXML (eRuleChild));
940               else
941                 _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
942       }
943       else
944         ret.addForeignElement (eRuleChild.getClone ());
945     });
946     return ret;
947   }
948 
949   /**
950    * Parse the Schematron into a pure Java object. This method makes no
951    * assumptions on the validity of the document!
952    *
953    * @param eSchema
954    *        The XML element to use. May not be <code>null</code>.
955    * @return The created {@link PSSchema} object or <code>null</code> in case of
956    *         <code>null</code> document or a fatal error.
957    * @throws SchematronReadException
958    *         If reading fails
959    */
960   @Nonnull
961   public PSSchema readSchemaFromXML (@Nonnull final IMicroElement eSchema) throws SchematronReadException
962   {
963     ValueEnforcer.notNull (eSchema, "Schema");
964     if (!CSchematron.NAMESPACE_SCHEMATRON.equals (eSchema.getNamespaceURI ()))
965       throw new SchematronReadException (m_aResource, "The passed element is not an ISO Schematron element!");
966 
967     final PSSchema ret = new PSSchema (m_aResource);
968     final PSRichGroup aRichGroup = new PSRichGroup ();
969     eSchema.forAllAttributes ( (sNS, sAttrName, sVal) -> {
970       final String sAttrValue = _getAttributeValue (sVal);
971       if (sAttrName.equals (CSchematronXML.ATTR_ID))
972         ret.setID (sAttrValue);
973       else
974         if (sAttrName.equals (CSchematronXML.ATTR_SCHEMA_VERSION))
975           ret.setSchemaVersion (sAttrValue);
976         else
977           if (sAttrName.equals (CSchematronXML.ATTR_DEFAULT_PHASE))
978             ret.setDefaultPhase (sAttrValue);
979           else
980             if (sAttrName.equals (CSchematronXML.ATTR_QUERY_BINDING))
981               ret.setQueryBinding (sAttrValue);
982             else
983               if (PSRichGroup.isRichAttribute (sAttrName))
984                 _handleRichGroup (sAttrName, sAttrValue, aRichGroup);
985               else
986                 ret.addForeignAttribute (sAttrName, sAttrValue);
987     });
988     ret.setRich (aRichGroup);
989 
990     eSchema.forAllChildElements (eSchemaChild -> {
991       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eSchemaChild.getNamespaceURI ()))
992       {
993         if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_INCLUDE))
994           ret.addInclude (readIncludeFromXML (eSchemaChild));
995         else
996           if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_TITLE))
997             ret.setTitle (readTitleFromXML (eSchemaChild));
998           else
999             if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_NS))
1000               ret.addNS (readNSFromXML (eSchemaChild));
1001             else
1002               if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_P))
1003               {
1004                 final PSP aP = readPFromXML (eSchemaChild);
1005                 if (ret.hasNoPatterns ())
1006                   ret.addStartP (aP);
1007                 else
1008                   ret.addEndP (aP);
1009               }
1010               else
1011                 if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_LET))
1012                   ret.addLet (readLetFromXML (eSchemaChild));
1013                 else
1014                   if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_PHASE))
1015                     ret.addPhase (readPhaseFromXML (eSchemaChild));
1016                   else
1017                     if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_PATTERN))
1018                       ret.addPattern (readPatternFromXML (eSchemaChild));
1019                     else
1020                       if (eSchemaChild.getLocalName ().equals (CSchematronXML.ELEMENT_DIAGNOSTICS))
1021                         ret.setDiagnostics (readDiagnosticsFromXML (eSchemaChild));
1022                       else
1023                         _warn (ret, "Unsupported Schematron element '" + eSchemaChild.getLocalName () + "'");
1024       }
1025       else
1026         ret.addForeignElement (eSchemaChild.getClone ());
1027     });
1028     return ret;
1029   }
1030 
1031   /**
1032    * Read a &lt;span&gt; element
1033    *
1034    * @param eSpan
1035    *        The source micro element. Never <code>null</code>.
1036    * @return The created domain object. May not be <code>null</code>.
1037    */
1038   @Nonnull
1039   public PSSpan readSpanFromXML (@Nonnull final IMicroElement eSpan)
1040   {
1041     final PSSpan ret = new PSSpan ();
1042 
1043     eSpan.forAllAttributes ( (sNS, sAttrName, sVal) -> {
1044       final String sAttrValue = _getAttributeValue (sVal);
1045       if (sAttrName.equals (CSchematronXML.ATTR_CLASS))
1046         ret.setClazz (sAttrValue);
1047       else
1048         ret.addForeignAttribute (sAttrName, sAttrValue);
1049     });
1050 
1051     eSpan.forAllChildren (aSpanChild -> {
1052       switch (aSpanChild.getType ())
1053       {
1054         case TEXT:
1055           ret.addText (((IMicroText) aSpanChild).getNodeValue ());
1056           break;
1057         case ELEMENT:
1058           final IMicroElement eElement = (IMicroElement) aSpanChild;
1059           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
1060           {
1061             _warn (ret, "Unsupported Schematron element '" + eElement.getLocalName () + "'");
1062           }
1063           else
1064             ret.addForeignElement (eElement.getClone ());
1065 
1066           break;
1067         case COMMENT:
1068           // Ignore comments
1069           break;
1070         default:
1071           _warn (ret, "Unsupported child node: " + aSpanChild);
1072       }
1073     });
1074     return ret;
1075   }
1076 
1077   /**
1078    * Read a &lt;title&gt; element
1079    *
1080    * @param eTitle
1081    *        The source micro element. Never <code>null</code>.
1082    * @return The created domain object. May not be <code>null</code>.
1083    */
1084   @Nonnull
1085   public PSTitle readTitleFromXML (@Nonnull final IMicroElement eTitle)
1086   {
1087     final PSTitle ret = new PSTitle ();
1088 
1089     eTitle.forAllAttributes ( (sNS, sAttrName, sVal) -> {
1090       final String sAttrValue = _getAttributeValue (sVal);
1091       _warn (ret, "Unsupported attribute '" + sAttrName + "'='" + sAttrValue + "'");
1092     });
1093 
1094     eTitle.forAllChildren (aTitleChild -> {
1095       switch (aTitleChild.getType ())
1096       {
1097         case TEXT:
1098           ret.addText (((IMicroText) aTitleChild).getNodeValue ());
1099           break;
1100         case ELEMENT:
1101           final IMicroElement eElement = (IMicroElement) aTitleChild;
1102           if (CSchematron.NAMESPACE_SCHEMATRON.equals (eElement.getNamespaceURI ()))
1103           {
1104             final String sLocalName = eElement.getLocalName ();
1105             if (sLocalName.equals (CSchematronXML.ELEMENT_DIR))
1106               ret.addDir (readDirFromXML (eElement));
1107             else
1108               _warn (ret, "Unsupported Schematron element '" + sLocalName + "'");
1109           }
1110           else
1111             _warn (ret, "Unsupported namespace URI '" + eElement.getNamespaceURI () + "'");
1112 
1113           break;
1114         case COMMENT:
1115           // Ignore comments
1116           break;
1117         default:
1118           _warn (ret, "Unsupported child node: " + aTitleChild);
1119       }
1120     });
1121     return ret;
1122   }
1123 
1124   /**
1125    * Read a &lt;value-of&gt; element
1126    *
1127    * @param eValueOf
1128    *        The source micro element. Never <code>null</code>.
1129    * @return The created domain object. May not be <code>null</code>.
1130    */
1131   @Nonnull
1132   public PSValueOf readValueOfFromXML (@Nonnull final IMicroElement eValueOf)
1133   {
1134     final PSValueOf ret = new PSValueOf ();
1135 
1136     eValueOf.forAllAttributes ( (sNS, sAttrName, sVal) -> {
1137       final String sAttrValue = _getAttributeValue (sVal);
1138       if (sAttrName.equals (CSchematronXML.ATTR_SELECT))
1139         ret.setSelect (sAttrValue);
1140       else
1141         ret.addForeignAttribute (sAttrName, sAttrValue);
1142     });
1143 
1144     eValueOf.forAllChildElements (eValueOfChild -> {
1145       if (CSchematron.NAMESPACE_SCHEMATRON.equals (eValueOfChild.getNamespaceURI ()))
1146       {
1147         _warn (ret, "Unsupported Schematron element '" + eValueOfChild.getLocalName () + "'");
1148       }
1149       else
1150         _warn (ret, "Unsupported namespace URI '" + eValueOfChild.getNamespaceURI () + "'");
1151     });
1152     return ret;
1153   }
1154 
1155   /**
1156    * Read the schema from the resource supplied in the constructor. First all
1157    * includes are resolved and the {@link #readSchemaFromXML(IMicroElement)} is
1158    * called.
1159    *
1160    * @return The read {@link PSSchema}.
1161    * @throws SchematronReadException
1162    *         If reading fails
1163    */
1164   @Nonnull
1165   public PSSchema readSchema () throws SchematronReadException
1166   {
1167     // Resolve all includes as the first action
1168     final SAXReaderSettings aSettings = new SAXReaderSettings ().setEntityResolver (m_aEntityResolver);
1169 
1170     final IMicroDocument aDoc = SchematronHelper.getWithResolvedSchematronIncludes (m_aResource,
1171                                                                                     aSettings,
1172                                                                                     m_aErrorHandler);
1173     if (aDoc == null || aDoc.getDocumentElement () == null)
1174       throw new SchematronReadException (m_aResource, "Failed to resolve includes in resource " + m_aResource);
1175 
1176     if (SchematronDebug.isShowResolvedSourceSchematron ())
1177       s_aLogger.info ("Resolved source Schematron:\n" + MicroWriter.getNodeAsString (aDoc));
1178 
1179     return readSchemaFromXML (aDoc.getDocumentElement ());
1180   }
1181 
1182   @Override
1183   public String toString ()
1184   {
1185     return new ToStringGenerator (this).append ("resource", m_aResource)
1186                                        .append ("errorHandler", m_aErrorHandler)
1187                                        .getToString ();
1188   }
1189 }