Package lxml :: Package isoschematron
[hide private]
[frames] | no frames]

Source Code for Package lxml.isoschematron

  1  """The ``lxml.isoschematron`` package implements ISO Schematron support on top 
  2  of the pure-xslt 'skeleton' implementation. 
  3  """ 
  4   
  5  import sys 
  6  import os.path 
  7  from lxml import etree as _etree # due to validator __init__ signature 
  8   
  9   
 10  # some compat stuff, borrowed from lxml.html 
 11  try: 
 12      unicode 
 13  except NameError: 
 14      # Python 3 
 15      unicode = str 
 16  try: 
 17      basestring 
 18  except NameError: 
 19      # Python 3 
 20      basestring = str 
 21   
 22   
 23  __all__ = ['extract_xsd', 'extract_rng', 'iso_dsdl_include', 
 24             'iso_abstract_expand', 'iso_svrl_for_xslt1', 
 25             'svrl_validation_errors', 'schematron_schema_valid', 
 26             'stylesheet_params', 'Schematron'] 
 27   
 28   
 29  # some namespaces 
 30  #FIXME: Maybe lxml should provide a dedicated place for common namespace 
 31  #FIXME: definitions? 
 32  XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema" 
 33  RELAXNG_NS = "http://relaxng.org/ns/structure/1.0" 
 34  SCHEMATRON_NS = "http://purl.oclc.org/dsdl/schematron" 
 35  SVRL_NS = "http://purl.oclc.org/dsdl/svrl" 
 36   
 37   
 38  # some helpers 
 39  _schematron_root = '{%s}schema' % SCHEMATRON_NS 
 40  _xml_schema_root = '{%s}schema' % XML_SCHEMA_NS 
 41  _resources_dir = os.path.join(os.path.dirname(__file__), 'resources') 
 42   
 43   
 44  # the iso-schematron skeleton implementation steps aka xsl transformations 
 45  extract_xsd = _etree.XSLT(_etree.parse( 
 46      os.path.join(_resources_dir, 'xsl', 'XSD2Schtrn.xsl'))) 
 47  extract_rng = _etree.XSLT(_etree.parse( 
 48      os.path.join(_resources_dir, 'xsl', 'RNG2Schtrn.xsl'))) 
 49  iso_dsdl_include = _etree.XSLT(_etree.parse( 
 50      os.path.join(_resources_dir, 'xsl', 'iso-schematron-xslt1', 
 51                   'iso_dsdl_include.xsl'))) 
 52  iso_abstract_expand = _etree.XSLT(_etree.parse( 
 53      os.path.join(_resources_dir, 'xsl', 'iso-schematron-xslt1', 
 54                   'iso_abstract_expand.xsl'))) 
 55  iso_svrl_for_xslt1 = _etree.XSLT(_etree.parse( 
 56      os.path.join(_resources_dir, 
 57                   'xsl', 'iso-schematron-xslt1', 'iso_svrl_for_xslt1.xsl'))) 
 58   
 59   
 60  # svrl result accessors 
 61  svrl_validation_errors = _etree.XPath( 
 62      '//svrl:failed-assert', namespaces={'svrl': SVRL_NS}) 
 63   
 64   
 65  # RelaxNG validator for schematron schemas 
 66  schematron_schema_valid = _etree.RelaxNG(_etree.parse( 
 67      os.path.join(_resources_dir, 'rng', 'iso-schematron.rng'))) 
