home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 June / maximum-cd-2009-06.iso / DiscContents / digsby_setup.exe / lib / lxml / html / __init__.pyo (.txt) next >
Encoding:
Python Compiled Bytecode  |  2009-02-26  |  36.7 KB  |  1,254 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. import threading
  5. import re
  6.  
  7. try:
  8.     from urlparse import urljoin
  9. except ImportError:
  10.     from urllib.parse import urljoin
  11.  
  12. import copy
  13. from lxml import etree
  14. from lxml.html import defs
  15. from lxml import cssselect
  16. from lxml.html._setmixin import SetMixin
  17.  
  18. try:
  19.     from UserDict import DictMixin
  20. except ImportError:
  21.     from lxml.html._dictmixin import DictMixin
  22.  
  23.  
  24. try:
  25.     set
  26. except NameError:
  27.     from sets import Set as set
  28.  
  29.  
  30. try:
  31.     bytes = __builtins__['bytes']
  32. except (KeyError, NameError):
  33.     bytes = str
  34.  
  35.  
  36. try:
  37.     unicode = __builtins__['unicode']
  38. except (KeyError, NameError):
  39.     unicode = str
  40.  
  41.  
  42. try:
  43.     basestring = __builtins__['basestring']
  44. except (KeyError, NameError):
  45.     basestring = (str, bytes)
  46.  
  47.  
  48. def __fix_docstring(s):
  49.     import sys
  50.     if s is None:
  51.         return None
  52.     
  53.     if sys.version_info[0] >= 3:
  54.         sub = re.compile("^(\\s*)u'", re.M).sub
  55.     else:
  56.         sub = re.compile("^(\\s*)b'", re.M).sub
  57.     return sub("\\1'", s)
  58.  
  59. __all__ = [
  60.     'document_fromstring',
  61.     'fragment_fromstring',
  62.     'fragments_fromstring',
  63.     'fromstring',
  64.     'tostring',
  65.     'Element',
  66.     'defs',
  67.     'open_in_browser',
  68.     'submit_form',
  69.     'find_rel_links',
  70.     'find_class',
  71.     'make_links_absolute',
  72.     'resolve_base_href',
  73.     'iterlinks',
  74.     'rewrite_links',
  75.     'open_in_browser']
  76. XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'
  77. _rel_links_xpath = etree.XPath('descendant-or-self::a[@rel]|descendant-or-self::x:a[@rel]', namespaces = {
  78.     'x': XHTML_NAMESPACE })
  79. _options_xpath = etree.XPath('descendant-or-self::option|descendant-or-self::x:option', namespaces = {
  80.     'x': XHTML_NAMESPACE })
  81. _forms_xpath = etree.XPath('descendant-or-self::form|descendant-or-self::x:form', namespaces = {
  82.     'x': XHTML_NAMESPACE })
  83. _class_xpath = etree.XPath("descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), concat(' ', $class_name, ' '))]")
  84. _id_xpath = etree.XPath('descendant-or-self::*[@id=$id]')
  85. _collect_string_content = etree.XPath('string()')
  86. _css_url_re = re.compile('url\\((.*?)\\)', re.I)
  87. _css_import_re = re.compile('@import "(.*?)"')
  88. _label_xpath = etree.XPath('//label[@for=$id]|//x:label[@for=$id]', namespaces = {
  89.     'x': XHTML_NAMESPACE })
  90. _archive_re = re.compile('[^ ]+')
  91.  
  92. def _transform_result(typ, result):
  93.     if issubclass(typ, bytes):
  94.         return tostring(result, encoding = 'utf-8')
  95.     elif issubclass(typ, unicode):
  96.         return tostring(result, encoding = unicode)
  97.     else:
  98.         return result
  99.  
  100.  
  101. def _nons(tag):
  102.     if isinstance(tag, basestring):
  103.         if tag[0] == '{' and tag[1:len(XHTML_NAMESPACE) + 1] == XHTML_NAMESPACE:
  104.             return tag.split('}')[-1]
  105.         
  106.     
  107.     return tag
  108.  
  109.  
  110. class HtmlMixin(object):
  111.     
  112.     def base_url(self):
  113.         return self.getroottree().docinfo.URL
  114.  
  115.     base_url = property(base_url, doc = base_url.__doc__)
  116.     
  117.     def forms(self):
  118.         return _forms_xpath(self)
  119.  
  120.     forms = property(forms, doc = forms.__doc__)
  121.     
  122.     def body(self):
  123.         return self.xpath('//body|//x:body', namespaces = {
  124.             'x': XHTML_NAMESPACE })[0]
  125.  
  126.     body = property(body, doc = body.__doc__)
  127.     
  128.     def head(self):
  129.         return self.xpath('//head|//x:head', namespaces = {
  130.             'x': XHTML_NAMESPACE })[0]
  131.  
  132.     head = property(head, doc = head.__doc__)
  133.     
  134.     def _label__get(self):
  135.         id = self.get('id')
  136.         if not id:
  137.             return None
  138.         
  139.         result = _label_xpath(self, id = id)
  140.         if not result:
  141.             return None
  142.         else:
  143.             return result[0]
  144.  
  145.     
  146.     def _label__set(self, label):
  147.         id = self.get('id')
  148.         if not id:
  149.             raise TypeError('You cannot set a label for an element (%r) that has no id' % self)
  150.         
  151.         if _nons(label.tag) != 'label':
  152.             raise TypeError('You can only assign label to a label element (not %r)' % label)
  153.         
  154.         label.set('for', id)
  155.  
  156.     
  157.     def _label__del(self):
  158.         label = self.label
  159.         if label is not None:
  160.             del label.attrib['for']
  161.         
  162.  
  163.     label = property(_label__get, _label__set, _label__del, doc = _label__get.__doc__)
  164.     
  165.     def drop_tree(self):
  166.         parent = self.getparent()
  167.         if self.tail:
  168.             previous = self.getprevious()
  169.             if previous is None:
  170.                 if not parent.text:
  171.                     pass
  172.                 parent.text = '' + self.tail
  173.             elif not previous.tail:
  174.                 pass
  175.             previous.tail = '' + self.tail
  176.         
  177.         parent.remove(self)
  178.  
  179.     
  180.     def drop_tag(self):
  181.         parent = self.getparent()
  182.         previous = self.getprevious()
  183.         if self.text and isinstance(self.tag, basestring):
  184.             if previous is None:
  185.                 if not parent.text:
  186.                     pass
  187.                 parent.text = '' + self.text
  188.             elif not previous.tail:
  189.                 pass
  190.             previous.tail = '' + self.text
  191.         
  192.         if self.tail:
  193.             if len(self):
  194.                 last = self[-1]
  195.                 if not last.tail:
  196.                     pass
  197.                 last.tail = '' + self.tail
  198.             elif previous is None:
  199.                 if not parent.text:
  200.                     pass
  201.                 parent.text = '' + self.tail
  202.             elif not previous.tail:
  203.                 pass
  204.             previous.tail = '' + self.tail
  205.         
  206.         index = parent.index(self)
  207.         parent[index:index + 1] = self[:]
  208.  
  209.     
  210.     def find_rel_links(self, rel):
  211.         rel = rel.lower()
  212.         return _[1]
  213.  
  214.     
  215.     def find_class(self, class_name):
  216.         return _class_xpath(self, class_name = class_name)
  217.  
  218.     
  219.     def get_element_by_id(self, id, *default):
  220.         
  221.         try:
  222.             return _id_xpath(self, id = id)[0]
  223.         except IndexError:
  224.             if default:
  225.                 return default[0]
  226.             else:
  227.                 raise KeyError(id)
  228.         except:
  229.             default
  230.  
  231.  
  232.     
  233.     def text_content(self):
  234.         return _collect_string_content(self)
  235.  
  236.     
  237.     def cssselect(self, expr):
  238.         return cssselect.CSSSelector(expr)(self)
  239.  
  240.     
  241.     def make_links_absolute(self, base_url = None, resolve_base_href = True):
  242.         if base_url is None:
  243.             base_url = self.base_url
  244.             if base_url is None:
  245.                 raise TypeError('No base_url given, and the document has no base_url')
  246.             
  247.         
  248.         if resolve_base_href:
  249.             self.resolve_base_href()
  250.         
  251.         
  252.         def link_repl(href):
  253.             return urljoin(base_url, href)
  254.  
  255.         self.rewrite_links(link_repl)
  256.  
  257.     
  258.     def resolve_base_href(self):
  259.         base_href = None
  260.         basetags = self.xpath('//base[@href]|//x:base[@href]', namespaces = {
  261.             'x': XHTML_NAMESPACE })
  262.         for b in basetags:
  263.             base_href = b.get('href')
  264.             b.drop_tree()
  265.         
  266.         if not base_href:
  267.             return None
  268.         
  269.         self.make_links_absolute(base_href, resolve_base_href = False)
  270.  
  271.     
  272.     def iterlinks(self):
  273.         link_attrs = defs.link_attrs
  274.         for el in self.iter():
  275.             attribs = el.attrib
  276.             tag = _nons(el.tag)
  277.             if tag != 'object':
  278.                 for attrib in link_attrs:
  279.                     if attrib in attribs:
  280.                         yield (el, attrib, attribs[attrib], 0)
  281.                         continue
  282.                 
  283.             elif tag == 'object':
  284.                 codebase = None
  285.                 if 'codebase' in attribs:
  286.                     codebase = el.get('codebase')
  287.                     yield (el, 'codebase', codebase, 0)
  288.                 
  289.                 for attrib in ('classid', 'data'):
  290.                     if attrib in attribs:
  291.                         value = el.get(attrib)
  292.                         if codebase is not None:
  293.                             value = urljoin(codebase, value)
  294.                         
  295.                         yield (el, attrib, value, 0)
  296.                         continue
  297.                 
  298.                 if 'archive' in attribs:
  299.                     for match in _archive_re.finditer(el.get('archive')):
  300.                         value = match.group(0)
  301.                         if codebase is not None:
  302.                             value = urljoin(codebase, value)
  303.                         
  304.                         yield (el, 'archive', value, match.start())
  305.                     
  306.                 
  307.             
  308.             if tag == 'param':
  309.                 if not el.get('valuetype'):
  310.                     pass
  311.                 valuetype = ''
  312.                 if valuetype.lower() == 'ref':
  313.                     yield (el, 'value', el.get('value'), 0)
  314.                 
  315.             
  316.             if tag == 'style' and el.text:
  317.                 for match in _css_url_re.finditer(el.text):
  318.                     yield (el, None, match.group(1), match.start(1))
  319.                 
  320.                 for match in _css_import_re.finditer(el.text):
  321.                     yield (el, None, match.group(1), match.start(1))
  322.                 
  323.             
  324.             if 'style' in attribs:
  325.                 for match in _css_url_re.finditer(attribs['style']):
  326.                     yield (el, 'style', match.group(1), match.start(1))
  327.                 
  328.         
  329.  
  330.     
  331.     def rewrite_links(self, link_repl_func, resolve_base_href = True, base_href = None):
  332.         if base_href is not None:
  333.             self.make_links_absolute(base_href, resolve_base_href = resolve_base_href)
  334.         elif resolve_base_href:
  335.             self.resolve_base_href()
  336.         
  337.         for el, attrib, link, pos in self.iterlinks():
  338.             new_link = link_repl_func(link.strip())
  339.             if new_link == link:
  340.                 continue
  341.             
  342.             if new_link is None:
  343.                 if attrib is None:
  344.                     el.text = ''
  345.                     continue
  346.                 del el.attrib[attrib]
  347.                 continue
  348.             
  349.             if attrib is None:
  350.                 new = el.text[:pos] + new_link + el.text[pos + len(link):]
  351.                 el.text = new
  352.                 continue
  353.             cur = el.attrib[attrib]
  354.             if not pos and len(cur) == len(link):
  355.                 el.attrib[attrib] = new_link
  356.                 continue
  357.             new = cur[:pos] + new_link + cur[pos + len(link):]
  358.             el.attrib[attrib] = new
  359.         
  360.  
  361.  
  362.  
  363. class _MethodFunc(object):
  364.     
  365.     def __init__(self, name, copy = False, source_class = HtmlMixin):
  366.         self.name = name
  367.         self.copy = copy
  368.         self.__doc__ = getattr(source_class, self.name).__doc__
  369.  
  370.     
  371.     def __call__(self, doc, *args, **kw):
  372.         result_type = type(doc)
  373.         if isinstance(doc, basestring):
  374.             if 'copy' in kw:
  375.                 raise TypeError("The keyword 'copy' can only be used with element inputs to %s, not a string input" % self.name)
  376.             
  377.             doc = fromstring(doc, **kw)
  378.         elif 'copy' in kw:
  379.             copy = kw.pop('copy')
  380.         else:
  381.             copy = self.copy
  382.         if copy:
  383.             doc = copy.deepcopy(doc)
  384.         
  385.         meth = getattr(doc, self.name)
  386.         result = meth(*args, **kw)
  387.         if result is None:
  388.             return _transform_result(result_type, doc)
  389.         else:
  390.             return result
  391.  
  392.  
  393. find_rel_links = _MethodFunc('find_rel_links', copy = False)
  394. find_class = _MethodFunc('find_class', copy = False)
  395. make_links_absolute = _MethodFunc('make_links_absolute', copy = True)
  396. resolve_base_href = _MethodFunc('resolve_base_href', copy = True)
  397. iterlinks = _MethodFunc('iterlinks', copy = False)
  398. rewrite_links = _MethodFunc('rewrite_links', copy = True)
  399.  
  400. class HtmlComment(etree.CommentBase, HtmlMixin):
  401.     pass
  402.  
  403.  
  404. class HtmlElement(etree.ElementBase, HtmlMixin):
  405.     pass
  406.  
  407.  
  408. class HtmlProcessingInstruction(etree.PIBase, HtmlMixin):
  409.     pass
  410.  
  411.  
  412. class HtmlEntity(etree.EntityBase, HtmlMixin):
  413.     pass
  414.  
  415.  
  416. class HtmlElementClassLookup(etree.CustomElementClassLookup):
  417.     _default_element_classes = { }
  418.     
  419.     def __init__(self, classes = None, mixins = None):
  420.         etree.CustomElementClassLookup.__init__(self)
  421.         if classes is None:
  422.             classes = self._default_element_classes.copy()
  423.         
  424.         if mixins:
  425.             mixers = { }
  426.             for name, value in mixins:
  427.                 if name == '*':
  428.                     for n in classes.keys():
  429.                         mixers.setdefault(n, []).append(value)
  430.                     
  431.                 mixers.setdefault(name, []).append(value)
  432.             
  433.             for name, mix_bases in mixers.items():
  434.                 cur = classes.get(name, HtmlElement)
  435.                 bases = tuple(mix_bases + [
  436.                     cur])
  437.                 classes[name] = type(cur.__name__, bases, { })
  438.             
  439.         
  440.         self._element_classes = classes
  441.  
  442.     
  443.     def lookup(self, node_type, document, namespace, name):
  444.         if node_type == 'element':
  445.             return self._element_classes.get(name.lower(), HtmlElement)
  446.         elif node_type == 'comment':
  447.             return HtmlComment
  448.         elif node_type == 'PI':
  449.             return HtmlProcessingInstruction
  450.         elif node_type == 'entity':
  451.             return HtmlEntity
  452.         
  453.  
  454.  
  455.  
  456. def document_fromstring(html, parser = None, **kw):
  457.     if parser is None:
  458.         parser = html_parser
  459.     
  460.     value = etree.fromstring(html, parser, **kw)
  461.     if value is None:
  462.         raise etree.ParserError('Document is empty')
  463.     
  464.     return value
  465.  
  466.  
  467. def fragments_fromstring(html, no_leading_text = False, base_url = None, parser = None, **kw):
  468.     if parser is None:
  469.         parser = html_parser
  470.     
  471.     start = html[:20].lstrip().lower()
  472.     if not start.startswith('<html') and not start.startswith('<!doctype'):
  473.         html = '<html><body>%s</body></html>' % html
  474.     
  475.     doc = document_fromstring(html, parser = parser, base_url = base_url, **kw)
  476.     bodies = _[1]
  477.     body = bodies[0]
  478.     elements = []
  479.     if body.text and body.text.strip():
  480.         elements.append(body.text)
  481.     
  482.     elements.extend(body)
  483.     return elements
  484.  
  485.  
  486. def fragment_fromstring(html, create_parent = False, base_url = None, parser = None, **kw):
  487.     if parser is None:
  488.         parser = html_parser
  489.     
  490.     if create_parent:
  491.         if not isinstance(create_parent, basestring):
  492.             create_parent = 'div'
  493.         
  494.         return fragment_fromstring('<%s>%s</%s>' % (create_parent, html, create_parent), parser = parser, base_url = base_url, **kw)
  495.     
  496.     elements = fragments_fromstring(html, parser = parser, no_leading_text = True, base_url = base_url, **kw)
  497.     if not elements:
  498.         raise etree.ParserError('No elements found')
  499.     
  500.     el = elements[0]
  501.     if el.tail and el.tail.strip():
  502.         raise etree.ParserError('Element followed by text: %r' % el.tail)
  503.     
  504.     el.tail = None
  505.     return el
  506.  
  507.  
  508. def fromstring(html, base_url = None, parser = None, **kw):
  509.     if parser is None:
  510.         parser = html_parser
  511.     
  512.     start = html[:10].lstrip().lower()
  513.     if start.startswith('<html') or start.startswith('<!doctype'):
  514.         return document_fromstring(html, parser = parser, base_url = base_url, **kw)
  515.     
  516.     doc = document_fromstring(html, parser = parser, base_url = base_url, **kw)
  517.     bodies = doc.findall('body')
  518.     if not bodies:
  519.         bodies = doc.findall('{%s}body' % XHTML_NAMESPACE)
  520.     
  521.     if bodies:
  522.         body = bodies[0]
  523.         if len(bodies) > 1:
  524.             for other_body in bodies[1:]:
  525.                 if other_body.text:
  526.                     if len(body):
  527.                         if not body[-1].tail:
  528.                             pass
  529.                         body[-1].tail = '' + other_body.text
  530.                     elif not body.text:
  531.                         pass
  532.                     body.text = '' + other_body.text
  533.                 
  534.                 body.extend(other_body)
  535.                 other_body.drop_tree()
  536.             
  537.         
  538.     else:
  539.         body = None
  540.     heads = doc.findall('head')
  541.     if not heads:
  542.         heads = doc.findall('{%s}head' % XHTML_NAMESPACE)
  543.     
  544.     if heads:
  545.         head = heads[0]
  546.         if len(heads) > 1:
  547.             for other_head in heads[1:]:
  548.                 head.extend(other_head)
  549.                 other_head.drop_tree()
  550.             
  551.         
  552.         return doc
  553.     
  554.     if len(body) == 1:
  555.         if not (body.text) or not body.text.strip():
  556.             if not (body[-1].tail) or not body[-1].tail.strip():
  557.                 return body[0]
  558.             
  559.     if _contains_block_level_tag(body):
  560.         body.tag = 'div'
  561.     else:
  562.         body.tag = 'span'
  563.     return body
  564.  
  565.  
  566. def parse(filename_or_url, parser = None, base_url = None, **kw):
  567.     if parser is None:
  568.         parser = html_parser
  569.     
  570.     return etree.parse(filename_or_url, parser, base_url = base_url, **kw)
  571.  
  572.  
  573. def _contains_block_level_tag(el):
  574.     for el in el.iter():
  575.         if _nons(el.tag) in defs.block_tags:
  576.             return True
  577.             continue
  578.     
  579.     return False
  580.  
  581.  
  582. def _element_name(el):
  583.     if isinstance(el, etree.CommentBase):
  584.         return 'comment'
  585.     elif isinstance(el, basestring):
  586.         return 'string'
  587.     else:
  588.         return _nons(el.tag)
  589.  
  590.  
  591. class FormElement(HtmlElement):
  592.     
  593.     def inputs(self):
  594.         return InputGetter(self)
  595.  
  596.     inputs = property(inputs, doc = inputs.__doc__)
  597.     
  598.     def _fields__get(self):
  599.         return FieldsDict(self.inputs)
  600.  
  601.     
  602.     def _fields__set(self, value):
  603.         prev_keys = self.fields.keys()
  604.         for key, value in value.iteritems():
  605.             if key in prev_keys:
  606.                 prev_keys.remove(key)
  607.             
  608.             self.fields[key] = value
  609.         
  610.         for key in prev_keys:
  611.             if key is None:
  612.                 continue
  613.             
  614.             self.fields[key] = None
  615.         
  616.  
  617.     fields = property(_fields__get, _fields__set, doc = _fields__get.__doc__)
  618.     
  619.     def _name(self):
  620.         if self.get('name'):
  621.             return self.get('name')
  622.         elif self.get('id'):
  623.             return '#' + self.get('id')
  624.         
  625.         forms = self.body.findall('form')
  626.         if not forms:
  627.             forms = self.body.findall('{%s}form' % XHTML_NAMESPACE)
  628.         
  629.         return str(forms.index(self))
  630.  
  631.     
  632.     def form_values(self):
  633.         results = []
  634.         for el in self.inputs:
  635.             name = el.name
  636.             if not name:
  637.                 continue
  638.             
  639.             tag = _nons(el.tag)
  640.             if tag == 'textarea':
  641.                 results.append((name, el.value))
  642.                 continue
  643.             if tag == 'select':
  644.                 value = el.value
  645.                 if el.multiple:
  646.                     for v in value:
  647.                         results.append((name, v))
  648.                     
  649.                 elif value is not None:
  650.                     results.append((name, el.value))
  651.                 
  652.             el.multiple
  653.             if el.checkable and not (el.checked):
  654.                 continue
  655.             
  656.             if el.type in ('submit', 'image', 'reset'):
  657.                 continue
  658.             
  659.             value = el.value
  660.             if value is not None:
  661.                 results.append((name, el.value))
  662.                 continue
  663.         
  664.         return results
  665.  
  666.     
  667.     def _action__get(self):
  668.         base_url = self.base_url
  669.         action = self.get('action')
  670.         if base_url and action is not None:
  671.             return urljoin(base_url, action)
  672.         else:
  673.             return action
  674.  
  675.     
  676.     def _action__set(self, value):
  677.         self.set('action', value)
  678.  
  679.     
  680.     def _action__del(self):
  681.         if 'action' in self.attrib:
  682.             del self.attrib['action']
  683.         
  684.  
  685.     action = property(_action__get, _action__set, _action__del, doc = _action__get.__doc__)
  686.     
  687.     def _method__get(self):
  688.         return self.get('method', 'GET').upper()
  689.  
  690.     
  691.     def _method__set(self, value):
  692.         self.set('method', value.upper())
  693.  
  694.     method = property(_method__get, _method__set, doc = _method__get.__doc__)
  695.  
  696. HtmlElementClassLookup._default_element_classes['form'] = FormElement
  697.  
  698. def submit_form(form, extra_values = None, open_http = None):
  699.     values = form.form_values()
  700.     if extra_values:
  701.         if hasattr(extra_values, 'items'):
  702.             extra_values = extra_values.items()
  703.         
  704.         values.extend(extra_values)
  705.     
  706.     if open_http is None:
  707.         open_http = open_http_urllib
  708.     
  709.     return open_http(form.method, form.action, values)
  710.  
  711.  
  712. def open_http_urllib(method, url, values):
  713.     import urllib
  714.     if method == 'GET':
  715.         if '?' in url:
  716.             url += '&'
  717.         else:
  718.             url += '?'
  719.         url += urllib.urlencode(values)
  720.         data = None
  721.     else:
  722.         data = urllib.urlencode(values)
  723.     return urllib.urlopen(url, data)
  724.  
  725.  
  726. class FieldsDict(DictMixin):
  727.     
  728.     def __init__(self, inputs):
  729.         self.inputs = inputs
  730.  
  731.     
  732.     def __getitem__(self, item):
  733.         return self.inputs[item].value
  734.  
  735.     
  736.     def __setitem__(self, item, value):
  737.         self.inputs[item].value = value
  738.  
  739.     
  740.     def __delitem__(self, item):
  741.         raise KeyError('You cannot remove keys from ElementDict')
  742.  
  743.     
  744.     def keys(self):
  745.         return self.inputs.keys()
  746.  
  747.     
  748.     def __contains__(self, item):
  749.         return item in self.inputs
  750.  
  751.     
  752.     def __repr__(self):
  753.         return '<%s for form %s>' % (self.__class__.__name__, self.inputs.form._name())
  754.  
  755.  
  756.  
  757. class InputGetter(object):
  758.     _name_xpath = etree.XPath(".//*[@name = $name and (local-name(.) = 'select' or local-name(.) = 'input' or local-name(.) = 'textarea')]")
  759.     _all_xpath = etree.XPath(".//*[local-name() = 'select' or local-name() = 'input' or local-name() = 'textarea']")
  760.     
  761.     def __init__(self, form):
  762.         self.form = form
  763.  
  764.     
  765.     def __repr__(self):
  766.         return '<%s for form %s>' % (self.__class__.__name__, self.form._name())
  767.  
  768.     
  769.     def __getitem__(self, name):
  770.         results = self._name_xpath(self.form, name = name)
  771.         if results:
  772.             type = results[0].get('type')
  773.             if type == 'radio' and len(results) > 1:
  774.                 group = RadioGroup(results)
  775.                 group.name = name
  776.                 return group
  777.             elif type == 'checkbox' and len(results) > 1:
  778.                 group = CheckboxGroup(results)
  779.                 group.name = name
  780.                 return group
  781.             else:
  782.                 return results[0]
  783.         else:
  784.             raise KeyError('No input element with the name %r' % name)
  785.  
  786.     
  787.     def __contains__(self, name):
  788.         results = self._name_xpath(self.form, name = name)
  789.         return bool(results)
  790.  
  791.     
  792.     def keys(self):
  793.         names = set()
  794.         for el in self:
  795.             names.add(el.name)
  796.         
  797.         if None in names:
  798.             names.remove(None)
  799.         
  800.         return list(names)
  801.  
  802.     
  803.     def __iter__(self):
  804.         return iter(self._all_xpath(self.form))
  805.  
  806.  
  807.  
  808. class InputMixin(object):
  809.     
  810.     def _name__get(self):
  811.         return self.get('name')
  812.  
  813.     
  814.     def _name__set(self, value):
  815.         self.set('name', value)
  816.  
  817.     
  818.     def _name__del(self):
  819.         if 'name' in self.attrib:
  820.             del self.attrib['name']
  821.         
  822.  
  823.     name = property(_name__get, _name__set, _name__del, doc = _name__get.__doc__)
  824.     
  825.     def __repr__(self):
  826.         type = getattr(self, 'type', None)
  827.         if type:
  828.             type = ' type=%r' % type
  829.         else:
  830.             type = ''
  831.         return '<%s %x name=%r%s>' % (self.__class__.__name__, id(self), self.name, type)
  832.  
  833.  
  834.  
  835. class TextareaElement(InputMixin, HtmlElement):
  836.     
  837.     def _value__get(self):
  838.         if not self.text:
  839.             pass
  840.         return ''
  841.  
  842.     
  843.     def _value__set(self, value):
  844.         self.text = value
  845.  
  846.     
  847.     def _value__del(self):
  848.         self.text = ''
  849.  
  850.     value = property(_value__get, _value__set, _value__del, doc = _value__get.__doc__)
  851.  
  852. HtmlElementClassLookup._default_element_classes['textarea'] = TextareaElement
  853.  
  854. class SelectElement(InputMixin, HtmlElement):
  855.     
  856.     def _value__get(self):
  857.         if self.multiple:
  858.             return MultipleSelectOptions(self)
  859.         
  860.         for el in _options_xpath(self):
  861.             if 'selected' in el.attrib:
  862.                 value = el.get('value')
  863.                 return value
  864.                 continue
  865.         
  866.  
  867.     
  868.     def _value__set(self, value):
  869.         if self.multiple:
  870.             if isinstance(value, basestring):
  871.                 raise TypeError('You must pass in a sequence')
  872.             
  873.             self.value.clear()
  874.             self.value.update(value)
  875.             return None
  876.         
  877.         if value is not None:
  878.             for el in _options_xpath(self):
  879.                 if el.get('value') == value:
  880.                     checked_option = el
  881.                     break
  882.                     continue
  883.             else:
  884.                 raise ValueError('There is no option with the value of %r' % value)
  885.         
  886.         for el in _options_xpath(self):
  887.             if 'selected' in el.attrib:
  888.                 del el.attrib['selected']
  889.                 continue
  890.         
  891.         if value is not None:
  892.             checked_option.set('selected', '')
  893.         
  894.  
  895.     
  896.     def _value__del(self):
  897.         if self.multiple:
  898.             self.value.clear()
  899.         else:
  900.             self.value = None
  901.  
  902.     value = property(_value__get, _value__set, _value__del, doc = _value__get.__doc__)
  903.     
  904.     def value_options(self):
  905.         return [ el.get('value') for el in _options_xpath(self) ]
  906.  
  907.     value_options = property(value_options, doc = value_options.__doc__)
  908.     
  909.     def _multiple__get(self):
  910.         return 'multiple' in self.attrib
  911.  
  912.     
  913.     def _multiple__set(self, value):
  914.         if value:
  915.             self.set('multiple', '')
  916.         elif 'multiple' in self.attrib:
  917.             del self.attrib['multiple']
  918.         
  919.  
  920.     multiple = property(_multiple__get, _multiple__set, doc = _multiple__get.__doc__)
  921.  
  922. HtmlElementClassLookup._default_element_classes['select'] = SelectElement
  923.  
  924. class MultipleSelectOptions(SetMixin):
  925.     
  926.     def __init__(self, select):
  927.         self.select = select
  928.  
  929.     
  930.     def options(self):
  931.         return iter(_options_xpath(self.select))
  932.  
  933.     options = property(options)
  934.     
  935.     def __iter__(self):
  936.         for option in self.options:
  937.             yield option.get('value')
  938.         
  939.  
  940.     
  941.     def add(self, item):
  942.         for option in self.options:
  943.             if option.get('value') == item:
  944.                 option.set('selected', '')
  945.                 break
  946.                 continue
  947.         else:
  948.             raise ValueError('There is no option with the value %r' % item)
  949.  
  950.     
  951.     def remove(self, item):
  952.         for option in self.options:
  953.             if option.get('value') == item:
  954.                 if 'selected' in option.attrib:
  955.                     del option.attrib['selected']
  956.                 else:
  957.                     raise ValueError('The option %r is not currently selected' % item)
  958.                 break
  959.                 continue
  960.         else:
  961.             raise ValueError('There is not option with the value %r' % item)
  962.  
  963.     
  964.     def __repr__(self):
  965.         return ', '.join % ([], []([ repr(v) for v in self ]), self.select.name)
  966.  
  967.  
  968.  
  969. class RadioGroup(list):
  970.     
  971.     def _value__get(self):
  972.         for el in self:
  973.             if 'checked' in el.attrib:
  974.                 return el.get('value')
  975.                 continue
  976.         
  977.  
  978.     
  979.     def _value__set(self, value):
  980.         if value is not None:
  981.             for el in self:
  982.                 if el.get('value') == value:
  983.                     checked_option = el
  984.                     break
  985.                     continue
  986.             else:
  987.                 raise ValueError('There is no radio input with the value %r' % value)
  988.         
  989.         for el in self:
  990.             if 'checked' in el.attrib:
  991.                 del el.attrib['checked']
  992.                 continue
  993.         
  994.         if value is not None:
  995.             checked_option.set('checked', '')
  996.         
  997.  
  998.     
  999.     def _value__del(self):
  1000.         self.value = None
  1001.  
  1002.     value = property(_value__get, _value__set, _value__del, doc = _value__get.__doc__)
  1003.     
  1004.     def value_options(self):
  1005.         return [ el.get('value') for el in self ]
  1006.  
  1007.     value_options = property(value_options, doc = value_options.__doc__)
  1008.     
  1009.     def __repr__(self):
  1010.         return '%s(%s)' % (self.__class__.__name__, list.__repr__(self))
  1011.  
  1012.  
  1013.  
  1014. class CheckboxGroup(list):
  1015.     
  1016.     def _value__get(self):
  1017.         return CheckboxValues(self)
  1018.  
  1019.     
  1020.     def _value__set(self, value):
  1021.         self.value.clear()
  1022.         if not hasattr(value, '__iter__'):
  1023.             raise ValueError('A CheckboxGroup (name=%r) must be set to a sequence (not %r)' % (self[0].name, value))
  1024.         
  1025.         self.value.update(value)
  1026.  
  1027.     
  1028.     def _value__del(self):
  1029.         self.value.clear()
  1030.  
  1031.     value = property(_value__get, _value__set, _value__del, doc = _value__get.__doc__)
  1032.     
  1033.     def __repr__(self):
  1034.         return '%s(%s)' % (self.__class__.__name__, list.__repr__(self))
  1035.  
  1036.  
  1037.  
  1038. class CheckboxValues(SetMixin):
  1039.     
  1040.     def __init__(self, group):
  1041.         self.group = group
  1042.  
  1043.     
  1044.     def __iter__(self):
  1045.         return [](_[1])
  1046.  
  1047.     
  1048.     def add(self, value):
  1049.         for el in self.group:
  1050.             if el.get('value') == value:
  1051.                 el.set('checked', '')
  1052.                 break
  1053.                 continue
  1054.         else:
  1055.             raise KeyError('No checkbox with value %r' % value)
  1056.  
  1057.     
  1058.     def remove(self, value):
  1059.         for el in self.group:
  1060.             if el.get('value') == value:
  1061.                 if 'checked' in el.attrib:
  1062.                     del el.attrib['checked']
  1063.                 else:
  1064.                     raise KeyError('The checkbox with value %r was already unchecked' % value)
  1065.                 break
  1066.                 continue
  1067.         else:
  1068.             raise KeyError('No checkbox with value %r' % value)
  1069.  
  1070.     
  1071.     def __repr__(self):
  1072.         return ', '.join % ([], []([ repr(v) for v in self ]), self.group.name)
  1073.  
  1074.  
  1075.  
  1076. class InputElement(InputMixin, HtmlElement):
  1077.     
  1078.     def _value__get(self):
  1079.         if self.checkable:
  1080.             if self.checked:
  1081.                 if not self.get('value'):
  1082.                     pass
  1083.                 return 'on'
  1084.             else:
  1085.                 return None
  1086.         
  1087.         return self.get('value')
  1088.  
  1089.     
  1090.     def _value__set(self, value):
  1091.         if self.checkable:
  1092.             if not value:
  1093.                 self.checked = False
  1094.             else:
  1095.                 self.checked = True
  1096.                 if isinstance(value, basestring):
  1097.                     self.set('value', value)
  1098.                 
  1099.         else:
  1100.             self.set('value', value)
  1101.  
  1102.     
  1103.     def _value__del(self):
  1104.         if self.checkable:
  1105.             self.checked = False
  1106.         elif 'value' in self.attrib:
  1107.             del self.attrib['value']
  1108.         
  1109.  
  1110.     value = property(_value__get, _value__set, _value__del, doc = _value__get.__doc__)
  1111.     
  1112.     def _type__get(self):
  1113.         return self.get('type', 'text').lower()
  1114.  
  1115.     
  1116.     def _type__set(self, value):
  1117.         self.set('type', value)
  1118.  
  1119.     type = property(_type__get, _type__set, doc = _type__get.__doc__)
  1120.     
  1121.     def checkable(self):
  1122.         return self.type in ('checkbox', 'radio')
  1123.  
  1124.     checkable = property(checkable, doc = checkable.__doc__)
  1125.     
  1126.     def _checked__get(self):
  1127.         if not self.checkable:
  1128.             raise AttributeError('Not a checkable input type')
  1129.         
  1130.         return 'checked' in self.attrib
  1131.  
  1132.     
  1133.     def _checked__set(self, value):
  1134.         if not self.checkable:
  1135.             raise AttributeError('Not a checkable input type')
  1136.         
  1137.         if value:
  1138.             self.set('checked', '')
  1139.         elif 'checked' in self.attrib:
  1140.             del self.attrib['checked']
  1141.         
  1142.  
  1143.     checked = property(_checked__get, _checked__set, doc = _checked__get.__doc__)
  1144.  
  1145. HtmlElementClassLookup._default_element_classes['input'] = InputElement
  1146.  
  1147. class LabelElement(HtmlElement):
  1148.     
  1149.     def _for_element__get(self):
  1150.         id = self.get('for')
  1151.         if not id:
  1152.             return None
  1153.         
  1154.         return self.body.get_element_by_id(id)
  1155.  
  1156.     
  1157.     def _for_element__set(self, other):
  1158.         id = other.get('id')
  1159.         if not id:
  1160.             raise TypeError('Element %r has no id attribute' % other)
  1161.         
  1162.         self.set('for', id)
  1163.  
  1164.     
  1165.     def _for_element__del(self):
  1166.         if 'id' in self.attrib:
  1167.             del self.attrib['id']
  1168.         
  1169.  
  1170.     for_element = property(_for_element__get, _for_element__set, _for_element__del, doc = _for_element__get.__doc__)
  1171.  
  1172. HtmlElementClassLookup._default_element_classes['label'] = LabelElement
  1173.  
  1174. def html_to_xhtml(html):
  1175.     
  1176.     try:
  1177.         html = html.getroot()
  1178.     except AttributeError:
  1179.         pass
  1180.  
  1181.     prefix = '{%s}' % XHTML_NAMESPACE
  1182.     for el in html.iter():
  1183.         tag = el.tag
  1184.         if isinstance(tag, basestring):
  1185.             if tag[0] != '{':
  1186.                 el.tag = prefix + tag
  1187.             
  1188.         tag[0] != '{'
  1189.     
  1190.  
  1191.  
  1192. def xhtml_to_html(xhtml):
  1193.     
  1194.     try:
  1195.         xhtml = xhtml.getroot()
  1196.     except AttributeError:
  1197.         pass
  1198.  
  1199.     prefix = '{%s}' % XHTML_NAMESPACE
  1200.     prefix_len = len(prefix)
  1201.     for el in xhtml.iter(prefix + '*'):
  1202.         el.tag = el.tag[prefix_len:]
  1203.     
  1204.  
  1205. __replace_meta_content_type = re.compile('<meta http-equiv="Content-Type".*?>').sub
  1206.  
  1207. def tostring(doc, pretty_print = False, include_meta_content_type = False, encoding = None, method = 'html'):
  1208.     html = etree.tostring(doc, method = method, pretty_print = pretty_print, encoding = encoding)
  1209.     if not include_meta_content_type:
  1210.         html = __replace_meta_content_type('', html)
  1211.     
  1212.     return html
  1213.  
  1214. tostring.__doc__ = __fix_docstring(tostring.__doc__)
  1215.  
  1216. def open_in_browser(doc):
  1217.     import os
  1218.     import webbrowser
  1219.     
  1220.     try:
  1221.         write_doc = doc.write
  1222.     except AttributeError:
  1223.         write_doc = etree.ElementTree(element = doc).write
  1224.  
  1225.     fn = os.tempnam() + '.html'
  1226.     write_doc(fn, method = 'html')
  1227.     url = 'file://' + fn.replace(os.path.sep, '/')
  1228.     print url
  1229.     webbrowser.open(url)
  1230.  
  1231.  
  1232. class HTMLParser(etree.HTMLParser):
  1233.     
  1234.     def __init__(self, **kwargs):
  1235.         super(HTMLParser, self).__init__(**kwargs)
  1236.         self.set_element_class_lookup(HtmlElementClassLookup())
  1237.  
  1238.  
  1239.  
  1240. class XHTMLParser(etree.XMLParser):
  1241.     
  1242.     def __init__(self, **kwargs):
  1243.         super(XHTMLParser, self).__init__(**kwargs)
  1244.         self.set_element_class_lookup(HtmlElementClassLookup())
  1245.  
  1246.  
  1247.  
  1248. def Element(*args, **kw):
  1249.     v = html_parser.makeelement(*args, **kw)
  1250.     return v
  1251.  
  1252. html_parser = HTMLParser()
  1253. xhtml_parser = XHTMLParser()
  1254.