# ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License # Version 1.1 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the # License for the specific language governing rights and limitations # under the License. # # The Original Code is Komodo code. # # The Initial Developer of the Original Code is ActiveState Software Inc. # Portions created by ActiveState Software Inc are Copyright (C) 2000-2007 # ActiveState Software Inc. All Rights Reserved. # # Contributor(s): # ActiveState Software Inc # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # import htmlentitydefs import re import sys import email import io from elementtree import ElementTree class recollector: def __init__(self): self.res = {} self.regs = {} def add(self, name, reg, mods=None): self.regs[name] = reg % self.regs # print "%s = %s" % (name, self.regs[name]) if mods: self.res[name] = re.compile(self.regs[ name], mods) # check that it is valid else: self.res[name] = re.compile(self.regs[ name]) # check that it is valid collector = recollector() a = collector.add a("TextSE", "[^<]+") a("UntilHyphen", "[^-]*-") a("Until2Hyphens", "%(UntilHyphen)s(?:[^-]%(UntilHyphen)s)*-") a("CommentCE", "%(Until2Hyphens)s>?") a("UntilRSBs", "[^\\]]*](?:[^\\]]+])*]+") a("CDATA_CE", "%(UntilRSBs)s(?:[^\\]>]%(UntilRSBs)s)*>") a("S", "[ \\n\\t\\r]+") a("NameStrt", "[A-Za-z_:]|[^\\x00-\\x7F]") a("NameChar", "[A-Za-z0-9_:.-]|[^\\x00-\\x7F]") a("Name", "(?:%(NameStrt)s)(?:%(NameChar)s)*") a("QuoteSE", "\"[^\"]*\"|'[^']*'") a("DT_IdentSE", "%(S)s%(Name)s(?:%(S)s(?:%(Name)s|%(QuoteSE)s))*") # http://bugs.activestate.com/show_bug.cgi?id=28765 # a("MarkupDeclCE" , "(?:[^\\]\"'><]+|%(QuoteSE)s)*>" ) a("MarkupDeclCE", "(?:[^\\]\"'> \\n\\t\\r<]+|%(QuoteSE)s)*>") a("S1", "[\\n\\r\\t ]") a("UntilQMs", "[^?]*\\?+") a("PI_Tail", "\\?>|%(S1)s%(UntilQMs)s(?:[^>?]%(UntilQMs)s)*>") a("DT_ItemSE", "<(?:!(?:--%(Until2Hyphens)s>|[^-]%(MarkupDeclCE)s)|\\?%(Name)s(?:%(PI_Tail)s))|%%%(Name)s;|%(S)s" ) a("DocTypeCE", "%(DT_IdentSE)s(?:%(S)s)?(?:\\[(?:%(DT_ItemSE)s)*](?:%(S)s)?)?>?") a("DeclCE", "--(?:%(CommentCE)s)?|\\[CDATA\\[(?:%(CDATA_CE)s)?|DOCTYPE(?:%(DocTypeCE)s)?") a("PI_CE", "%(Name)s(?:%(PI_Tail)s)?") a("EndTagCE", "(?P%(Name)s)(?:%(S)s)?>?") a("AttValSE", "\"[^<\"]*\"|'[^<']*'") a("ElemTagCE", "(?P%(Name)s)(?P(?:%(S)s%(Name)s(?:%(S)s)?=(?:%(S)s)?(?:%(AttValSE)s))*)(?:%(S)s)?/?>?") a("MarkupSPE", "<(?:!(?:%(DeclCE)s)?|\\?(?:%(PI_CE)s)?|/(?:%(EndTagCE)s)?|(?:%(ElemTagCE)s)?)") a("XML_SPE", "%(TextSE)s|%(MarkupSPE)s") a("XML_MARKUP_ONLY_SPE", "%(MarkupSPE)s") a("DOCTYPE", r'\S+)\s+(?PPUBLIC|SYSTEM)\s+(?P%(QuoteSE)s)\s*(?P%(QuoteSE)s)?\s*(?:\[|>)', re.S) a("attrfinderRE", "(?:[\n \t]*)(%(Name)s)(?:%(S)s)?=(?:%(S)s)?(%(AttValSE)s)", re.S | re.U) attrfinder = collector.res["attrfinderRE"] is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search def parseiter(data, markuponly=0): if markuponly: reg = "XML_MARKUP_ONLY_SPE" else: reg = "XML_SPE" regex = collector.res[reg] return regex.finditer(data) def strip_quotes(str): if not str: return None if str[0] in ["'", '"']: return str[1:-1] return str # XXX this should realy be done via DTD/Schema, but that would be a major # pain. For general purposes, this will work fine and be faster # these tags are defined to NOT ALLOW end tags at all in html. They never # have children and never have end tags # defined in dtd as ELEMENT NAME - O EMPTY html_no_close_tags = set([ "basefont", "br", "area", "link", "img", "param", "hr", "input", "col", "frame", "isindex", "base", "meta" ]) # defined in dtd as ELEMENT NAME - O * html_optional_close_tags = set([ "p", "dt", "dd", "li", "option", "thead", "tfoot", "colgroup", "col", "tr", "th", "td" ]) html_block_tags = set([ "p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "pre", "dl", "div", "noscript", "blockquote", "form", "hr", "table", "fieldset", "address" ]) # these are optional end tag and cannot contain other block tags defined above html_cannot_contain_block_tags = set([ "p", "dt" ]) html_close_tag_unnecessary = html_no_close_tags.union(html_optional_close_tags) class HTMLTreeBuilder(ElementTree.TreeBuilder): def __init__(self, encoding="iso-8859-1"): ElementTree.TreeBuilder.__init__(self) self.encoding = encoding self.nodes = [] self.nodemap = {} # {child_elem: parent_elem, ... } self._rootnodes = [] self.current = None def start(self, tag, attrs, loc_start, loc_end): if not tag: return # print loc if tag == "meta": # look for encoding directives http_equiv = content = None for k, v in attrs: if k == "http-equiv": http_equiv = v.lower() elif k == "content": content = v if http_equiv == "content-type" and content: # use email to parse the http header header = email.message.Message( io.StringIO("%s: %s\n\n" % (http_equiv, content)) ) encoding = header.get_charset() if encoding: self.encoding = encoding l_tag = tag.lower() if self._elem: p_tag = self._elem[-1].tag.lower() # if the parent and child are the same tag, then close the # parent if it uses optional close tags if l_tag in html_optional_close_tags and p_tag == l_tag: self.end(tag) # special case table tags that should be autoclosed only when # hitting a new table row elif p_tag in ("td", "th") and l_tag == "tr": self.end_tag(p_tag) # if the parent and child are block tags, close the parent elif p_tag in html_cannot_contain_block_tags and l_tag in html_block_tags: self.end_tag(p_tag) attrib = {} for attr in attrs: attrib[attr[0]] = strip_quotes(attr[1]) ElementTree.TreeBuilder.start(self, tag, attrib) el = self._elem[-1] self.current = el el.ns = None el.localName = el.tag el.start = loc_start el.end = None self.nodes.append(el) if len(self._elem) > 1: self.nodemap[el] = self._elem[-2] else: self.nodemap[el] = None if l_tag in html_no_close_tags: self.end_tag(tag, loc_end) def end(self, tag, loc=None): if not self._elem: return None l_tag = tag l_lasttag = lasttag = self._elem[-1].tag if l_tag: l_tag = l_tag.lower() if l_lasttag: l_lasttag = lasttag.lower() while (l_tag != l_lasttag and l_lasttag in html_optional_close_tags and len(self._elem) > 1 and self._last.start[2] < self._elem[-1].start[2]): self.end_tag(lasttag) if self._elem: lasttag = self._elem[-1].tag l_lasttag = lasttag.lower() else: self.current = self._last return self._last # protect against a previous close of this tag if l_tag in html_close_tag_unnecessary and l_tag != self._elem[-1].tag.lower(): return None return self.end_tag(tag, loc) def end_tag(self, tag, loc=None): if not tag: return None self._flush() # find this tag: tags = [e.localName for e in self._elem] if tag not in tags: # invalid end tag? return None last = self._elem.pop() while last.tag != tag: last = self._elem.pop() self._last = last if not self._elem: self._rootnodes.append(self._last) if loc: self._last.end = loc self._tail = 1 self.current = self._last return self._last def data(self, data): if isinstance(data, bytes) and is_not_ascii(data): # convert to unicode, but only if necessary data = str(data, self.encoding, "ignore") ElementTree.TreeBuilder.data(self, data) def close(self): if self._elem: return self._elem[0] return self._last class Parser: def __init__(self, builder=None): if not builder: builder = ElementTree.TreeBuilder() self._builder = builder self.doctype = None self.publicId = None self.systemId = None self.locator = {} self._lastloc = None self.data = None def parse_doctype(self, data): m = collector.res["DOCTYPE"].match(data) if m is None: return result = m.groupdict() self.doctype = result self.publicId = None if result['ident'] == "PUBLIC": self.publicId = strip_quotes(result['data1']) self.systemId = strip_quotes(result['data2']) else: self.systemId = strip_quotes(result['data1']) def getLocation(self, loc): pos = 0 last_lines = 0 if self._lastloc: pos = self._lastloc last_lines = self.locator[pos][0] lines = last_lines + self.data.count("\n", pos, loc) col = 0 if lines > last_lines: col = loc - self.data.rfind("\n", pos, loc) - 1 elif pos in self.locator: col = loc - pos + self.locator[pos][1] self.locator[loc] = [lines, col] self._lastloc = loc return (lines + 1, col, loc) def feed(self, data, markuponly=0): no_close_tag = [] opt_close_tag = [] self.data = data for matchObj in parseiter(data, markuponly): x = matchObj.group(0) m = matchObj.groupdict() if x.startswith(""): self._builder.end(m["tag"], end) else: self._builder.data(x) def close(self): return self._builder.close() try: import sgmlop ReParser = Parser class SgmlopParser(ReParser): def __init__(self, builder=None): ReParser.__init__(self, builder) self.__parser = sgmlop.XMLParser() self.__parser.register(self) def finish_starttag(self, tag, attrib, loc_start, loc_end): # builder expects a list of tuples attrs = list(attrib.items()) self._builder.start(tag, attrs, self.getLocation( loc_start), self.getLocation(loc_end)) def finish_endtag(self, tag, loc): self._builder.end(tag, self.getLocation(loc)) def handle_data(self, data): self._builder.data(data) def handle_special(self, data, token_type=None): # here's where we figure out if we've got a doctype if (token_type == 0x105 or # from sgmlop.c data and data.startswith("DOCTYPE")): # we get everything inside self.parse_doctype("" % data) def feed(self, data, markuponly=0): self.data = data return self.__parser.feed(data) def close(self): if self.__parser: self.__parser.close() self.__parser = None return ReParser.close(self) Parser = SgmlopParser except: pass def HTML(data, ParserClass=Parser): p = ParserClass(HTMLTreeBuilder()) p.feed(data) return p.close() if __name__ == "__main__": import sys if len(sys.argv) > 1: import time # read the file and parse it to get a time. f = open(sys.argv[1]) data = f.read() f.close() t1 = time.time() tree = HTML(data, ReParser) t2 = time.time() print("RE parsing took %s" % (t2-t1)) t1 = time.time() tree = HTML(data, SgmlopParser) t2 = time.time() print("sgmlop parsing took %s" % (t2-t1)) sys.exit(0) data = """ my title

