Hello. I have pushed the initial commit for ReadFeeds addon. Some points: 1. It is in development, without documentation, since it must to be converted to markdown format, and changes could still be added. 2. I have sent the version found in italian site, with the corresponding channels, which can be also modified. 3. I have indicated my name as author, but other contributors should add his or her name too. Thanks. 2013/7/13, commits-noreply@xxxxxxxxxxxxx <commits-noreply@xxxxxxxxxxxxx>: > 1 new commit in readFeeds: > > https://bitbucket.org/nvdaaddonteam/readfeeds/commits/e15c9cb7fbe9/ > Changeset: e15c9cb7fbe9 > Branch: master > User: norrumar > Date: 2013-07-13 15:08:24 > Summary: Initial commit. > > Affected #: 38 files > > diff --git a/.gitattributes b/.gitattributes > new file mode 100644 > index 0000000..ceae4de > --- /dev/null > +++ b/.gitattributes > @@ -0,0 +1,2 @@ > +# Set default behaviour, in case users don't have core.autocrlf set. > +* text=auto > > diff --git a/.gitignore b/.gitignore > new file mode 100644 > index 0000000..6d124be > --- /dev/null > +++ b/.gitignore > @@ -0,0 +1,10 @@ > +addon/doc/*.css > +addon/doc/en/ > +*_docHandler.py > +*.html > +*.ini > +*.mo > +*.pot > +*.pyc > +*.nvda-addon > +.sconsign.dblite > > diff --git a/addon/globalPlugins/RSS/Blog SpazioAusili.txt > b/addon/globalPlugins/RSS/Blog SpazioAusili.txt > new file mode 100644 > index 0000000..4e93538 > --- /dev/null > +++ b/addon/globalPlugins/RSS/Blog SpazioAusili.txt > @@ -0,0 +1 @@ > +http://www.spazioausili.net/rss/blog > \ No newline at end of file > > diff --git a/addon/globalPlugins/RSS/Forum SpazioAusili.txt > b/addon/globalPlugins/RSS/Forum SpazioAusili.txt > new file mode 100644 > index 0000000..467e223 > --- /dev/null > +++ b/addon/globalPlugins/RSS/Forum SpazioAusili.txt > @@ -0,0 +1 @@ > +http://www.spazioausili.net/rss/forum > \ No newline at end of file > > diff --git a/addon/globalPlugins/RSS/W3C-news.txt > b/addon/globalPlugins/RSS/W3C-news.txt > new file mode 100644 > index 0000000..d6d78cd > --- /dev/null > +++ b/addon/globalPlugins/RSS/W3C-news.txt > @@ -0,0 +1 @@ > +http://www.w3.org/News/atom.xml > \ No newline at end of file > > diff --git a/addon/globalPlugins/RSS/addons NVDA-project IT RSS.txt > b/addon/globalPlugins/RSS/addons NVDA-project IT RSS.txt > new file mode 100644 > index 0000000..ce662ed > --- /dev/null > +++ b/addon/globalPlugins/RSS/addons NVDA-project IT RSS.txt > @@ -0,0 +1 @@ > +http://addons.nvda-project.org/index.it.rss > \ No newline at end of file > > diff --git a/addon/globalPlugins/RSS/addressFile.txt > b/addon/globalPlugins/RSS/addressFile.txt > new file mode 100644 > index 0000000..467e223 > --- /dev/null > +++ b/addon/globalPlugins/RSS/addressFile.txt > @@ -0,0 +1 @@ > +http://www.spazioausili.net/rss/forum > \ No newline at end of file > > diff --git a/addon/globalPlugins/readFeeds.py > b/addon/globalPlugins/readFeeds.py > new file mode 100644 > index 0000000..42ae39f > --- /dev/null > +++ b/addon/globalPlugins/readFeeds.py > @@ -0,0 +1,388 @@ > +# -*- coding: UTF-8 -*- > + > +# Read feeds: A simple plugin for reading feeds with NVDA > +# Version: 5.0 > +# Control instead of alt in gesture. Added installTask to update the > add-on > +# Version: 4.2 > +# Channel title and number of articles in News list dialog > +# Date: 23/06/2012 > + > + > +import addonHandler > +import globalPluginHandler > +import languageHandler > +import os > +import sys > +import shutil > +import config > +import urllib > +import scriptHandler > +import api > +import gui > +import wx > +import ui > + > +sys.path.append(os.path.dirname(__file__)) > +from xml2.dom import minidom > +del sys.path[-1] > + > +addonHandler.initTranslation() > + > +address = 'http://rss.slashdot.org/Slashdot/slashdot' # Default address > +_savePath = os.path.join(os.path.dirname(__file__), "RSS") > +addressFile = os.path.join(_savePath, "addressFile.txt") > + > +try: > + f = open(addressFile, "r") > + address = f.read() > + f.close() > +except IOError: > + pass > + > + > +class Feed: > + > + def __init__(self, range): > + # The URL of the RSS feed > + # Our actual XML document > + try: > + document = minidom.parse(urllib.urlopen(address)) > + except IOError: > + self.counter = -1 > + self.titlesList = [] > + self.title = "" > + self.linksList = [] > + self.link = "" > + self.channelName = "" > + return > + index = 0 > + self.counter = 0 > + self.titlesList = [] > + self.linksList = [] > + channel = document.getElementsByTagName('channel') > + if len(channel)==1: > + self.RSSArticles = True > + else: > + self.RSSArticles = False > + channel = document.getElementsByTagName('feed') > + channelTitle = > channel[0].getElementsByTagName('title')[0].firstChild > + if channelTitle is None: > + self.channelName = "" > + else: > + self.channelName = channelTitle.data > + # if self.channelName is None or self.channelName == "": > + # ui.message(_("Untitled feed")) > + if self.RSSArticles: > + articles = document.getElementsByTagName('item') > + else: > + articles = document.getElementsByTagName('entry') > + if len(articles) == 0: > + self.counter = -1 > + return > + for item in articles: > + self.counter +=1 > + title = > item.getElementsByTagName('title')[0].firstChild.data > + if title is None or title == "": > + self.counter = -1 > + ui.message(_("This feed contains untitled > items")) > + return > + self.titlesList.append(title) > + if self.RSSArticles: > + link = > item.getElementsByTagName('link')[0].firstChild.data > + else: > + link = > item.getElementsByTagName('link')[0].getAttribute('href') > + if link is None or link == "": > + self.counter = -1 > + ui.message(_("This feed contains items without > a link")) > + return > + self.linksList.append(link) > + if index <= range: > + self.title = title > + self.link = link > + # self.creator = > item.getElementsByTagName('dc:creator')[0].firstChild.data > + index +=1 > + > + > +class GlobalPlugin(globalPluginHandler.GlobalPlugin): > + > + def __init__(self): > + super(globalPluginHandler.GlobalPlugin, self).__init__() > + self.menu = gui.mainFrame.sysTrayIcon.menu > + self.readFeedsMenu = wx.Menu() > + self.mainItem = self.menu.AppendSubMenu(self.readFeedsMenu, > _("&Read > feeds"), "Manage RSS and Atom feeds") > + self.newsListItem = self.readFeedsMenu.Append(wx.ID_ANY, _("News > &list..."), _("View news list")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onList, > self.newsListItem) > + self.setAddressItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("&Temporary > address..."), _("View or choose current feed address")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onSetAddress, > self.setAddressItem) > + self.setAddressFileItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("L&oad > saved address..."), _("Choose file containing address")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, > self.onSetAddressFile, > self.setAddressFileItem) > + self.saveAddressItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("Save > current address..."), _("Copy current address to text file")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onSaveAddress, > self.saveAddressItem) > + self.readFirstItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("Updat&e > current feed"), _("Reload current feed")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, > self.onReadFirstFeed, > self.readFirstItem) > + self.copyFeedsItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("&Copy feeds > folder..."), _("Backup of feeds folder")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onCopyFeeds, > self.copyFeedsItem) > + self.restoreFeedsItem = self.readFeedsMenu.Append(wx.ID_ANY, > _("R&estore > feeds..."), _("Restore previously saved feeds")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onRestoreFeeds, > self.restoreFeedsItem) > + self.aboutItem = self.readFeedsMenu.Append(wx.ID_ANY, _("Open > &documentation"), _("Open documentation for current language")) > + gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onAbout, > self.aboutItem) > + self._counter = -1 > + self._titlesList = [] > + self._linksList = [] > + self._index = 0 > + self._channelName = "" > + > + def terminate(self): > + try: > + self.menu.RemoveItem(self.mainItem) > + except wx.PyDeadObjectError: > + pass > + > + def onList(self, evt): > + _titlesList = self._titlesList > + _linksList = self._linksList > + if _titlesList == []: > + wx.CallAfter(gui.messageBox, _("Feeds can not be > reported. Check your > Internet conectivity or specified address"), _("Update Error"), > wx.OK|wx.ICON_ERROR) > + return > + dlg = wx.SingleChoiceDialog(gui.mainFrame, _("Open the web page > for > reading your selected article"), "%s (%d)" % (self._channelName, > len(_titlesList)), choices=_titlesList) > + dlg.SetSelection(0) > + gui.mainFrame.prePopup() > + try: > + result = dlg.ShowModal() > + except AttributeError: > + pass > + gui.mainFrame.postPopup() > + if result == wx.ID_OK: > + self._index = dlg.GetSelection() > + link = _linksList[self._index] > + os.startfile(link) > + > + def onSetAddress(self, evt): > + self.setAddressDialog() > + > + def onSetAddressFile(self, evt): > + self.setAddressFileDialog() > + > + def onSaveAddress(self, evt): > + self.saveAddressDialog() > + > + def onReadFirstFeed(self, evt): > + self._index = 0 > + self._titlesList = [] > + self._linksList = [] > + self._channelName = "" > + _RSS = Feed(self._index) > + self._RSS = _RSS > + self._counter = _RSS.counter > + self._titlesList = _RSS.titlesList > + self._linksList = _RSS.linksList > + self._channelName = _RSS.channelName > + if self._counter == -1: > + self._titlesList = [] > + self._linksList = [] > + self._channelName = "" > + if self.getCounter() == -1: > + ui.message(_("Feeds can not be reported. Check your > Internet conectivity > or specified address")) > + return > + RSS = self._RSS > + ui.message(RSS.title) > + > + def onCopyFeeds(self, evt): > + configPath = config.getUserDefaultConfigPath() > + dlg = wx.DirDialog(gui.mainFrame, _("Select a folder for > copying your > saved feeds"), configPath, wx.DD_DEFAULT_STYLE) > + gui.mainFrame.prePopup() > + result = dlg.ShowModal() > + gui.mainFrame.postPopup() > + if result == wx.ID_OK: > + copyPath = os.path.join(dlg.GetPath(), "RSS") > + try: > + shutil.rmtree(copyPath, ignore_errors=True) > + shutil.copytree(_savePath, copyPath) > + except WindowsError: > + wx.CallAfter(gui.messageBox,_("Folder not > copied"), _("Copy Error"), > wx.OK|wx.ICON_ERROR) > + > + def onRestoreFeeds(self, evt): > + feedsPath = os.path.join(config.getUserDefaultConfigPath(), > "RSS") > + dlg = wx.DirDialog(gui.mainFrame, _("Select the feeds folder > you wish to > restore"), feedsPath, wx.DD_DIR_MUST_EXIST | wx.DD_DEFAULT_STYLE) > + gui.mainFrame.prePopup() > + result = dlg.ShowModal() > + gui.mainFrame.postPopup() > + if result == wx.ID_OK: > + feedsPath = dlg.GetPath() > + try: > + shutil.rmtree(_savePath, ignore_errors=True) > + shutil.copytree(feedsPath, _savePath) > + except WindowsError: > + wx.CallAfter(gui.messageBox,_("Folder not > copied"), _("Copy Error"), > wx.OK|wx.ICON_ERROR) > + > + def getDocFolder(self): > + langs = [languageHandler.getLanguage(), "en"] > + for lang in langs: > + docFolder = os.path.join(os.path.dirname(__file__), > "..", "doc", lang) > + if os.path.isdir(docFolder): > + return docFolder > + if "_" in lang: > + tryLang = lang.split("_")[0] > + docFolder = > os.path.join(os.path.dirname(__file__), "..", "doc", > tryLang) > + if os.path.isdir(docFolder): > + return docFolder > + if tryLang == "en": > + break > + if lang == "en": > + break > + return None > + > + def getDocPath(self, docFileName): > + docPath = self.getDocFolder() > + if docPath is not None: > + docFile = os.path.join(docPath, docFileName) > + if os.path.isfile(docFile): > + docPath = docFile > + return docPath > + > + def onAbout(self, evt): > + try: > + os.startfile(self.getDocPath("readme.html")) > + except WindowsError: > + pass > + > + def getCounter(self): > + return self._counter > + > + def getFeed(self): > + # feed = "" > + feed = self._titlesList[self._index] > + return feed > + > + def script_readFirstFeed(self, gesture): > + self.onReadFirstFeed(None) > + script_readFirstFeed.__doc__ = _("Updates the selected channel and reads > the first feed title.") > + > + def script_readCurrentFeed(self, gesture): > + if self.getCounter() == -1: > + ui.message(_("Feeds can not be reported. Check your > Internet conectivity > or specified address")) > + return > + feed = self.getFeed() > + link = self._linksList[self._index] > + feedLink = "%s\r\n\r\n%s" % (feed, link) > + if scriptHandler.getLastScriptRepeatCount()==1 and > api.copyToClip(feedLink): > + ui.message(_("Copied to clipboard %s") % feedLink) > + else: > + ui.message(feed) > + script_readCurrentFeed.__doc__ = _("Reads the current feed title. > Pressed > two times, copies the text to clipboard.") > + > + def script_readNextFeed(self, gesture): > + if self.getCounter() == -1: > + ui.message(_("Feeds can not be reported. Check your > Internet conectivity > or specified address")) > + return > + if self._index >= self.getCounter()-1: > + self._index = 0 > + else: > + self._index += 1 > + ui.message(self.getFeed()) > + script_readNextFeed.__doc__ = _("Reads the next feed title.") > + > + def script_readPriorFeed(self, gesture): > + if self.getCounter() == -1: > + ui.message(_("Feeds can not be reported. Check your > Internet conectivity > or specified address")) > + return > + if self._index <= 0: > + self._index = self.getCounter()-1 > + else: > + self._index -=1 > + ui.message(self.getFeed()) > + script_readPriorFeed.__doc__ = _("Reads the prior feed title.") > + > + def script_reportLink(self, gesture): > + if self.getCounter() == -1: > + ui.message(_("Feeds can not be reported. Check your > Internet conectivity > or specified address")) > + return > + feedLink = self._linksList[self._index] > + if scriptHandler.getLastScriptRepeatCount()==1: > + os.startfile(feedLink) > + else: > + ui.message(feedLink) > + script_reportLink.__doc__ = _("Reads the article link, that allows open > its web page. Pressed two times, opens the web.") > + > + def setAddressDialog(self): > + d = wx.TextEntryDialog(gui.mainFrame, _("The address of the > channel you > wish to follow"), _("Address"), defaultValue=address) > + def callback(result): > + if result == wx.ID_OK: > + # Make sure this happens after focus returns to > the document. > + wx.CallLater(100, self.doSetAddress, > d.GetValue()) > + gui.runScriptModalDialog(d, callback) > + > + def doSetAddress(self, text): > + if not text: > + return > + global address > + address=text > + self._index = 0 > + self._RSS =Feed(self._index) > + ui.message(_("Selected %s") % address) > + > + def script_setAddress(self, gesture): > + self.onSetAddress(None) > + script_setAddress.__doc__ = _("Opens a dialog for setting the address of > the feeds channel.") > + > + def setAddressFileDialog(self): > + d =wx.FileDialog(gui.mainFrame, _("Choose a file containing a > channel > address"), _savePath, "addressFile.txt", _("Text files (*.txt) |*.txt"), > wx.FD_OPEN) > + def callback(result): > + if result == wx.ID_OK: > + # Make sure this happens after focus returns to > the document. > + wx.CallLater(100, self.doSetAddressFile, > d.GetPath()) > + gui.runScriptModalDialog(d, callback) > + > + def doSetAddressFile(self, file): > + if not file: > + return > + global address > + try: > + f = open (file, "r") > + address = f.read() > + f.close() > + ui.message(_("Selected %s") % address) > + except IOError: > + ui.message(_("Address can not be selected")) > + return > + self._index = 0 > + self._RSS =Feed(self._index) > + > + def script_setAddressFile(self, gesture): > + self.onSetAddressFile(None) > + script_setAddressFile.__doc__ = _("Opens a dialog for setting the > address > of the feeds channel from a selected file.") > + > + def saveAddressDialog(self): > + d =wx.FileDialog(gui.mainFrame, _("File for saving your current > address"), _savePath, "addressFile.txt", _("Text files (*.txt) | *.txt"), > wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) > + def callback(result): > + if result == wx.ID_OK: > + # Make sure this happens after focus returns to > the document. > + wx.CallLater(200, self.doSaveAddress, > d.GetPath()) > + gui.runScriptModalDialog(d, callback) > + > + def doSaveAddress(self, file): > + if not file: > + return > + global address > + try: > + f = open (file, "w") > + f.write(address) > + f.close() > + ui.message(_("Saved %s") % address) > + except IOError: > + ui.message(_("Address can not be saved")) > + > + def script_saveAddress(self, gesture): > + self.onSaveAddress(None) > + script_saveAddress.__doc__ = _("Opens a dialog for saving a file > containing the selected address.") > + > + __gestures = { > + "kb:control+shift+NVDA+8": "readFirstFeed", > + "kb:control+shift+NVDA+i": "readCurrentFeed", > + "kb:control+shift+NVDA+o": "readNextFeed", > + "kb:control+shift+NVDA+u": "readPriorFeed", > + "kb:control+shift+NVDA+space": "reportLink", > + "kb:control+shift+NVDA+enter": "setAddress", > + "kb:control+NVDA+enter": "setAddressFile", > + "kb:shift+NVDA+enter": "saveAddress", > + } > \ No newline at end of file > > diff --git a/addon/globalPlugins/xml2/__init__.py > b/addon/globalPlugins/xml2/__init__.py > new file mode 100644 > index 0000000..f7dcb04 > --- /dev/null > +++ b/addon/globalPlugins/xml2/__init__.py > @@ -0,0 +1,48 @@ > +"""Core XML support for Python. > + > +This package contains four sub-packages: > + > +dom -- The W3C Document Object Model. This supports DOM Level 1 + > + Namespaces. > + > +parsers -- Python wrappers for XML parsers (currently only supports > Expat). > + > +sax -- The Simple API for XML, developed by XML-Dev, led by David > + Megginson and ported to Python by Lars Marius Garshol. This > + supports the SAX 2 API. > + > +etree -- The ElementTree XML library. This is a subset of the full > + ElementTree XML release. > + > +""" > + > + > +__all__ = ["dom", "parsers", "sax", "etree"] > + > +_MINIMUM_XMLPLUS_VERSION = (0, 8, 4) > + > + > +try: > + import _xmlplus > +except ImportError: > + pass > +else: > + try: > + v = _xmlplus.version_info > + except AttributeError: > + # _xmlplus is too old; ignore it > + pass > + else: > + if v >= _MINIMUM_XMLPLUS_VERSION: > + import sys > + _xmlplus.__path__.extend(__path__) > + sys.modules[__name__] = _xmlplus > + else: > + del v > + > +import globalPluginHandler > + > +class GlobalPlugin(globalPluginHandler.GlobalPlugin): > + > + """Plugin class""" > + > > diff --git a/addon/globalPlugins/xml2/dom/NodeFilter.py > b/addon/globalPlugins/xml2/dom/NodeFilter.py > new file mode 100644 > index 0000000..fc05245 > --- /dev/null > +++ b/addon/globalPlugins/xml2/dom/NodeFilter.py > @@ -0,0 +1,27 @@ > +# This is the Python mapping for interface NodeFilter from > +# DOM2-Traversal-Range. It contains only constants. > + > +class NodeFilter: > + """ > + This is the DOM2 NodeFilter interface. It contains only constants. > + """ > + FILTER_ACCEPT = 1 > + FILTER_REJECT = 2 > + FILTER_SKIP = 3 > + > + SHOW_ALL = 0xFFFFFFFFL > + SHOW_ELEMENT = 0x00000001 > + SHOW_ATTRIBUTE = 0x00000002 > + SHOW_TEXT = 0x00000004 > + SHOW_CDATA_SECTION = 0x00000008 > + SHOW_ENTITY_REFERENCE = 0x00000010 > + SHOW_ENTITY = 0x00000020 > + SHOW_PROCESSING_INSTRUCTION = 0x00000040 > + SHOW_COMMENT = 0x00000080 > + SHOW_DOCUMENT = 0x00000100 > + SHOW_DOCUMENT_TYPE = 0x00000200 > + SHOW_DOCUMENT_FRAGMENT = 0x00000400 > + SHOW_NOTATION = 0x00000800 > + > + def acceptNode(self, node): > + raise NotImplementedError > > diff --git a/addon/globalPlugins/xml2/dom/__init__.py > b/addon/globalPlugins/xml2/dom/__init__.py > new file mode 100644 > index 0000000..6363d00 > --- /dev/null > +++ b/addon/globalPlugins/xml2/dom/__init__.py > @@ -0,0 +1,139 @@ > +"""W3C Document Object Model implementation for Python. > + > +The Python mapping of the Document Object Model is documented in the > +Python Library Reference in the section on the xml.dom package. > + > +This package contains the following modules: > + > +minidom -- A simple implementation of the Level 1 DOM with namespace > + support added (based on the Level 2 specification) and other > + minor Level 2 functionality. > + > +pulldom -- DOM builder supporting on-demand tree-building for selected > + subtrees of the document. > + > +""" > + > + > +class Node: > + """Class giving the NodeType constants.""" > + > + # DOM implementations may use this as a base class for their own > + # Node implementations. If they don't, the constants defined here > + # should still be used as the canonical definitions as they match > + # the values given in the W3C recommendation. Client code can > + # safely refer to these values in all tests of Node.nodeType > + # values. > + > + ELEMENT_NODE = 1 > + ATTRIBUTE_NODE = 2 > + TEXT_NODE = 3 > + CDATA_SECTION_NODE = 4 > + ENTITY_REFERENCE_NODE = 5 > + ENTITY_NODE = 6 > + PROCESSING_INSTRUCTION_NODE = 7 > + COMMENT_NODE = 8 > + DOCUMENT_NODE = 9 > + DOCUMENT_TYPE_NODE = 10 > + DOCUMENT_FRAGMENT_NODE = 11 > + NOTATION_NODE = 12 > + > + > +#ExceptionCode > +INDEX_SIZE_ERR = 1 > +DOMSTRING_SIZE_ERR = 2 > +HIERARCHY_REQUEST_ERR = 3 > +WRONG_DOCUMENT_ERR = 4 > +INVALID_CHARACTER_ERR = 5 > +NO_DATA_ALLOWED_ERR = 6 > +NO_MODIFICATION_ALLOWED_ERR = 7 > +NOT_FOUND_ERR = 8 > +NOT_SUPPORTED_ERR = 9 > +INUSE_ATTRIBUTE_ERR = 10 > +INVALID_STATE_ERR = 11 > +SYNTAX_ERR = 12 > +INVALID_MODIFICATION_ERR = 13 > +NAMESPACE_ERR = 14 > +INVALID_ACCESS_ERR = 15 > +VALIDATION_ERR = 16 > + > + > +class DOMException(Exception): > + """Abstract base class for DOM exceptions. > + Exceptions with specific codes are specializations of this class.""" > + > + def __init__(self, *args, **kw): > + if self.__class__ is DOMException: > + raise RuntimeError( > + "DOMException should not be instantiated directly") > + Exception.__init__(self, *args, **kw) > + > + def _get_code(self): > + return self.code > + > + > +class IndexSizeErr(DOMException): > + code = INDEX_SIZE_ERR > + > +class DomstringSizeErr(DOMException): > + code = DOMSTRING_SIZE_ERR > + > +class HierarchyRequestErr(DOMException): > + code = HIERARCHY_REQUEST_ERR > + > +class WrongDocumentErr(DOMException): > + code = WRONG_DOCUMENT_ERR > + > +class InvalidCharacterErr(DOMException): > + code = INVALID_CHARACTER_ERR > + > +class NoDataAllowedErr(DOMException): > + code = NO_DATA_ALLOWED_ERR > + > +class NoModificationAllowedErr(DOMException): > + code = NO_MODIFICATION_ALLOWED_ERR > + > +class NotFoundErr(DOMException): > + code = NOT_FOUND_ERR > + > +class NotSupportedErr(DOMException): > + code = NOT_SUPPORTED_ERR > + > +class InuseAttributeErr(DOMException): > + code = INUSE_ATTRIBUTE_ERR > + > +class InvalidStateErr(DOMException): > + code = INVALID_STATE_ERR > + > +class SyntaxErr(DOMException): > + code = SYNTAX_ERR > + > +class InvalidModificationErr(DOMException): > + code = INVALID_MODIFICATION_ERR > + > +class NamespaceErr(DOMException): > + code = NAMESPACE_ERR > + > +class InvalidAccessErr(DOMException): > + code = INVALID_ACCESS_ERR > + > +class ValidationErr(DOMException): > + code = VALIDATION_ERR > + > +class UserDataHandler: > + """Class giving the operation constants for > UserDataHandler.handle().""" > + > + # Based on DOM Level 3 (WD 9 April 2002) > + > + NODE_CLONED = 1 > + NODE_IMPORTED = 2 > + NODE_DELETED = 3 > + NODE_RENAMED = 4 > + > +XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; > +XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"; > +XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; > +EMPTY_NAMESPACE = None > +EMPTY_PREFIX = None > + > +from domreg import getDOMImplementation,registerDOMImplementation > > diff --git a/addon/globalPlugins/xml2/dom/domreg.py > b/addon/globalPlugins/xml2/dom/domreg.py > new file mode 100644 > index 0000000..ea8b7c9 > --- /dev/null > +++ b/addon/globalPlugins/xml2/dom/domreg.py > @@ -0,0 +1,99 @@ > +"""Registration facilities for DOM. This module should not be used > +directly. Instead, the functions getDOMImplementation and > +registerDOMImplementation should be imported from xml.dom.""" > + > +from xml2.dom.minicompat import * # isinstance, StringTypes > + > +# This is a list of well-known implementations. Well-known names > +# should be published by posting to xml-sig@xxxxxxxxxx, and are > +# subsequently recorded in this file. > + > +well_known_implementations = { > + 'minidom':'xml.dom.minidom', > + '4DOM': 'xml.dom.DOMImplementation', > + } > + > +# DOM implementations not officially registered should register > +# themselves with their > + > +registered = {} > + > +def registerDOMImplementation(name, factory): > + """registerDOMImplementation(name, factory) > + > + Register the factory function with the name. The factory function > + should return an object which implements the DOMImplementation > + interface. The factory function can either return the same object, > + or a new one (e.g. if that implementation supports some > + customization).""" > + > + registered[name] = factory > + > +def _good_enough(dom, features): > + "_good_enough(dom, features) -> Return 1 if the dom offers the > features" > + for f,v in features: > + if not dom.hasFeature(f,v): > + return 0 > + return 1 > + > +def getDOMImplementation(name = None, features = ()): > + """getDOMImplementation(name = None, features = ()) -> DOM > implementation. > + > + Return a suitable DOM implementation. The name is either > + well-known, the module name of a DOM implementation, or None. If > + it is not None, imports the corresponding module and returns > + DOMImplementation object if the import succeeds. > + > + If name is not given, consider the available implementations to > + find one with the required feature set. If no implementation can > + be found, raise an ImportError. The features list must be a sequence > + of (feature, version) pairs which are passed to hasFeature.""" > + > + import os > + creator = None > + mod = well_known_implementations.get(name) > + if mod: > + mod = __import__(mod, {}, {}, ['getDOMImplementation']) > + return mod.getDOMImplementation() > + elif name: > + return registered[name]() > + elif "PYTHON_DOM" in os.environ: > + return getDOMImplementation(name = os.environ["PYTHON_DOM"]) > + > + # User did not specify a name, try implementations in arbitrary > + # order, returning the one that has the required features > + if isinstance(features, StringTypes): > + features = _parse_feature_string(features) > + for creator in registered.values(): > + dom = creator() > + if _good_enough(dom, features): > + return dom > + > + for creator in well_known_implementations.keys(): > + try: > + dom = getDOMImplementation(name = creator) > + except StandardError: # typically ImportError, or AttributeError > + continue > + if _good_enough(dom, features): > + return dom > + > + raise ImportError,"no suitable DOM implementation found" > + > +def _parse_feature_string(s): > + features = [] > + parts = s.split() > + i = 0 > + length = len(parts) > + while i < length: > + feature = parts[i] > + if feature[0] in "0123456789": > + raise ValueError, "bad feature name: %r" % (feature,) > + i = i + 1 > + version = None > + if i < length: > + v = parts[i] > + if v[0] in "0123456789": > + i = i + 1 > + version = v > + features.append((feature, version)) > + return tuple(features) > > diff --git a/addon/globalPlugins/xml2/dom/expatbuilder.py > b/addon/globalPlugins/xml2/dom/expatbuilder.py > new file mode 100644 > index 0000000..ddfef11 > --- /dev/null > +++ b/addon/globalPlugins/xml2/dom/expatbuilder.py > @@ -0,0 +1,983 @@ > +"""Facility to use the Expat parser to load a minidom instance > +from a string or file. > + > +This avoids all the overhead of SAX and pulldom to gain performance. > +""" > + > +# Warning! > +# > +# This module is tightly bound to the implementation details of the > +# minidom DOM and can't be used with other DOM implementations. This > +# is due, in part, to a lack of appropriate methods in the DOM (there is > +# no way to create Entity and Notation nodes via the DOM Level 2 > +# interface), and for performance. The later is the cause of some fairly > +# cryptic code. > +# > +# Performance hacks: > +# > +# - .character_data_handler() has an extra case in which continuing > +# data is appended to an existing Text node; this can be a > +# speedup since pyexpat can break up character data into multiple > +# callbacks even though we set the buffer_text attribute on the > +# parser. This also gives us the advantage that we don't need a > +# separate normalization pass. > +# > +# - Determining that a node exists is done using an identity comparison > +# with None rather than a truth test; this avoids searching for and > +# calling any methods on the node object if it exists. (A rather > +# nice speedup is achieved this way as well!) > + > +from xml2.dom import xmlbuilder, minidom, Node > +from xml2.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE > +from xml2.parsers import expat > +from xml2.dom.minidom import _append_child, _set_attribute_node > +from xml2.dom.NodeFilter import NodeFilter > + > +from xml2.dom.minicompat import * > + > +TEXT_NODE = Node.TEXT_NODE > +CDATA_SECTION_NODE = Node.CDATA_SECTION_NODE > +DOCUMENT_NODE = Node.DOCUMENT_NODE > + > +FILTER_ACCEPT = xmlbuilder.DOMBuilderFilter.FILTER_ACCEPT > +FILTER_REJECT = xmlbuilder.DOMBuilderFilter.FILTER_REJECT > +FILTER_SKIP = xmlbuilder.DOMBuilderFilter.FILTER_SKIP > +FILTER_INTERRUPT = xmlbuilder.DOMBuilderFilter.FILTER_INTERRUPT > + > +theDOMImplementation = minidom.getDOMImplementation() > + > +# Expat typename -> TypeInfo > +_typeinfo_map = { > + "CDATA": minidom.TypeInfo(None, "cdata"), > + "ENUM": minidom.TypeInfo(None, "enumeration"), > + "ENTITY": minidom.TypeInfo(None, "entity"), > + "ENTITIES": minidom.TypeInfo(None, "entities"), > + "ID": minidom.TypeInfo(None, "id"), > + "IDREF": minidom.TypeInfo(None, "idref"), > + "IDREFS": minidom.TypeInfo(None, "idrefs"), > + "NMTOKEN": minidom.TypeInfo(None, "nmtoken"), > + "NMTOKENS": minidom.TypeInfo(None, "nmtokens"), > + } > + > +class ElementInfo(object): > + __slots__ = '_attr_info', '_model', 'tagName' > + > + def __init__(self, tagName, model=None): > + self.tagName = tagName > + self._attr_info = [] > + self._model = model > + > + def __getstate__(self): > + return self._attr_info, self._model, self.tagName > + > + def __setstate__(self, state): > + self._attr_info, self._model, self.tagName = state > + > + def getAttributeType(self, aname): > + for info in self._attr_info: > + if info[1] == aname: > + t = info[-2] > + if t[0] == "(": > + return _typeinfo_map["ENUM"] > + else: > + return _typeinfo_map[info[-2]] > + return minidom._no_type > + > + def getAttributeTypeNS(self, namespaceURI, localName): > + return minidom._no_type > + > + def isElementContent(self): > + if self._model: > + type = self._model[0] > + return type not in (expat.model.XML_CTYPE_ANY, > + expat.model.XML_CTYPE_MIXED) > + else: > + return False > + > + def isEmpty(self): > + if self._model: > + return self._model[0] == expat.model.XML_CTYPE_EMPTY > + else: > + return False > + > + def isId(self, aname): > + for info in self._attr_info: > + if info[1] == aname: > + return info[-2] == "ID" > + return False > + > + def isIdNS(self, euri, ename, auri, aname): > + # not sure this is meaningful > + return self.isId((auri, aname)) > + > +def _intern(builder, s): > + return builder._intern_setdefault(s, s) > + > +def _parse_ns_name(builder, name): > + assert ' ' in name > + parts = name.split(' ') > + intern = builder._intern_setdefault > + if len(parts) == 3: > + uri, localname, prefix = parts > + prefix = intern(prefix, prefix) > + qname = "%s:%s" % (prefix, localname) > + qname = intern(qname, qname) > + localname = intern(localname, localname) > + else: > + uri, localname = parts > + prefix = EMPTY_PREFIX > + qname = localname = intern(localname, localname) > + return intern(uri, uri), localname, prefix, qname > + > + > +class ExpatBuilder: > + """Document builder that uses Expat to build a ParsedXML.DOM document > + instance.""" > + > + def __init__(self, options=None): > + if options is None: > + options = xmlbuilder.Options() > + self._options = options > + if self._options.filter is not None: > + self._filter = > FilterVisibilityController(self._options.filter) > + else: > + self._filter = None > + # This *really* doesn't do anything in this case, so > + # override it with something fast & minimal. > + self._finish_start_element = id > + self._parser = None > + self.reset() > + > + def createParser(self): > + """Create a new parser object.""" > + return expat.ParserCreate() > + > + def getParser(self): > + """Return the parser object, creating a new one if needed.""" > + if not self._parser: > + self._parser = self.createParser() > + self._intern_setdefault = self._parser.intern.setdefault > + self._parser.buffer_text = True > + self._parser.ordered_attributes = True > + self._parser.specified_attributes = True > + self.install(self._parser) > + return self._parser > + > + def reset(self): > + """Free all data structures used during DOM construction.""" > + self.document = theDOMImplementation.createDocument( > + EMPTY_NAMESPACE, None, None) > + self.curNode = self.document > + self._elem_info = self.document._elem_info > + self._cdata = False > + > + def install(self, parser): > + """Install the callbacks needed to build the DOM into the > parser.""" > + # This creates circular references! > + parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler > + parser.StartElementHandler = self.first_element_handler > + parser.EndElementHandler = self.end_element_handler > + parser.ProcessingInstructionHandler = self.pi_handler > + if self._options.entities: > + parser.EntityDeclHandler = self.entity_decl_handler > + parser.NotationDeclHandler = self.notation_decl_handler > + if self._options.comments: > + parser.CommentHandler = self.comment_handler > + if self._options.cdata_sections: > + parser.StartCdataSectionHandler = > self.start_cdata_section_handler > + parser.EndCdataSectionHandler = self.end_cdata_section_handler > + parser.CharacterDataHandler = > self.character_data_handler_cdata > + else: > + parser.CharacterDataHandler = self.character_data_handler > + parser.ExternalEntityRefHandler = self.external_entity_ref_handler > + parser.XmlDeclHandler = self.xml_decl_handler > + parser.ElementDeclHandler = self.element_decl_handler > + parser.AttlistDeclHandler = self.attlist_decl_handler > + > + def parseFile(self, file): > + """Parse a document from a file object, returning the document > + node.""" > + parser = self.getParser() > + first_buffer = True > + try: > + while 1: > + buffer = file.read(16*1024) > + if not buffer: > + break > + parser.Parse(buffer, 0) > + if first_buffer and self.document.documentElement: > + self._setup_subset(buffer) > + first_buffer = False > + parser.Parse("", True) > + except ParseEscape: > + pass > + doc = self.document > + self.reset() > + self._parser = None > + return doc > + > + def parseString(self, string): > + """Parse a document from a string, returning the document node.""" > + parser = self.getParser() > + try: > + parser.Parse(string, True) > + self._setup_subset(string) > + except ParseEscape: > + pass > + doc = self.document > + self.reset() > + self._parser = None > + return doc > + > + def _setup_subset(self, buffer): > + """Load the internal subset if there might be one.""" > + if self.document.doctype: > + extractor = InternalSubsetExtractor() > + extractor.parseString(buffer) > + subset = extractor.getSubset() > + self.document.doctype.internalSubset = subset > + > + def start_doctype_decl_handler(self, doctypeName, systemId, publicId, > + has_internal_subset): > + doctype = self.document.implementation.createDocumentType( > + doctypeName, publicId, systemId) > + doctype.ownerDocument = self.document > + _append_child(self.document, doctype) > + self.document.doctype = doctype > + if self._filter and self._filter.acceptNode(doctype) == > FILTER_REJECT: > + self.document.doctype = None > + del self.document.childNodes[-1] > + doctype = None > + self._parser.EntityDeclHandler = None > + self._parser.NotationDeclHandler = None > + if has_internal_subset: > + if doctype is not None: > + doctype.entities._seq = [] > + doctype.notations._seq = [] > + self._parser.CommentHandler = None > + self._parser.ProcessingInstructionHandler = None > + self._parser.EndDoctypeDeclHandler = > self.end_doctype_decl_handler > + > + def end_doctype_decl_handler(self): > + if self._options.comments: > + self._parser.CommentHandler = self.comment_handler > + self._parser.ProcessingInstructionHandler = self.pi_handler > + if not (self._elem_info or self._filter): > + self._finish_end_element = id > + > + def pi_handler(self, target, data): > + node = self.document.createProcessingInstruction(target, data) > + _append_child(self.curNode, node) > + if self._filter and self._filter.acceptNode(node) == > FILTER_REJECT: > + self.curNode.removeChild(node) > + > + def character_data_handler_cdata(self, data): > + childNodes = self.curNode.childNodes > + if self._cdata: > + if ( self._cdata_continue > + and childNodes[-1].nodeType == CDATA_SECTION_NODE): > + childNodes[-1].appendData(data) > + return > + node = self.document.createCDATASection(data) > + self._cdata_continue = True > + elif childNodes and childNodes[-1].nodeType == TEXT_NODE: > + node = childNodes[-1] > + value = node.data + data > + d = node.__dict__ > + d['data'] = d['nodeValue'] = value > + return > + else: > + node = minidom.Text() > + d = node.__dict__ > + d['data'] = d['nodeValue'] = data > + d['ownerDocument'] = self.document > + _append_child(self.curNode, node) > + > + def character_data_handler(self, data): > + childNodes = self.curNode.childNodes > + if childNodes and childNodes[-1].nodeType == TEXT_NODE: > + node = childNodes[-1] > + d = node.__dict__ > + d['data'] = d['nodeValue'] = node.data + data > + return > + node = minidom.Text() > + d = node.__dict__ > + d['data'] = d['nodeValue'] = node.data + data > + d['ownerDocument'] = self.document > + _append_child(self.curNode, node) > + > + def entity_decl_handler(self, entityName, is_parameter_entity, value, > + base, systemId, publicId, notationName): > + if is_parameter_entity: > + # we don't care about parameter entities for the DOM > + return > + if not self._options.entities: > + return > + node = self.document._create_entity(entityName, publicId, > + systemId, notationName) > + if value is not None: > + # internal entity > + # node *should* be readonly, but we'll cheat > + child = self.document.createTextNode(value) > + node.childNodes.append(child) > + self.document.doctype.entities._seq.append(node) > + if self._filter and self._filter.acceptNode(node) == > FILTER_REJECT: > + del self.document.doctype.entities._seq[-1] > + > + def notation_decl_handler(self, notationName, base, systemId, > publicId): > + node = self.document._create_notation(notationName, publicId, > systemId) > + self.document.doctype.notations._seq.append(node) > + if self._filter and self._filter.acceptNode(node) == > FILTER_ACCEPT: > + del self.document.doctype.notations._seq[-1] > + > + def comment_handler(self, data): > + node = self.document.createComment(data) > + _append_child(self.curNode, node) > + if self._filter and self._filter.acceptNode(node) == > FILTER_REJECT: > + self.curNode.removeChild(node) > + > + def start_cdata_section_handler(self): > + self._cdata = True > + self._cdata_continue = False > + > + def end_cdata_section_handler(self): > + self._cdata = False > + self._cdata_continue = False > + > + def external_entity_ref_handler(self, context, base, systemId, > publicId): > + return 1 > + > + def first_element_handler(self, name, attributes): > + if self._filter is None and not self._elem_info: > + self._finish_end_element = id > + self.getParser().StartElementHandler = self.start_element_handler > + self.start_element_handler(name, attributes) > + > + def start_element_handler(self, name, attributes): > + node = self.document.createElement(name) > + _append_child(self.curNode, node) > + self.curNode = node > + > + if attributes: > + for i in range(0, len(attributes), 2): > + a = minidom.Attr(attributes[i], EMPTY_NAMESPACE, > + None, EMPTY_PREFIX) > + value = attributes[i+1] > + d = a.childNodes[0].__dict__ > + d['data'] = d['nodeValue'] = value > + d = a.__dict__ > + d['value'] = d['nodeValue'] = value > + d['ownerDocument'] = self.document > + _set_attribute_node(node, a) > + > + if node is not self.document.documentElement: > + self._finish_start_element(node) > + > + def _finish_start_element(self, node): > + if self._filter: > + # To be general, we'd have to call isSameNode(), but this > + # is sufficient for minidom: > + if node is self.document.documentElement: > + return > + filt = self._filter.startContainer(node) > + if filt == FILTER_REJECT: > + # ignore this node & all descendents > + Rejecter(self) > + elif filt == FILTER_SKIP: > + # ignore this node, but make it's children become > + # children of the parent node > + Skipper(self) > + else: > + return > + self.curNode = node.parentNode > + node.parentNode.removeChild(node) > + node.unlink() > + > + # If this ever changes, Namespaces.end_element_handler() needs to > + # be changed to match. > + # > + def end_element_handler(self, name): > + curNode = self.curNode > + self.curNode = curNode.parentNode > + self._finish_end_element(curNode) > + > + def _finish_end_element(self, curNode): > + info = self._elem_info.get(curNode.tagName) > + if info: > + self._handle_white_text_nodes(curNode, info) > + if self._filter: > + if curNode is self.document.documentElement: > + return > + if self._filter.acceptNode(curNode) == FILTER_REJECT: > + self.curNode.removeChild(curNode) > + curNode.unlink() > + > + def _handle_white_text_nodes(self, node, info): > + if (self._options.whitespace_in_element_content > + or not info.isElementContent()): > + return > + > + # We have element type information and should remove ignorable > + # whitespace; identify for text nodes which contain only > + # whitespace. > + L = [] > + for child in node.childNodes: > + if child.nodeType == TEXT_NODE and not child.data.strip(): > + L.append(child) > + > + # Remove ignorable whitespace from the tree. > + for child in L: > + node.removeChild(child) > + > + def element_decl_handler(self, name, model): > + info = self._elem_info.get(name) > + if info is None: > + self._elem_info[name] = ElementInfo(name, model) > + else: > + assert info._model is None > + info._model = model > + > + def attlist_decl_handler(self, elem, name, type, default, required): > + info = self._elem_info.get(elem) > + if info is None: > + info = ElementInfo(elem) > + self._elem_info[elem] = info > + info._attr_info.append( > + [None, name, None, None, default, 0, type, required]) > + > + def xml_decl_handler(self, version, encoding, standalone): > + self.document.version = version > + self.document.encoding = encoding > + # This is still a little ugly, thanks to the pyexpat API. ;-( > + if standalone >= 0: > + if standalone: > + self.document.standalone = True > + else: > + self.document.standalone = False > + > + > +# Don't include FILTER_INTERRUPT, since that's checked separately > +# where allowed. > +_ALLOWED_FILTER_RETURNS = (FILTER_ACCEPT, FILTER_REJECT, FILTER_SKIP) > + > +class FilterVisibilityController(object): > + """Wrapper around a DOMBuilderFilter which implements the checks > + to make the whatToShow filter attribute work.""" > + > + __slots__ = 'filter', > + > + def __init__(self, filter): > + self.filter = filter > + > + def startContainer(self, node): > + mask = self._nodetype_mask[node.nodeType] > + if self.filter.whatToShow & mask: > + val = self.filter.startContainer(node) > + if val == FILTER_INTERRUPT: > + raise ParseEscape > + if val not in _ALLOWED_FILTER_RETURNS: > + raise ValueError, \ > + "startContainer() returned illegal value: " + > repr(val) > + return val > + else: > + return FILTER_ACCEPT > + > + def acceptNode(self, node): > + mask = self._nodetype_mask[node.nodeType] > + if self.filter.whatToShow & mask: > + val = self.filter.acceptNode(node) > + if val == FILTER_INTERRUPT: > + raise ParseEscape > + if val == FILTER_SKIP: > + # move all child nodes to the parent, and remove this node > + parent = node.parentNode > + for child in node.childNodes[:]: > + parent.appendChild(child) > + # node is handled by the caller > + return FILTER_REJECT > + if val not in _ALLOWED_FILTER_RETURNS: > + raise ValueError, \ > + "acceptNode() returned illegal value: " + repr(val) > + return val > + else: > + return FILTER_ACCEPT > + > + _nodetype_mask = { > + Node.ELEMENT_NODE: NodeFilter.SHOW_ELEMENT, > + Node.ATTRIBUTE_NODE: NodeFilter.SHOW_ATTRIBUTE, > + Node.TEXT_NODE: NodeFilter.SHOW_TEXT, > + Node.CDATA_SECTION_NODE: NodeFilter.SHOW_CDATA_SECTION, > + Node.ENTITY_REFERENCE_NODE: > NodeFilter.SHOW_ENTITY_REFERENCE, > + Node.ENTITY_NODE: NodeFilter.SHOW_ENTITY, > + Node.PROCESSING_INSTRUCTION_NODE: > NodeFilter.SHOW_PROCESSING_INSTRUCTION, > + Node.COMMENT_NODE: NodeFilter.SHOW_COMMENT, > + Node.DOCUMENT_NODE: NodeFilter.SHOW_DOCUMENT, > + Node.DOCUMENT_TYPE_NODE: NodeFilter.SHOW_DOCUMENT_TYPE, > + Node.DOCUMENT_FRAGMENT_NODE: > NodeFilter.SHOW_DOCUMENT_FRAGMENT, > + Node.NOTATION_NODE: NodeFilter.SHOW_NOTATION, > + } > + > + > +class FilterCrutch(object): > + __slots__ = '_builder', '_level', '_old_start', '_old_end' > + > + def __init__(self, builder): > + self._level = 0 > + self._builder = builder > + parser = builder._parser > + self._old_start = parser.StartElementHandler > + self._old_end = parser.EndElementHandler > + parser.StartElementHandler = self.start_element_handler > + parser.EndElementHandler = self.end_element_handler > + > +class Rejecter(FilterCrutch): > + __slots__ = () > + > + def __init__(self, builder): > + FilterCrutch.__init__(self, builder) > + parser = builder._parser > + for name in ("ProcessingInstructionHandler", > + "CommentHandler", > + "CharacterDataHandler", > + "StartCdataSectionHandler", > + "EndCdataSectionHandler", > + "ExternalEntityRefHandler", > + ): > + setattr(parser, name, None) > + > + def start_element_handler(self, *args): > + self._level = self._level + 1 > + > + def end_element_handler(self, *args): > + if self._level == 0: > + # restore the old handlers > + parser = self._builder._parser > + self._builder.install(parser) > + parser.StartElementHandler = self._old_start > + parser.EndElementHandler = self._old_end > + else: > + self._level = self._level - 1 > + > +class Skipper(FilterCrutch): > + __slots__ = () > + > + def start_element_handler(self, *args): > + node = self._builder.curNode > + self._old_start(*args) > + if self._builder.curNode is not node: > + self._level = self._level + 1 > + > + def end_element_handler(self, *args): > + if self._level == 0: > + # We're popping back out of the node we're skipping, so we > + # shouldn't need to do anything but reset the handlers. > + self._builder._parser.StartElementHandler = self._old_start > + self._builder._parser.EndElementHandler = self._old_end > + self._builder = None > + else: > + self._level = self._level - 1 > + self._old_end(*args) > + > + > +# framework document used by the fragment builder. > +# Takes a string for the doctype, subset string, and namespace attrs > string. > + > +_FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID = \ > + "http://xml.python.org/entities/fragment-builder/internal"; > + > +_FRAGMENT_BUILDER_TEMPLATE = ( > + '''\ > +<!DOCTYPE wrapper > + %%s [ > + <!ENTITY fragment-builder-internal > + SYSTEM "%s"> > +%%s > +]> > +<wrapper %%s > +>&fragment-builder-internal;</wrapper>''' > + % _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID) > + > + > +class FragmentBuilder(ExpatBuilder): > + """Builder which constructs document fragments given XML source > + text and a context node. > + > + The context node is expected to provide information about the > + namespace declarations which are in scope at the start of the > + fragment. > + """ > + > + def __init__(self, context, options=None): > + if context.nodeType == DOCUMENT_NODE: > + self.originalDocument = context > + self.context = context > + else: > + self.originalDocument = context.ownerDocument > + self.context = context > + ExpatBuilder.__init__(self, options) > + > + def reset(self): > + ExpatBuilder.reset(self) > + self.fragment = None > + > + def parseFile(self, file): > + """Parse a document fragment from a file object, returning the > + fragment node.""" > + return self.parseString(file.read()) > + > + def parseString(self, string): > + """Parse a document fragment from a string, returning the > + fragment node.""" > + self._source = string > + parser = self.getParser() > + doctype = self.originalDocument.doctype > + ident = "" > + if doctype: > + subset = doctype.internalSubset or self._getDeclarations() > + if doctype.publicId: > + ident = ('PUBLIC "%s" "%s"' > + % (doctype.publicId, doctype.systemId)) > + elif doctype.systemId: > + ident = 'SYSTEM "%s"' % doctype.systemId > + else: > + subset = "" > + nsattrs = self._getNSattrs() # get ns decls from node's ancestors > + document = _FRAGMENT_BUILDER_TEMPLATE % (ident, subset, nsattrs) > + try: > + parser.Parse(document, 1) > + except: > + self.reset() > + raise > + fragment = self.fragment > + self.reset() > +## self._parser = None > + return fragment > + > + def _getDeclarations(self): > + """Re-create the internal subset from the DocumentType node. > + > + This is only needed if we don't already have the > + internalSubset as a string. > + """ > + doctype = self.context.ownerDocument.doctype > + s = "" > + if doctype: > + for i in range(doctype.notations.length): > + notation = doctype.notations.item(i) > + if s: > + s = s + "\n " > + s = "%s<!NOTATION %s" % (s, notation.nodeName) > + if notation.publicId: > + s = '%s PUBLIC "%s"\n "%s">' \ > + % (s, notation.publicId, notation.systemId) > + else: > + s = '%s SYSTEM "%s">' % (s, notation.systemId) > + for i in range(doctype.entities.length): > + entity = doctype.entities.item(i) > + if s: > + s = s + "\n " > + s = "%s<!ENTITY %s" % (s, entity.nodeName) > + if entity.publicId: > + s = '%s PUBLIC "%s"\n "%s"' \ > + % (s, entity.publicId, entity.systemId) > + elif entity.systemId: > + s = '%s SYSTEM "%s"' % (s, entity.systemId) > + else: > + s = '%s "%s"' % (s, entity.firstChild.data) > + if entity.notationName: > + s = "%s NOTATION %s" % (s, entity.notationName) > + s = s + ">" > + return s > + > + def _getNSattrs(self): > + return "" > + > + def external_entity_ref_handler(self, context, base, systemId, > publicId): > + if systemId == _FRAGMENT_BUILDER_INTERNAL_SYSTEM_ID: > + # this entref is the one that we made to put the subtree > + # in; all of our given input is parsed in here. > + old_document = self.document > + old_cur_node = self.curNode > + parser = self._parser.ExternalEntityParserCreate(context) > + # put the real document back, parse into the fragment to > return > + self.document = self.originalDocument > + self.fragment = self.document.createDocumentFragment() > + self.curNode = self.fragment > + try: > + parser.Parse(self._source, 1) > + finally: > + self.curNode = old_cur_node > + self.document = old_document > + self._source = None > + return -1 > + else: > + return ExpatBuilder.external_entity_ref_handler( > + self, context, base, systemId, publicId) > + > + > +class Namespaces: > + """Mix-in class for builders; adds support for namespaces.""" > + > + def _initNamespaces(self): > + # list of (prefix, uri) ns declarations. Namespace attrs are > + # constructed from this and added to the element's attrs. > + self._ns_ordered_prefixes = [] > + > + def createParser(self): > + """Create a new namespace-handling parser.""" > + parser = expat.ParserCreate(namespace_separator=" ") > + parser.namespace_prefixes = True > + return parser > + > + def install(self, parser): > + """Insert the namespace-handlers onto the parser.""" > + ExpatBuilder.install(self, parser) > + if self._options.namespace_declarations: > + parser.StartNamespaceDeclHandler = ( > + self.start_namespace_decl_handler) > + > + def start_namespace_decl_handler(self, prefix, uri): > + """Push this namespace declaration on our storage.""" > + self._ns_ordered_prefixes.append((prefix, uri)) > + > + def start_element_handler(self, name, attributes): > + if ' ' in name: > + uri, localname, prefix, qname = _parse_ns_name(self, name) > + else: > + uri = EMPTY_NAMESPACE > + qname = name > + localname = None > + prefix = EMPTY_PREFIX > + node = minidom.Element(qname, uri, prefix, localname) > + node.ownerDocument = self.document > + _append_child(self.curNode, node) > + self.curNode = node > + > + if self._ns_ordered_prefixes: > + for prefix, uri in self._ns_ordered_prefixes: > + if prefix: > + a = minidom.Attr(_intern(self, 'xmlns:' + prefix), > + XMLNS_NAMESPACE, prefix, "xmlns") > + else: > + a = minidom.Attr("xmlns", XMLNS_NAMESPACE, > + "xmlns", EMPTY_PREFIX) > + d = a.childNodes[0].__dict__ > + d['data'] = d['nodeValue'] = uri > + d = a.__dict__ > + d['value'] = d['nodeValue'] = uri > + d['ownerDocument'] = self.document > + _set_attribute_node(node, a) > + del self._ns_ordered_prefixes[:] > + > + if attributes: > + _attrs = node._attrs > + _attrsNS = node._attrsNS > + for i in range(0, len(attributes), 2): > + aname = attributes[i] > + value = attributes[i+1] > + if ' ' in aname: > + uri, localname, prefix, qname = _parse_ns_name(self, > aname) > + a = minidom.Attr(qname, uri, localname, prefix) > + _attrs[qname] = a > + _attrsNS[(uri, localname)] = a > + else: > + a = minidom.Attr(aname, EMPTY_NAMESPACE, > + aname, EMPTY_PREFIX) > + _attrs[aname] = a > + _attrsNS[(EMPTY_NAMESPACE, aname)] = a > + d = a.childNodes[0].__dict__ > + d['data'] = d['nodeValue'] = value > + d = a.__dict__ > + d['ownerDocument'] = self.document > + d['value'] = d['nodeValue'] = value > + d['ownerElement'] = node > + > + if __debug__: > + # This only adds some asserts to the original > + # end_element_handler(), so we only define this when -O is not > + # used. If changing one, be sure to check the other to see if > + # it needs to be changed as well. > + # > + def end_element_handler(self, name): > + curNode = self.curNode > + if ' ' in name: > + uri, localname, prefix, qname = _parse_ns_name(self, name) > + assert (curNode.namespaceURI == uri > + and curNode.localName == localname > + and curNode.prefix == prefix), \ > + "element stack messed up! (namespace)" > + else: > + assert curNode.nodeName == name, \ > + "element stack messed up - bad nodeName" > + assert curNode.namespaceURI == EMPTY_NAMESPACE, \ > + "element stack messed up - bad namespaceURI" > + self.curNode = curNode.parentNode > + self._finish_end_element(curNode) > + > + > +class ExpatBuilderNS(Namespaces, ExpatBuilder): > + """Document builder that supports namespaces.""" > + > + def reset(self): > + ExpatBuilder.reset(self) > + self._initNamespaces() > + > + > +class FragmentBuilderNS(Namespaces, FragmentBuilder): > + """Fragment builder that supports namespaces.""" > + > + def reset(self): > + FragmentBuilder.reset(self) > + self._initNamespaces() > + > + def _getNSattrs(self): > + """Return string of namespace attributes from this element and > + ancestors.""" > + # XXX This needs to be re-written to walk the ancestors of the > + # context to build up the namespace information from > + # declarations, elements, and attributes found in context. > + # Otherwise we have to store a bunch more data on the DOM > + # (though that *might* be more reliable -- not clear). > + attrs = "" > + context = self.context > + L = [] > + while context: > + if hasattr(context, '_ns_prefix_uri'): > + for prefix, uri in context._ns_prefix_uri.items(): > + # add every new NS decl from context to L and attrs > string > + if prefix in L: > + continue > + L.append(prefix) > + if prefix: > + declname = "xmlns:" + prefix > + else: > + declname = "xmlns" > + if attrs: > + attrs = "%s\n %s='%s'" % (attrs, declname, uri) > + else: > + attrs = " %s='%s'" % (declname, uri) > + context = context.parentNode > + return attrs > + > + > +class ParseEscape(Exception): > + """Exception raised to short-circuit parsing in > InternalSubsetExtractor.""" > + pass > + > +class InternalSubsetExtractor(ExpatBuilder): > + """XML processor which can rip out the internal document type > subset.""" > + > + subset = None > + > + def getSubset(self): > + """Return the internal subset as a string.""" > + return self.subset > + > + def parseFile(self, file): > + try: > + ExpatBuilder.parseFile(self, file) > + except ParseEscape: > + pass > + > + def parseString(self, string): > + try: > + ExpatBuilder.parseString(self, string) > + except ParseEscape: > + pass > + > + def install(self, parser): > + parser.StartDoctypeDeclHandler = self.start_doctype_decl_handler > + parser.StartElementHandler = self.start_element_handler > + > + def start_doctype_decl_handler(self, name, publicId, systemId, > + has_internal_subset): > + if has_internal_subset: > + parser = self.getParser() > + self.subset = [] > + parser.DefaultHandler = self.subset.append > + parser.EndDoctypeDeclHandler = self.end_doctype_decl_handler > + else: > + raise ParseEscape() > + > + def end_doctype_decl_handler(self): > + s = ''.join(self.subset).replace('\r\n', '\n').replace('\r', '\n') > + self.subset = s > + raise ParseEscape() > + > + def start_element_handler(self, name, attrs): > + raise ParseEscape() > + > + > +def parse(file, namespaces=True): > + """Parse a document, returning the resulting Document node. > + > + 'file' may be either a file name or an open file object. > + """ > + if namespaces: > + builder = ExpatBuilderNS() > + else: > + builder = ExpatBuilder() > + > + if isinstance(file, StringTypes): > + fp = open(file, 'rb') > + try: > + result = builder.parseFile(fp) > + finally: > + fp.close() > + else: > + result = builder.parseFile(file) > + return result > + > + > +def parseString(string, namespaces=True): > + """Parse a document from a string, returning the resulting > + Document node. > + """ > + if namespaces: > + builder = ExpatBuilderNS() > + else: > + builder = ExpatBuilder() > + return builder.parseString(string) > + > + > +def parseFragment(file, context, namespaces=True): > + """Parse a fragment of a document, given the context from which it > + was originally extracted. context should be the parent of the > + node(s) which are in the fragment. > + > + 'file' may be either a file name or an open file object. > + """ > + if namespaces: > + builder = FragmentBuilderNS(context) > + else: > + builder = FragmentBuilder(context) > + > + if isinstance(file, StringTypes): > + fp = open(file, 'rb') > + try: > + result = builder.parseFile(fp) > + finally: > + fp.close() > + else: > + result = builder.parseFile(file) > + return result > + > + > +def parseFragmentString(string, context, namespaces=True): > + """Parse a fragment of a document from a string, given the context > + from which it was originally extracted. context should be the > + parent of the node(s) which are in the fragment. > + """ > + if namespaces: > + builder = FragmentBuilderNS(context) > + else: > + builder = FragmentBuilder(context) > + return builder.parseString(string) > + > + > +def makeBuilder(options): > + """Create a builder based on an Options object.""" > + if options.namespaces: > + return ExpatBuilderNS(options) > + else: > + return ExpatBuilder(options) > > diff --git a/addon/globalPlugins/xml2/dom/minicompat.py > b/addon/globalPlugins/xml2/dom/minicompat.py > new file mode 100644 > index 0000000..27299f4 > --- /dev/null > +++ b/addon/globalPlugins/xml2/dom/minicompat.py > @@ -0,0 +1,110 @@ > +"""Python version compatibility support for minidom.""" > + > +# This module should only be imported using "import *". > +# > +# The following names are defined: > +# > +# NodeList -- lightest possible NodeList implementation > +# > +# EmptyNodeList -- lightest possible NodeList that is guaranteed to > +# remain empty (immutable) > +# > +# StringTypes -- tuple of defined string types > +# > +# defproperty -- function used in conjunction with GetattrMagic; > +# using these together is needed to make them work > +# as efficiently as possible in both Python 2.2+ > +# and older versions. For example: > +# > +# class MyClass(GetattrMagic): > +# def _get_myattr(self): > +# return something > +# > +# defproperty(MyClass, "myattr", > +# "return some value") > +# > +# For Python 2.2 and newer, this will construct a > +# property object on the class, which avoids > +# needing to override __getattr__(). It will only > +# work for read-only attributes. > +# > +# For older versions of Python, inheriting from > +# GetattrMagic will use the traditional > +# __getattr__() hackery to achieve the same effect, > +# but less efficiently. > +# > +# defproperty() should be used for each version of > +# the relevant _get_<property>() function. > + > +__all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"] > + > +import xml2.dom > + > +try: > + unicode > +except NameError: > + StringTypes = type(''), > +else: > + StringTypes = type(''), type(unicode('')) > + > + > +class NodeList(list): > + __slots__ = () > + > + def item(self, index): > + if 0 <= index < len(self): > + return self[index] > + > + def _get_length(self): > + return len(self) > + > + def _set_length(self, value): > + raise xml.dom.NoModificationAllowedErr( > + "attempt to modify read-only attribute 'length'") > + > + length = property(_get_length, _set_length, > + doc="The number of nodes in the NodeList.") > + > + def __getstate__(self): > + return list(self) > + > + def __setstate__(self, state): > + self[:] = state > + > + > +class EmptyNodeList(tuple): > + __slots__ = () > + > + def __add__(self, other): > + NL = NodeList() > + NL.extend(other) > + return NL > + > + def __radd__(self, other): > + NL = NodeList() > + NL.extend(other) > + return NL > + > + def item(self, index): > + return None > + > + def _get_length(self): > + return 0 > + > + def _set_length(self, value): > + raise xml.dom.NoModificationAllowedErr( > + "attempt to modify read-only attribute 'length'") > + > + length = property(_get_length, _set_length, > + doc="The number of nodes in the NodeList.") > + > + > +def defproperty(klass, name, doc): > + get = getattr(klass, ("_get_" + name)).im_func > + def set(self, value, name=name): > + raise xml.dom.NoModificationAllowedErr( > + "attempt to modify read-only attribute " + repr(name)) > + assert not hasattr(klass, "_set_" + name), \ > + "expected not to find _set_" + name > + prop = property(get, set, doc=doc) > + setattr(klass, name, prop) > > This diff is so big that we needed to truncate the remainder. > > Repository URL: https://bitbucket.org/nvdaaddonteam/readfeeds/ > > -- > > This is a commit notification from bitbucket.org. You are receiving > this because you have the service enabled, addressing the recipient of > this email. > ---------------------------------------------------------------- > > NVDA add-ons Central: A list for discussing NVDA add-ons > > To post a message, send an email to nvda-addons@xxxxxxxxxxxxx. > > To unsubscribe, send an email with the subject line of "unsubscribe" > (without quotes) to nvda-addons-request@xxxxxxxxxxxxx. > > If you have questions for list moderators, please send a message to > nvda-addons-moderators@xxxxxxxxxxxxx. > > Community addons can be found here: http://addons.nvda-project.org > ---------------------------------------------------------------- NVDA add-ons Central: A list for discussing NVDA add-ons To post a message, send an email to nvda-addons@xxxxxxxxxxxxx. To unsubscribe, send an email with the subject line of "unsubscribe" (without quotes) to nvda-addons-request@xxxxxxxxxxxxx. If you have questions for list moderators, please send a message to nvda-addons-moderators@xxxxxxxxxxxxx. Community addons can be found here: http://addons.nvda-project.org