Move loading DTD into a function as the call caused a hang when loaded into wsgi
[htsworkflow.git] / htsworkflow / util / ethelp.py
1 """ElementTree helper functions
2 """
3 import logging
4 import os
5 LOGGER = logging.getLogger(__name__)
6
7 import lxml.etree
8
9 def indent(elem, level=0):
10     """
11     reformat an element tree to be 'pretty' (indented)
12     """
13     i = "\n" + level*"  "
14     if len(elem):
15         if not elem.text or not elem.text.strip():
16             elem.text = i + "  "
17         for child in elem:
18             indent(child, level+1)
19         # we don't want the closing tag indented too far
20         child.tail = i
21         if not elem.tail or not elem.tail.strip():
22             elem.tail = i
23     else:
24         if level and (not elem.tail or not elem.tail.strip()):
25             elem.tail = i
26
27 def flatten(elem, include_tail=0):
28     """
29     Extract the text from an element tree
30     (AKA extract the text that not part of XML tags)
31     """
32     text = elem.text or ""
33     for e in elem:
34         text += flatten(e, 1)
35     if include_tail and elem.tail: text += elem.tail
36     return text
37
38 def validate_xhtml(html, base_url='http://localhost'):
39     """Helper for validating xhtml, mostly intended for test code
40
41     Defaults to assuming XHTML+RDFa
42     Returns None if there was a problem configuring validation
43     Logs messages from lxml.etree using python logging
44     Returns True if it passed validation
45     and False if it fails.
46     """
47     try:
48         XHTML_RDF_DTD = lxml.etree.DTD(external_id='-//W3C//DTD XHTML+RDFa 1.0//EN')
49     except lxml.etree.DTDParseError as e:
50         LOGGER.warn("Unable to load XHTML DTD %s" % (str(e),))
51         return
52
53     try:
54         root = lxml.etree.fromstring(html, base_url=base_url)
55     except lxml.etree.ParseError as e:
56         LOGGER.warn("Unable to parse document: %s" % (str(e),))
57         return False
58
59     if XHTML_RDF_DTD.validate(root):
60         # so unlikely.
61         return True
62
63     isgood = True
64     for msg in XHTML_RDF_DTD.error_log.filter_from_errors():
65         # I have no idea how to suppress this error
66         # but I need the xmlns attributes for of my RDFa 1.0 encoding
67         if 'ERROR:VALID:DTD_UNKNOWN_ATTRIBUTE' in str(msg):
68             continue
69         else:
70             LOGGER.error(msg)
71             isgood = False
72
73     return isgood