blah blah... blah

""" tree = HTML(data) print(ElementTree.tostring(tree)) sys.exit(0) data = """ """ tree = HTML(data) print(ElementTree.tostring(tree)) sys.exit(0) data = """



""" #
#

#
  • #
  • #
  • #
  • #

    #


    #

    # # #""" data = """ XML Catalogs

    XML Catalogs

    Komodo can add XML autocompletion support for any XML dialect with a DTD or RelaxNG Schema. This is done by mapping external identifier entries to local copies of the DTD or RelaxNG Schema for that document type using XML Catalogs.

     

    Using an Existing XML Catalog

    Some toolkits bundle DTDs or RelaxNG Schemas with their own XML catalogs. As long as the relative path from the catalog to the .dtd or .rng file is preserved on the local filesystem, you can add support for the dialect by specifying the catalog file in Preferences under SGML/XML Catalogs.

     

    Creating an XML Catalog

    If the DTD or RelaxNG Schema for the dialect does not have a catalog file, you can create one by mapping the external identifiers and URI references in the document's namespace declaration to a local filesystem URI. For example, the XSPF playlist format uses the following namespace declaration:

      <playlist version="1" xmlns="http://xspf.org/ns/0/">
    

    A simple catalog for this XML dialect would look like this:

      <?xml version='1.0'?>
      <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"
      prefer="public">
    
        <uri name="http://xspf.org/ns/0/" uri="xspf-draft8.rng"/>
    
      </catalog>
    

    If your documents use the DOCTYPE declaration, you can add support for that in the catalog by using the public and system identifier. For example, XUL uses DOCTYPE declarations like this one:

      <!DOCTYPE overlay PUBLIC "-//MOZILLA//DTD XUL V1.0//EN"
      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    

    Komodo's catalog for XUL uses publicId and systemId in addition to uri for the mapping.

      <?xml version='1.0'?>
      <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="public">
    
          <public publicId="-//MOZILLA//DTD XUL V1.0//EN"
                  uri="xul.dtd"/>
          <system systemId="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
                  uri="xul.dtd"/>
          <uri name="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
               uri="xul.dtd"/>
    
      </catalog>
    

     

    XML Catalog Resources

    The XML Catalog specification can be found at:

    Examples of XML catalog files can be found in the Komodo installation under:

    • <komodo-install-directory>\lib\support\catalogs (Windows)
    • /Applications/Komodo.app/Contents/SharedSupport/catalogs/ (OS X)
    • <komodo-install-directory>/lib/support/catalogs (Linux)

     


    """ tree = HTML(data) # print ElementTree.tostring(tree) data = """ link('calendar') ?> init($othAuth->data);?> css{'hack'};?> """ tree = HTML(data) # print ElementTree.tostring(tree) data = """<%= error_messages_for 'product' %>


    <%= text_field 'product', 'title' %>


    <%= text_area 'product', 'description' %>


    <%= text_field 'product', 'image_url' %>


    <%= text_field 'product', 'price' %>


    <%= datetime_select 'product', 'date_available' %>

    """ tree = HTML(data) print(ElementTree.tostring(tree)) p = Parser(HTMLTreeBuilder()) p.feed(data) p.close()