68 69 70 -def stylesheet_params(**kwargs):
71 """Convert keyword args to a dictionary of stylesheet parameters. 72 XSL stylesheet parameters must be XPath expressions, i.e.: 73 74 * string expressions, like "'5'" 75 * simple (number) expressions, like "5" 76 * valid XPath expressions, like "/a/b/text()" 77 78 This function converts native Python keyword arguments to stylesheet 79 parameters following these rules: 80 If an arg is a string wrap it with XSLT.strparam(). 81 If an arg is an XPath object use its path string. 82 If arg is None raise TypeError. 83 Else convert arg to string. 84 """ 85 result = {} 86 for key, val in kwargs.items(): 87 if isinstance(val, basestring): 88 val = _etree.XSLT.strparam(val) 89 elif val is None: 90 raise TypeError('None not allowed as a stylesheet parameter') 91 elif not isinstance(val, _etree.XPath): 92 val = unicode(val) 93 result[key] = val 94 return result
95
96 97 # helper function for use in Schematron __init__ 98 -def _stylesheet_param_dict(paramsDict, kwargsDict):
99 """Return a copy of paramsDict, updated with kwargsDict entries, wrapped as 100 stylesheet arguments. 101 kwargsDict entries with a value of None are ignored. 102 """ 103 # beware of changing mutable default arg 104 paramsDict = dict(paramsDict) 105 for k, v in kwargsDict.items(): 106 if v is not None: # None values do not override 107 paramsDict[k] = v 108 paramsDict = stylesheet_params(**paramsDict) 109 return paramsDict
110
111 112 -class Schematron(_etree._Validator):
113 """An ISO Schematron validator. 114 115 Pass a root Element or an ElementTree to turn it into a validator. 116 Alternatively, pass a filename as keyword argument 'file' to parse from 117 the file system. 118 119 Schematron is a less well known, but very powerful schema language. 120 The main idea is to use the capabilities of XPath to put restrictions on 121 the structure and the content of XML documents. 122 123 The standard behaviour is to fail on ``failed-assert`` findings only 124 (``ASSERTS_ONLY``). To change this, you can either pass a report filter 125 function to the ``error_finder`` parameter (e.g. ``ASSERTS_AND_REPORTS`` 126 or a custom ``XPath`` object), or subclass isoschematron.Schematron for 127 complete control of the validation process. 128 129 Built on the Schematron language 'reference' skeleton pure-xslt 130 implementation, the validator is created as an XSLT 1.0 stylesheet using 131 these steps: 132 133 0) (Extract from XML Schema or RelaxNG schema) 134 1) Process inclusions 135 2) Process abstract patterns 136 3) Compile the schematron schema to XSLT 137 138 The ``include`` and ``expand`` keyword arguments can be used to switch off 139 steps 1) and 2). 140 To set parameters for steps 1), 2) and 3) hand parameter dictionaries to the 141 keyword arguments ``include_params``, ``expand_params`` or 142 ``compile_params``. 143 For convenience, the compile-step parameter ``phase`` is also exposed as a 144 keyword argument ``phase``. This takes precedence if the parameter is also 145 given in the parameter dictionary. 146 147 If ``store_schematron`` is set to True, the (included-and-expanded) 148 schematron document tree is stored and available through the ``schematron`` 149 property. 150 If ``store_xslt`` is set to True, the validation XSLT document tree will be 151 stored and can be retrieved through the ``validator_xslt`` property. 152 With ``store_report`` set to True (default: False), the resulting validation 153 report document gets stored and can be accessed as the ``validation_report`` 154 property. 155 156 Here is a usage example:: 157 158 >>> from lxml import etree 159 >>> from lxml.isoschematron import Schematron 160 161 >>> schematron = Schematron(etree.XML(''' 162 ... <schema xmlns="http://purl.oclc.org/dsdl/schematron" > 163 ... <pattern id="id_only_attribute"> 164 ... <title>id is the only permitted attribute name</title> 165 ... <rule context="*"> 166 ... <report test="@*[not(name()='id')]">Attribute 167 ... <name path="@*[not(name()='id')]"/> is forbidden<name/> 168 ... </report> 169 ... </rule> 170 ... </pattern> 171 ... </schema>'''), 172 ... error_finder=Schematron.ASSERTS_AND_REPORTS) 173 174 >>> xml = etree.XML(''' 175 ... <AAA name="aaa"> 176 ... <BBB id="bbb"/> 177 ... <CCC color="ccc"/> 178 ... </AAA> 179 ... ''') 180 181 >>> schematron.validate(xml) 182 False 183 184 >>> xml = etree.XML(''' 185 ... <AAA id="aaa"> 186 ... <BBB id="bbb"/> 187 ... <CCC/> 188 ... </AAA> 189 ... ''') 190 191 >>> schematron.validate(xml) 192 True 193 """ 194 195 # libxml2 error categorization for validation errors 196 _domain = _etree.ErrorDomains.SCHEMATRONV 197 _level = _etree.ErrorLevels.ERROR 198 _error_type = _etree.ErrorTypes.SCHEMATRONV_ASSERT 199 200 # convenience definitions for common behaviours 201 ASSERTS_ONLY = svrl_validation_errors # Default 202 ASSERTS_AND_REPORTS = _etree.XPath( 203 '//svrl:failed-assert | //svrl:successful-report', 204 namespaces={'svrl': SVRL_NS}) 205
206 - def _extract(self, element):
207 """Extract embedded schematron schema from non-schematron host schema. 208 This method will only be called by __init__ if the given schema document 209 is not a schematron schema by itself. 210 Must return a schematron schema document tree or None. 211 """ 212 schematron = None 213 if element.tag == _xml_schema_root: 214 schematron = self._extract_xsd(element) 215 elif element.nsmap[element.prefix] == RELAXNG_NS: 216 # RelaxNG does not have a single unique root element 217 schematron = self._extract_rng(element) 218 return schematron
219 220 # customization points 221 # etree.XSLT objects that provide the extract, include, expand, compile 222 # steps 223 _extract_xsd = extract_xsd 224 _extract_rng = extract_rng 225 _include = iso_dsdl_include 226 _expand = iso_abstract_expand 227 _compile = iso_svrl_for_xslt1 228 229 # etree.xpath object that determines input document validity when applied to 230 # the svrl result report; must return a list of result elements (empty if 231 # valid) 232 _validation_errors = ASSERTS_ONLY 233
234 - def __init__(self, etree=None, file=None, include=True, expand=True, 235 include_params={}, expand_params={}, compile_params={}, 236 store_schematron=False, store_xslt=False, store_report=False, 237 phase=None, error_finder=ASSERTS_ONLY):
238 super(Schematron, self).__init__() 239 240 self._store_report = store_report 241 self._schematron = None 242 self._validator_xslt = None 243 self._validation_report = None 244 if error_finder is not self.ASSERTS_ONLY: 245 self._validation_errors = error_finder 246 247 # parse schema document, may be a schematron schema or an XML Schema or 248 # a RelaxNG schema with embedded schematron rules 249 root = None 250 try: 251 if etree is not None: 252 if _etree.iselement(etree): 253 root = etree 254 else: 255 root = etree.getroot() 256 elif file is not None: 257 root = _etree.parse(file).getroot() 258 except Exception: 259 raise _etree.SchematronParseError( 260 "No tree or file given: %s" % sys.exc_info()[1]) 261 if root is None: 262 raise ValueError("Empty tree") 263 if root.tag == _schematron_root: 264 schematron = root 265 else: 266 schematron = self._extract(root) 267 if schematron is None: 268 raise _etree.SchematronParseError( 269 "Document is not a schematron schema or schematron-extractable") 270 # perform the iso-schematron skeleton implementation steps to get a 271 # validating xslt 272 if include: 273 schematron = self._include(schematron, **include_params) 274 if expand: 275 schematron = self._expand(schematron, **expand_params) 276 if not schematron_schema_valid(schematron): 277 raise _etree.SchematronParseError( 278 "invalid schematron schema: %s" % 279 schematron_schema_valid.error_log) 280 if store_schematron: 281 self._schematron = schematron 282 # add new compile keyword args here if exposing them 283 compile_kwargs = {'phase': phase} 284 compile_params = _stylesheet_param_dict(compile_params, compile_kwargs) 285 validator_xslt = self._compile(schematron, **compile_params) 286 if store_xslt: 287 self._validator_xslt = validator_xslt 288 self._validator = _etree.XSLT(validator_xslt)
289
290 - def __call__(self, etree):
291 """Validate doc using Schematron. 292 293 Returns true if document is valid, false if not. 294 """ 295 self._clear_error_log() 296 result = self._validator(etree) 297 if self._store_report: 298 self._validation_report = result 299 errors = self._validation_errors(result) 300 if errors: 301 if _etree.iselement(etree): 302 fname = etree.getroottree().docinfo.URL or '<file>' 303 else: 304 fname = etree.docinfo.URL or '<file>' 305 for error in errors: 306 # Does svrl report the line number, anywhere? Don't think so. 307 self._append_log_message( 308 domain=self._domain, type=self._error_type, 309 level=self._level, line=0, 310 message=_etree.tostring(error, encoding='unicode'), 311 filename=fname) 312 return False 313 return True
314 315 @property
316 - def schematron(self):
317 """ISO-schematron schema document (None if object has been initialized 318 with store_schematron=False). 319 """ 320 return self._schematron
321 322 @property
323 - def validator_xslt(self):
324 """ISO-schematron skeleton implementation XSLT validator document (None 325 if object has been initialized with store_xslt=False). 326 """ 327 return self._validator_xslt
328 329 @property
330 - def validation_report(self):
331 """ISO-schematron validation result report (None if result-storing has 332 been turned off). 333 """ 334 return self._validation_report
335