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