=== added file 'examples/alerts.py'
--- examples/alerts.py 1970-01-01 00:00:00 +0000
+++ examples/alerts.py 2007-06-14 12:04:11 +0000
@@ -0,0 +1,53 @@
+
+""" PyKHTML does not allow alerts and prompts to interfere with the page running -- even more than that, it allows you to handle calls to alert yourself! """
+
+# in case pykhtml isn't already installed
+import sys
+sys.path.append("..")
+
+import pykhtml
+
+page = """
+
+
+
+
+ Alert Example
+
+
+
+
+
+"""
+
+def alertHandler(browser, text):
+ print "alert:", text
+ # if the text of the alert was 'And another...', then
+ # we create another alert to showcase the fact that we
+ # can evaluate javascript dynamically
+ if text == 'And another...':
+ browser.eval("alert('Dynamic alert!')")
+ # else if the text was 'Dynamic alert!' (from the alert
+ # we ran dynamically), then that's the last alert. Stop
+ # PyKHTML
+ elif text == 'Dynamic alert!':
+ pykhtml.stopEventLoop()
+
+
+def main():
+ browser = pykhtml.Browser()
+ # install our function be called when there are
+ # alerts in the JavaScript source code
+ browser.onAlert = pykhtml.partial(alertHandler, browser)
+ # load our markup
+ browser.setHtml(page)
+ #pykhtml.timer(3, pykhtml.stopEventLoop)
+ pykhtml.startEventLoop()
+
+
+if __name__ == "__main__":
+ main()
+
=== modified file 'doc/pykhtml.htm'
--- doc/pykhtml.htm 2007-05-06 15:05:28 +0000
+++ doc/pykhtml.htm 2007-06-14 12:04:11 +0000
@@ -18,9 +18,12 @@
A Browser is the main class you use to navigate around and visit different pages. Have a look at Browser.load and Browser.document to access basic use.
Get a reference to the document (see
dom.Document) for the currently loaded page. It contains all the tasty methods for walking the DOM tree like getElementById / getElementsByTagName, and methods for browsing to other linked pages.
+Evaluate a piece of JavaScript. The 'this' parameter, if specified, of type dom.Node, is the DOM node to be used when the javascript refers to 'this'. The return type should be casted to the appropriate Python type. If not, it will be a Qt QVariant.
Load a webpage in the browser. It takes as parameters the URI of the page to load, and a callable object to call when the page has loaded. This callback will be given the browser object as a reference unless you set Browser.referencelessCallbacks to True.
Browse to a new location. You probably don't want to set this directly as you'll receive no notification when the page has loaded. Have a look at
Browser.load instead.
+Set this to any callable that you want to receive alert messages. The default implementation just does nothing.
If you're going to do something that will inadvertently cause PyKHTML to browse to a new page and you want a function to be called when the page is loaded, set onNextLoad to the function.
+
Set whether callbacks passed to functions such as
Browser.load or
dom.Document.visit will have a reference to this browser object passed as a parameter. Default is True.
Take a screenshot of the current webpage and save it to the given file name. Once the screenshot has been taken and saved, the given callback parameter will be called. You can specify the width (the default is 800) to resize the page to. File type will be determined by extension or by the optional format parameter (one of "PNG", "BMP", "XBM", "XPM", or "JPG"). You can also specify the optional quality parameter, a value from 1-100 (leave as None for default values).
Set the HTML of the browser. Parses the HTML and generates the DOM tree so you can navigate it as usual. As well as the `source` parameter, a `url` parameter allows you to specify a URL with which this source code is linked so that e.g any scripts/images referenced in the HTML will be found.
@@ -38,6 +41,7 @@
Our exception hook that prints out the traceback, powers down the pykhtml engine, and then exits.
Utility function to search for and get the full path of a file in $PATH.
+Stops the event loop immediately, ignoring any pending processing.
=== modified file 'pykhtml/__init__.py'
--- pykhtml/__init__.py 2007-06-12 21:28:10 +0000
+++ pykhtml/__init__.py 2007-06-14 12:04:11 +0000
@@ -183,6 +183,20 @@
_createDialog()
+# QVariant to Python types
+def _variantToPython(variant):
+ return _variantConverters[variant.type()](variant)
+_variantConverters = {
+ qt.QVariant.Invalid: (lambda v: v),
+ qt.QVariant.Bool: (lambda v: bool(v.toBool())),
+ qt.QVariant.Double: (lambda v: v.toDouble()),
+ qt.QVariant.Int: (lambda v: v.toInt()),
+ qt.QVariant.LongLong: (lambda v: v.toLongLong()),
+ qt.QVariant.List: (lambda v: map(_variantToPython, v.toList())),
+ qt.QVariant.String: (lambda v: str(v.toString())),
+}
+
+
class partial:
""" Partial application of parameters. This is used internally but is also very useful with [[Browser.load]] as it allows you to pass data to other functions.
@@ -224,6 +238,10 @@
def stopEventLoop():
""" Stop the event loop and hence exit the scraper """
+ timer(0, stopEventLoopImmediately)
+
+def stopEventLoopImmediately():
+ """ Stops the event loop immediately, ignoring any pending processing """
if _weDisabledKWallet:
_reEnableKWallet()
#dialog.deleteLater()
@@ -336,6 +354,15 @@
self.onNextLoad = callback
self.location = uri
+ def eval(self, script, this=None):
+ """ Evaluate a piece of JavaScript. The 'this' parameter, if specified, of type dom.Node, is the DOM node to be used when the javascript refers to 'this'. The return type should be casted to the appropriate Python type. If not, it will be a Qt QVariant. """
+ if this is None:
+ this = DOM.Node()
+ else:
+ this = this._
+ value = self.part.executeScript(this, script)
+ return _variantToPython(value)
+
def _setOnNextLoad(self, callback):
if self.loadFunction:
self.disconnect(self.part, qt.SIGNAL("docCreated()"). self._slotDocCreated)
@@ -345,8 +372,63 @@
return self.loadFunction
onNextLoad = property(_getOnNextLoad, _setOnNextLoad, None, "If you're going to do something that will inadvertently cause PyKHTML to browse to a new page and you want a function to be called when the page is loaded, set onNextLoad to the function")
+ def _installJavaScriptOverrides(self):
+ """ Passing of alert(...) and prompt(...) back to Python """
+ # overwrite alert/prompt so that they fire events
+ self.eval("""
+ window.alert = function(s) {
+ window._alertEventMessage = s;
+ // basic event type (not related to the UI)
+ var event = document.createEvent('Event');
+ event.initEvent('AlertEvent', true, true);
+ document.dispatchEvent(event);
+ }
+ window.prompt = function(text, defaultValue) {
+ window._promptEventMessage = s;
+ window._promptDefaultValue = s;
+ var event = document.createEvent('Event');
+ event.initEvent('PromptEvent', true, true);
+ document.dispatchEvent(event);
+ }
+ """)
+ # which we can listen to from here
+ node = dom.Node(self.document._d, self)
+ # _handleFoo adds the parameters stored in the window
+ # object accordingly as you cannot pass data via event objects.
+ # A little sucky, but it works
+ node.addEvent("AlertEvent", self._handleAlert)
+ # XX enable prompt when we pass stuff back, add confirm
+ #node.addEvent("PromptEvent", self._handlePrompt)
+
+ def _handleAlert(self, e):
+ message = self.eval("window._alertEventMessage")
+ self.onAlert(message)
+
+ def _handlePrompt(self, e):
+ message = self.eval("window._promptEventMessage")
+ defaultText = self.eval("window._promptDefaultValue")
+ # if there is no default text, it will be a QVariant.
+ # make it None.
+ try:
+ defaultText.isNull()
+ except AttributeError:
+ pass
+ else:
+ if defaultText.isNull():
+ defaultText = None
+ self.onPrompt(message, defaultText)
+
+ def onAlert(self, s):
+ """ Set this to any callable that you want to receive alert messages. The default implementation just does nothing """
+ #print "ALERT:", message
+ pass
+
+ def onPrompt(self, message, defaultText):
+ pass
+
+
def _slotDocCreated(self):
- self.part.executeScript(DOM.Node(), "window.alert = function() {}; window.prompt = function() {}")
+ self._installJavaScriptOverrides()
self.disconnect(self.part, qt.SIGNAL("docCreated()"), self._slotDocCreated)
if not self.loadFunction:
raise AttributeError("No load function callback present")
@@ -355,13 +437,15 @@
# If _passReferenceToCallbacks, bind this browser to the function
if self._passReferenceToCallbacks:
func = partial(func, self)
- # do this so the DOM loads fully. Cast to an Element -- not strictly correct, but we just want to get to addEvent.
+ # do this so the DOM loads fully. Cast to a Node -- not strictly correct, but we just want to get to addEvent.
# XX why not just put addEvent in Node and make Document inherit from Node? Document IS meant to be a Node, after all.
dom.Node(self.document._d, self).addEvent("load", func)
def setHtml(self, source, url=None):
""" Set the HTML of the browser. Parses the HTML and generates the DOM tree so you can navigate it as usual. As well as the `source` parameter, a `url` parameter allows you to specify a URL with which this source code is linked so that e.g any scripts/images referenced in the HTML will be found. """
url = url or kdecore.KURL()
+ # doing this lets _slotDocCreated be loaded
+ self.onNextLoad = _null
self.part.begin(url)
self.part.write(source)
self.part.end()
@@ -376,3 +460,8 @@
""" Get a reference to the document (see [[dom.Document]]) for the currently loaded page. It contains all the tasty methods for walking the DOM tree like getElementById / getElementsByTagName, and methods for browsing to other linked pages. """
return dom.Document(self.part.htmlDocument(), self)
+
+def _null(*args):
+ # null func
+ pass
+
=== modified file 'pykhtml/dom.py'
--- pykhtml/dom.py 2007-05-27 12:29:55 +0000
+++ pykhtml/dom.py 2007-06-14 12:04:11 +0000
@@ -423,10 +423,10 @@
@staticmethod
def remove(event, callback):
- self._funcToListener.remove((event, callback))
+ _CallbackEventListener._funcToListener.remove((event, callback))
@staticmethod
def getCallbackInstance(event, callback):
- return self._funcToListener[(event, callback)]
+ return _CallbackEventListener._funcToListener[(event, callback)]