| #!/usr/bin/python |
| # $Id$ |
| |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (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.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import Javascript |
| import urllib |
| import urllib2 |
| import HTMLParser |
| import base64 |
| import re |
| import os |
| |
| # Class that describes answers to pop-up dialog boxes that may occur as a result of clicking a link |
| # or button. If the answer is not found, this is considered a test error, and an exception is thrown! |
| class VirtualDialogAnswers: |
| |
| def __init__( self ): |
| # a dictionary of dialogs, by title, containing the desired answer to click for each |
| self.dialogs = { } |
| |
| def add_dialog_answer( self, dialog_name, answer_button ): |
| self.dialogs[ dialog_name ] = answer_button |
| |
| def get_dialog_answer( self, dialog_name ): |
| try: |
| return self.dialogs[ dialog_name ] |
| except: |
| raise Exception("No answer found for dialog '%s'" % dialog_name) |
| |
| |
| # Base class of all form elements |
| class VirtualFormElement: |
| |
| def __init__( self, form_instance ): |
| self.form_instance = form_instance |
| |
| def get_form( self ): |
| return self.form_instance |
| |
| # Base class of form data elements. Each of these has an element name, and |
| # can contribute data to form submission. |
| class VirtualFormDataElement( VirtualFormElement ): |
| |
| def __init__(self, form_instance, element_name ): |
| VirtualFormElement.__init__( self, form_instance ) |
| self.element_name = element_name |
| |
| # Get the key which uniquely identifies this element in a form. |
| def get_key( self ): |
| return self.element_name |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| pass |
| |
| # Optionally add file tuples to a sequence to be posted |
| def add_files( self, accumulator ): |
| pass |
| |
| # Get the element name |
| def get_name( self ): |
| return self.element_name |
| |
| # Set a property (via javascript) |
| # Property value is a JSObject |
| def set_property( self, property_name, property_value ): |
| raise Exception("Form element '%s' has no such property '%s'" % (self.element_name, property_name)) |
| |
| # Get a property (via javascript). |
| # This returns a JSObject |
| def get_property( self, property_name ): |
| raise Exception("Form element '%s' has no such property '%s'" % (self.element_name, property_name)) |
| |
| |
| # Button base class |
| class VirtualButton( VirtualFormElement ): |
| |
| def __init__( self, form_instance, alt, buttontext ): |
| VirtualFormElement.__init__( self, form_instance ) |
| self.buttontext = buttontext |
| self.alt = alt |
| |
| # Public API |
| |
| def click( self ): |
| # Only derived classes actually do anything |
| pass |
| |
| # Private API |
| |
| def get_key( self ): |
| return self.alt |
| |
| |
| # Basic button |
| class VirtualBasicbutton( VirtualButton ): |
| |
| def __init__( self, form_instance, alt, buttontext, onclick ): |
| VirtualButton.__init__( self, form_instance, alt, buttontext ) |
| self.onclick = onclick |
| |
| |
| def click( self ): |
| # Execute the on-click, if any |
| self.get_form( ).execute_javascript_expression( self.onclick ) |
| |
| |
| # Since submit buttons are both buttons and form data elements, there's |
| # a wrapper class needed so that I can put submit buttons in the button |
| # pool. This avoids the tester needing to know about what kind of button |
| # it is; it can be clicked just like any other button. |
| class VirtualSubmitbuttonWrapper( VirtualButton ): |
| |
| def __init__( self, button_reference ): |
| assert isinstance( button_reference, VirtualSubmitbutton ) |
| VirtualButton.__init__( self, button_reference.get_form( ), |
| button_reference.get_alt( ), "Submit" ) |
| self.button_reference = button_reference |
| |
| def click( self ): |
| # Pass the click off to the actual button |
| self.button_reference.click( ) |
| |
| |
| # Submit button. This is the data element version of the submit button. |
| # It's also wrapped by |
| class VirtualSubmitbutton( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name, value, alt, onclick ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| self.alt = alt |
| self.value = value |
| self.clicked = False |
| self.onclick = onclick |
| |
| def get_alt( self ): |
| return self.alt |
| |
| def click( self ): |
| self.clicked = True |
| # Fire off javascript, or if not there, just submit form |
| result = self.get_form( ).execute_javascript_expression( self.onclick ) |
| if result != None: |
| if result.bool_value( ): |
| self.get_form( ).submit( ) |
| else: |
| self.get_form( ).submit( ) |
| self.clicked = False |
| pass |
| |
| # Get the key which uniquely identifies this element in a form. |
| def get_key( self ): |
| return VirtualFormDataElement.get_key( self ) + "=" + self.value |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| if self.clicked: |
| accumulator.append( ( self.get_name(), self.value ) ) |
| |
| # Hidden |
| class VirtualHiddenField( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name, value ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| assert isinstance( value, unicode ) or isinstance( value, str ) |
| self.value = { value:value } |
| |
| def get_specified_value( self ): |
| return_value = "" |
| for value in self.value.keys(): |
| if return_value == "": |
| return_value = value |
| else: |
| return_value = ";" + value |
| return return_value |
| |
| def add_value( self, new_value ): |
| self.value[ new_value ] = new_value |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| for value in self.value.keys(): |
| accumulator.append( ( self.get_name(), value ) ) |
| |
| # Set a property (via javascript) |
| def set_property( self, property_name, property_value ): |
| if property_name == "value": |
| assert isinstance( property_value, Javascript.JSObject ) |
| new_value = property_value.str_value( ) |
| self.value = { new_value:new_value } |
| else: |
| VirtualFormDataElement.set_property( self, property_name, property_value ) |
| |
| # Get a property (via javascript) |
| def get_property( self, property_name ): |
| if property_name == "type": |
| return Javascript.JSString("hidden") |
| if property_name == "value": |
| return Javascript.JSString( self.get_specified_value() ) |
| else: |
| return VirtualFormDataElement.get_property( self, property_name ) |
| |
| # File |
| class VirtualFileBrowser( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| self.filename = "" |
| self.content_type = None |
| |
| # Public part of interface |
| |
| # Set the upload file |
| def setfile( self, filename, content_type ): |
| assert isinstance( filename, str ) or isinstance( filename, unicode ) |
| assert isinstance( content_type, str ) or isinstance( content_type, unicode ) |
| self.filename = filename |
| self.content_type = content_type |
| |
| # Private part of interface |
| |
| # Optionally add a set of file tuples to a sequence to be posted |
| def add_files( self, accumulator ): |
| # Attempt to do the file upload, unless blank |
| if self.filename != None and len(self.filename) > 0: |
| accumulator.append( ( self.get_name(), self.filename, read_file(self.filename), self.content_type ) ) |
| |
| # Get a property (via javascript) |
| def get_property( self, property_name ): |
| if property_name == "value": |
| return Javascript.JSString( self.filename ) |
| else: |
| return VirtualFormDataElement.get_property( self, property_name ) |
| |
| |
| # Read a specified file entirely into a "string" |
| def read_file( filename ): |
| f = open( filename, "rb" ) |
| try: |
| return f.read() |
| finally: |
| f.close() |
| |
| |
| # Checkbox |
| class VirtualCheckbox( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name, value, selected ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| assert isinstance( value, unicode ) or isinstance( value, str ) |
| self.value = value |
| self.selected = selected |
| self.bodytext = None |
| |
| # Public part of interface |
| |
| # Select this checkbox |
| def select( self ): |
| if self.selected == False: |
| self.selected = True |
| |
| # Deselect this checkbox |
| def deselect( self ): |
| if self.selected: |
| self.selected = False |
| |
| # Private part of interface |
| |
| # Get the key which uniquely identifies this element in a form. |
| def get_key( self ): |
| return VirtualFormDataElement.get_key( self ) + "=" + self.value |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| if self.selected: |
| accumulator.append( ( self.get_name(), self.value ) ) |
| |
| def set_bodytext( self, bodytext ): |
| assert isinstance( bodytext, unicode ) or isinstance( bodytext, str ) |
| self.bodytext = bodytext |
| |
| # Radio |
| class VirtualRadiobutton( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name, value, selected ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| assert isinstance( value, str ) or isinstance( value, unicode ) |
| self.value = value |
| self.selected = selected |
| self.bodytext = None |
| |
| # Public part of interface |
| |
| # Select this radio button |
| def select( self ): |
| if self.selected == False: |
| self.get_form( ).clear_radio_buttons( self.get_name( ) ) |
| self.selected = True |
| |
| # Private part of interface |
| |
| def set_bodytext( self, bodytext ): |
| self.bodytext = bodytext |
| |
| # Deselect this radio button. This happens as a side-effect |
| # of selecting another one that has the same name. |
| def deselect( self ): |
| if self.selected == True: |
| self.selected = False |
| |
| # Get the key which uniquely identifies this element in a form. |
| def get_key( self ): |
| return VirtualFormDataElement.get_key( self ) + "=" + self.value |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| if self.selected: |
| accumulator.append( ( self.get_name(), self.value ) ) |
| |
| # Select box element |
| class SelectboxElement: |
| def __init__( self, value, body_text, is_selected ): |
| self.value = value |
| self.body_text = body_text |
| self.is_selected = is_selected |
| |
| def get_value( self ): |
| return self.value |
| |
| def get_body_text( self ): |
| return self.body_text |
| |
| def check_selected( self ): |
| return self.is_selected |
| |
| def set_selected( self, is_selected ): |
| self.is_selected = is_selected |
| |
| # Javascript object representing an option that's tied back to a VirtualSelectBox element |
| class JSOptionValue (Javascript.JSObject ): |
| |
| def __init__( self, parent ): |
| Javascript.JSObject.__init__( self ) |
| self.parent = parent |
| |
| def get_value( self, member_name ): |
| if member_name == "text": |
| return Javascript.JSString(self.parent.get_body_text()) |
| elif member_name == "value": |
| return Javascript.JSString(self.parent.get_value()) |
| elif member_name == "selected": |
| printval = "no" |
| if self.parent.is_selected: |
| printval = "yes" |
| #print "Checking whether option %s is selected: %s" % (self.parent.value,printval) |
| return Javascript.JSBoolean(self.parent.check_selected()) |
| return Javascript.JSObject.get_value( self, member_name ) |
| |
| def set_value( self, member_name, value ): |
| if member_name == "text": |
| self.parent.set_body_text(value.str_value()) |
| elif member_name == "value": |
| self.parent.set_value(value.str_value()) |
| elif member_name == "selected": |
| self.parent.set_selected(value.bool_value()) |
| else: |
| Javascript.JSObject.set_value( self, member_name, value ) |
| |
| # Javascript object representing a list of options - tied back to a VirtualSelectBox |
| class JSOptionList( Javascript.JSObject ): |
| |
| def __init__( self, parent_select_box ): |
| Javascript.JSObject.__init__( self ) |
| self.parent_select_box = parent_select_box |
| |
| def get_value( self, member_name ): |
| # This object should behave like an array, so interpret the member name to be |
| # an index value |
| index = int(member_name) |
| assert index >= 0 |
| return self.parent_select_box.get_option_object( index ) |
| |
| def set_value( self, member_name, value ): |
| # This object should behave like an array, so interpret the member name to be |
| # an index value |
| index = int(member_name) |
| assert index >= 0 |
| self.parent_select_box.set_option_object( index, value ) |
| |
| # Single/multi select |
| class VirtualSelectbox( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name, multi ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| self.multi = multi |
| self.option_value_list = [ ] |
| |
| # Public api |
| |
| # Select a value (without CTRL button). |
| # This works like a browser in that selecting in this way turns |
| # off all other current selections. |
| def select_value( self, selected_value ): |
| seen_value = False |
| for option in self.option_value_list: |
| if option.get_value() == selected_value: |
| if seen_value: |
| raise Exception("The selectbox value '%s' appears more than once in '%s'" % (selected_value,self.get_name())) |
| seen_value = True |
| option.set_selected( True ) |
| else: |
| option.set_selected( False ) |
| if seen_value == False: |
| raise Exception("The selectbox value '%s' doesn't exist in '%s'" % (selected_value,self.get_name())) |
| |
| # Select a value using a regular expression (without CTRL button) |
| def select_value_regexp( self, selected_value_regexp ): |
| regexp = re.compile( selected_value_regexp, 0 ) |
| seen_value = False |
| for option in self.option_value_list: |
| option_value = option.get_value() |
| mo = regexp.search( option_value ) |
| if mo != None: |
| if seen_value: |
| raise Exception("Selectbox '%s' has more than one value matching '%s'" % (self.get_name(),selected_value_regexp)) |
| seen_value = True |
| option.set_selected( True ) |
| else: |
| option.set_selected( False ) |
| if seen_value == False: |
| raise Exception("Selectbox '%s' does not have a value matching '%s'" % (self.get_name(),selected_value_regexp)) |
| |
| # CTRL-select a value. |
| # For multiselect boxes, this adds a new selection to those already |
| # chosen. For non-multi boxes, it works just like select_value. |
| def multi_select_value( self, selected_value ): |
| if self.multi == False: |
| self.select_value( selected_value ) |
| else: |
| seen_value = False |
| for option in self.option_value_list: |
| if option.get_value() == selected_value: |
| if seen_value: |
| raise Exception("Selectbox '%s' has more than one value matching '%s'" % (self.get_name(),selected_value)) |
| seen_value = True |
| # Toggle |
| option.set_selected( option.check_selected() == False ) |
| if seen_value == False: |
| raise Exception("Selectbox '%s' does not have a value matching '%s'" % (self.get_name(),selected_value)) |
| |
| # Private api |
| |
| # Add a legal selection to the selection list |
| def add_selection( self, value, bodytext, isselected ): |
| self.option_value_list.append(SelectboxElement( value, bodytext, isselected )) |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| for option in self.option_value_list: |
| if option.check_selected( ): |
| accumulator.append( ( self.get_name(), option.get_value() ) ) |
| |
| # Get a property (via javascript) |
| def get_property( self, property_name ): |
| if property_name == "type": |
| if self.multi: |
| return Javascript.JSString("select-multiple") |
| else: |
| return Javascript.JSString("select") |
| elif property_name == "value": |
| value = "" |
| for option in self.option_value_list: |
| if option.check_selected( ): |
| if len(value) > 0: |
| value += ";" |
| value += option.get_value() |
| return Javascript.JSString( value ) |
| elif property_name == "options": |
| # Return a JSArray describing the options objects underlying this selectbox |
| return JSOptionList( self ) |
| elif property_name == "length": |
| # Return as JSNumber describing the length of the array |
| return Javascript.JSNumber(len(self.option_value_list)) |
| elif property_name == "selectedIndex": |
| # Return the first selected index as a JSNumber |
| for index in range(len(self.option_value_list)): |
| if self.option_value_list[index].check_selected( ): |
| return Javascript.JSNumber(index) |
| return Javascript.JSNumber(-1) |
| else: |
| return VirtualFormDataElement.get_property( self, property_name ) |
| |
| # Get an option object |
| def get_option_object( self, index ): |
| assert index < len(self.option_value_list) |
| return JSOptionValue( self.option_value_list[index] ) |
| |
| # Set an option object |
| def set_option_object( self, index, object ): |
| assert isinstance(object,Javascript.JSObject) |
| if isinstance(object,Javascript.JSNull): |
| # It's a delete; remove the specified entry wherever it is found |
| #print "Deleting index %d from %s" % (index,self.get_name()) |
| if index >= len(self.option_value_list): |
| return |
| del self.option_value_list[index] |
| else: |
| # Grab the text and value attributes |
| text = object.get_value( "text" ).str_value() |
| value = object.get_value( "value" ).str_value() |
| #print "Setting index %d for %s to text %s value %s" % (index,self.get_name(),text,value) |
| # Set this at the current index |
| if index >= len(self.option_value_list): |
| self.add_selection( value, text, False ) |
| # Create a new selectbox element |
| element = SelectboxElement( value, text, False ) |
| self.option_value_list[index] = element |
| |
| # Text/password field |
| class VirtualTextarea( VirtualFormDataElement ): |
| |
| def __init__( self, form_instance, name ): |
| VirtualFormDataElement.__init__( self, form_instance, name ) |
| self.textvalue = "" |
| |
| # Public api |
| |
| # Set text value |
| def set_value( self, text_value ): |
| assert isinstance( text_value, unicode ) or isinstance( text_value, str ) |
| self.textvalue = text_value |
| |
| # Private API |
| |
| # Optionally add a set of name/value tuples to a sequence to be posted |
| def add_variables( self, accumulator ): |
| accumulator.append( ( self.get_name( ), self.textvalue ) ) |
| |
| # Set a property (via javascript) |
| def set_property( self, property_name, property_value ): |
| if property_name == "value": |
| assert isinstance( property_value, Javascript.JSObject ) |
| self.textvalue = property_value.str_value( ) |
| else: |
| VirtualFormDataElement.set_property( self, property_name, property_value ) |
| |
| # Get a property (via javascript) |
| def get_property( self, property_name ): |
| if property_name == "value": |
| return Javascript.JSString( self.textvalue ) |
| elif property_name == "focus": |
| return JSFocusMethod( self ) |
| else: |
| return VirtualFormDataElement.get_property( self, property_name ) |
| |
| # Class that describes a virtual form. Each form has an identifier (the form name), plus form elements |
| # that live in the form. |
| class VirtualForm: |
| |
| def __init__( self, window_instance, name, action_url, method ): |
| # These elements all have scrapable data, and are organized by |
| # key (which comes from the element). |
| self.data_elements = { } |
| self.window_instance = window_instance |
| self.form_name = name |
| self.action_url = action_url |
| self.method = method |
| |
| # Public API |
| |
| # Get the form name |
| def get_name( self ): |
| return self.form_name |
| |
| # Find a file browser |
| # Matches data variable name. |
| def find_filebrowser( self, data_name ): |
| try: |
| value = self.data_elements[ data_name ] |
| assert isinstance( value, VirtualFileBrowser ) |
| return value |
| except: |
| raise Exception("Can't find filebrowser %s on form %s for url %s" % (data_name,self.form_name,self.window_instance.get_current_url( ))) |
| |
| |
| # Find a checkbox |
| # Matches checkbox data variable name, and value. |
| # Returns a VirtualCheckbox object, or None if not found. |
| def find_checkbox( self, data_name, value ): |
| key = data_name + "=" + value |
| try: |
| value = self.data_elements[ key ] |
| assert isinstance( value, VirtualCheckbox ) |
| return value |
| except: |
| raise Exception("Can't find checkbox %s:%s on form %s for url %s" % (data_name,value,self.form_name,self.window_instance.get_current_url( ))) |
| |
| # Find a radio button |
| # Matches radio button data variable name, and value. |
| # Returns a VirtualRadiobutton object, or None if not found. |
| def find_radiobutton( self, data_name, value ): |
| key = data_name + "=" + value |
| try: |
| value = self.data_elements[ key ] |
| assert isinstance( value, VirtualRadiobutton ) |
| return value |
| except: |
| raise Exception("Can't find radiobutton %s:%s on form %s for url %s" % (data_name,value,self.form_name,self.window_instance.get_current_url())) |
| |
| # Find a selection box |
| # Matches based on the data variable name alone. |
| # Returns a VirtualSelectbox object, or None if not found. |
| def find_selectbox( self, data_name ): |
| try: |
| value = self.data_elements[ data_name ] |
| assert isinstance( value, VirtualSelectbox ) |
| return value |
| except: |
| raise Exception("Can't find selectbox %s on form %s for url %s" % (data_name,self.form_name,self.window_instance.get_current_url())) |
| |
| # Find a textarea/password field |
| # Matches based on the data variable name alone. |
| # Returns a VirtualTextarea object, or None if not found. |
| def find_textarea( self, data_name ): |
| try: |
| value = self.data_elements[ data_name ] |
| assert isinstance( value, VirtualTextarea ) |
| return value |
| except: |
| raise Exception("Can't find textarea %s on form %s for url %s" % (data_name,self.form_name,self.window_instance.get_current_url())) |
| |
| # Get the action URL |
| def get_action_url( self ): |
| return self.action_url |
| |
| # Set the action URL |
| def set_action_url( self, newurl ): |
| self.action_url = newurl |
| |
| # Private API |
| |
| |
| # Find an element based on its data name. |
| # This will NEVER return checkboxes or radio buttons! |
| def find_element_by_dataname( self, data_name ): |
| return self.data_elements[ data_name ] |
| |
| # Execute javascript expression in the form context. |
| # Returns a JSObject representing the result. |
| def execute_javascript_expression( self, javascript ): |
| return self.window_instance.execute_javascript_expression( javascript ) |
| |
| # Add an element to this form. |
| def add_element( self, element ): |
| assert isinstance( element, VirtualFormDataElement ) |
| self.data_elements[ element.get_key( ) ] = element |
| |
| # This method is called just before a radio button is selected. |
| # It must deselect all radio buttons that share this element name. |
| def clear_radio_buttons( self, element_name ): |
| # Go through all the elements |
| for element_key, element_value in self.data_elements.iteritems( ): |
| if element_name == element_value.get_name( ): |
| assert isinstance( element_value, VirtualRadiobutton ) |
| element_value.deselect( ) |
| |
| # This method does the nuts and bolts of submitting a form - basically, |
| # gathering data from all the form elements and stringing it together into |
| # a list of form variables and their values, then sending it on to the window |
| # (and hence to the browser instance) for posting. |
| def submit( self ): |
| # Go through all elements and scrape up current values, and send those |
| # to the action url |
| |
| # First, go through the elements and gather up a variable and file set |
| variables = [ ] |
| files = [ ] |
| for element_key, element_value in self.data_elements.iteritems( ): |
| element_value.add_variables( variables ) |
| element_value.add_files( files ) |
| |
| # Pass these off to the window |
| self.window_instance.execute_action( self.method, variables, files, self.action_url ) |
| |
| # Class that describes a link in a virtual browser window. |
| class VirtualLink: |
| |
| def __init__( self, window_instance, alt, url, onclick ): |
| self.window_instance = window_instance |
| self.alt = alt |
| self.linktext = None |
| self.onclick = onclick |
| self.url = url |
| |
| # Public part of interface |
| |
| # Click this virtual link |
| def click( self ): |
| result = self.window_instance.execute_javascript_expression( self.onclick ) |
| if result != None: |
| if result.bool_value( ): |
| # Take link |
| self.window_instance.execute_link( self.url ) |
| else: |
| self.window_instance.execute_link( self.url ) |
| |
| # Private part of interface |
| |
| def get_alt( self ): |
| return self.alt |
| |
| def set_bodytext( self, bodytext ): |
| self.linktext = bodytext |
| |
| # Class that describes a virtual browser window. Each virtual window has some set of forms and links, |
| # as well as a set of dialog boxes (which can be popped up due to various actions, and dismissed |
| # by virtual user activity) |
| class VirtualWindow: |
| |
| def __init__( self, browser_instance, window_name, data, parent, current_url ): |
| print "Loading window '%s' with data from url %s" % (window_name, current_url) |
| self.links = { } |
| self.buttons = { } |
| self.forms = { } |
| self.window_name = window_name |
| self.data = data |
| self.parent = parent |
| self.browser_instance = browser_instance |
| self.is_open = True |
| self.jscontext = Javascript.JSScope( enclosing_scope=None ) |
| self.dialog_answers = None |
| self.current_url = current_url |
| |
| # Parse the data |
| parser = VirtualActionParser( self ) |
| parser.feed( data ) |
| parser.close( ) |
| |
| # Now, assert javascript objects into the current scope to permit |
| # Javascript to work. |
| |
| # Need methods for: |
| # confirm( ) |
| # alert( ) |
| # eval( ) |
| self.jscontext.define_value( "confirm", JSConfirmMethod( self ) ) |
| self.jscontext.define_value( "alert", JSAlertMethod( self )) |
| self.jscontext.define_value( "eval", JSEvalMethod( self )) |
| |
| # Need built-in objects for: |
| # document (with form properties and form element properties beneath that). |
| # Also, need the shortcut entered, which is a property that's just the form name. |
| # First, create the document object. |
| jsdocobject = JSDocObject( ) |
| self.jscontext.define_value( "document", jsdocobject ) |
| for form_name, form_object in self.forms.iteritems( ): |
| # Build a javascript object representing the form |
| jsobject = JSFormObject( form_object ) |
| # Add this object to the doc object |
| jsdocobject.add_form( form_name, jsobject ) |
| # Add this object to the main context |
| self.jscontext.define_value( form_name, jsobject ) |
| jswindowobject = JSWindowObject( self ) |
| self.jscontext.define_value( "window", jswindowobject ) |
| |
| # Finally, need built-in "Option" class |
| jsoptionclassdef = JSOptionClassDef( ) |
| self.jscontext.define_value( "Option", jsoptionclassdef ) |
| |
| # Public part of interface |
| |
| # Look for a specific match in the page data, and return the value of the specified group |
| def find_match( self, regular_expression, group=0 ): |
| reobject = re.compile( regular_expression ) |
| mo = reobject.search( self.data ) |
| if mo == None: |
| raise Exception("Pattern %s not found in page %s (%s)" % (regular_expression,self.current_url,self.data)) |
| return mo.group(group) |
| |
| # Look for a specific match in the page data, and return the value of the specified group |
| def find_match_no_newlines( self, regular_expression, group=0 ): |
| reobject = re.compile( regular_expression ) |
| mo = reobject.search( " ".join(self.data.split( )) ) |
| if mo == None: |
| raise Exception("Pattern %s not found in page %s (%s)" % (regular_expression,self.current_url,self.data)) |
| return mo.group(group) |
| |
| # Make sure there is NOT a match of the specified kind. |
| def check_no_match( self, regular_expression ): |
| reobject = re.compile( regular_expression ) |
| mo = reobject.search( self.data ) |
| if mo != None: |
| raise Exception("Pattern %s was erroneously found in page %s (%s)" % (regular_expression,self.current_url,self.data)) |
| |
| # Set the current dialog answers, in case there are virtual dialog popups |
| def set_dialog_answers( self, dialog_answers ): |
| assert dialog_answers == None or isinstance( dialog_answers, VirtualDialogAnswers ) |
| self.dialog_answers = dialog_answers |
| |
| # For all of the operations that refer to form elements, the elements are |
| # referenced by form name and something which identifies the element within |
| # the form, which is element specific. For example, a checkbox is identified |
| # by the text and html between the <input> and </input> tags. This is presumed to be |
| # unique for the form. Since an exact match is often difficult to generate, the |
| # matches are passed in as regular expressions whenever arbitrary HTML needs to be |
| # matched. |
| # |
| # For links, the link text and alt text are used for identification. Regexps are |
| # allowed for the link text matching. |
| |
| # Find a link |
| # Needs to match the alt text, which must be unique on the page. |
| # Returns a VirtualLink object, or None if not found. |
| def find_link( self, alt ): |
| try: |
| return self.links[ alt ] |
| except: |
| raise Exception("Can't find link %s on page %s" % (alt,self.current_url)) |
| |
| # Find a form |
| # Needs to match the form name. Returns a VirtualForm object, or None. |
| def find_form( self, form_name ): |
| try: |
| return self.forms[ form_name ] |
| except: |
| raise Exception("Can't find form %s on page %s" % (form_name,self.current_url)) |
| |
| # Find a button |
| # Matches based on the button's alt text, which |
| # must be unique. Returns a VirtualButton object, or None if not |
| # found. The button may be of many different types (e.g. submit |
| # buttons, plain buttons, etc). |
| def find_button( self, alt ): |
| try: |
| return self.buttons[ alt ] |
| except: |
| raise Exception("Can't find button %s on page %s" % (alt,self.current_url)) |
| |
| # Close this window. This is meant to correspond directly to closing the window |
| # in the browser. |
| def close_window( self ): |
| # This may fire off scripts someday, but today it just marks the window as dead |
| self.is_open = False |
| self.browser_instance.delete_window( self.window_name ) |
| |
| # Get the current url for this window |
| def get_current_url( self ): |
| return self.current_url |
| |
| # Get the data for this window |
| def get_data( self ): |
| return self.data |
| |
| # Private part of interface |
| |
| # Initialize; execute whatever startup scripts and side-effect loading is needed |
| def initialize( self ): |
| # Probably I ought to implement frameset handling at least, but right now even |
| # that is not needed |
| is_open = True |
| |
| # Get the parent window |
| def get_parent_window( self ): |
| return self.parent |
| |
| # Get dialog answers in place |
| def get_dialog_answers( self ): |
| return self.dialog_answers |
| |
| # Check if a symbol exists in the current javascript context |
| def check_exists( self, symbol_name ): |
| try: |
| self.jscontext.get_value( symbol_name ) |
| return True |
| except: |
| return False |
| |
| # Execute javascript expression in the window context. |
| # Returns a JSObject representing the result. |
| def execute_javascript_expression( self, javascript ): |
| # All objects, methods, etc. have already been asserted into the javascript context, |
| # which gives javascript the access it needs to document objects and built-in methods. |
| # So we just need to execute against it. |
| |
| if javascript != None and javascript.lower( ).startswith("javascript:"): |
| tokenstream = Javascript.JSTokenStream( javascript[ len("javascript:") : len(javascript) ] ) |
| return tokenstream.evaluate_expr( self.jscontext, "HTML" ) |
| return None |
| |
| # Do a post or a get into the current window |
| def execute_action( self, method, parameters, files, url ): |
| if self.is_open == False: |
| raise Exception("Cannot execute action %s on already closed window %s" % ( url, self.window_name ) ) |
| # Send the url off to the browser instance to load |
| return self.browser_instance.execute_action( self.window_name, method, parameters, files, self.resolve( url ) ) |
| |
| # Do a get with a preformed url. |
| def execute_link( self, url ): |
| if self.is_open == False: |
| raise Exception("Cannot execute link %s on already closed window %s" % (url,self.window_name) ) |
| return self.browser_instance.execute_link( self.window_name, self.resolve( url ) ) |
| |
| # Add a link |
| def add_link( self, linkobject ): |
| assert isinstance( linkobject, VirtualLink ) |
| self.links[ linkobject.get_alt( ) ] = linkobject |
| |
| # Add a form |
| def add_form( self, formobject ): |
| assert isinstance( formobject, VirtualForm ) |
| self.forms[ formobject.get_name( ) ] = formobject |
| |
| # Add a button |
| def add_button( self, buttonobject ): |
| assert isinstance( buttonobject, VirtualButton ) |
| self.buttons[ buttonobject.get_key( ) ] = buttonobject |
| |
| # Accept javascript definitions etc. |
| def accept_javascript( self, javascript_text ): |
| javascript_text = javascript_text.lstrip( ).rstrip( ) |
| if javascript_text.startswith("<!--"): |
| javascript_text = javascript_text[4:len(javascript_text)] |
| if javascript_text.endswith("//-->"): |
| javascript_text = javascript_text[0:len(javascript_text)-5] |
| jstokens = Javascript.JSTokenStream( javascript_text ) |
| jstokens.evaluate_statement_list( self.jscontext ) |
| |
| # Get the answer to a dialog question. |
| def get_answer( self, question, default_answer ): |
| # Look in the dialog_answers structure for some guidance |
| # MHL |
| return default_answer |
| |
| # Resolve a (potentially relative) url into an absolute url with full qualification |
| def resolve( self, url ): |
| protocol = self.current_url.index("://") + 3 |
| if url.find("://") != -1: |
| return url |
| elif url.startswith("/"): |
| # Relative to domain |
| domain = self.current_url.index("/",protocol) |
| return self.current_url[0:domain] + url |
| else: |
| endguy = self.current_url.rindex("/")+1 |
| return self.current_url[0:endguy] + url |
| |
| # Class that describes a virtual browser instance, with various windows and their associated |
| # alerts/popups |
| class VirtualBrowser: |
| |
| def __init__( self, username=None, password=None, win_host=None ): |
| self.window_set = { } |
| self.username = username |
| self.password = password |
| self.win_host = win_host |
| if win_host == None and username != None: |
| # Set up basic auth |
| self.urllibopener = urllib2.build_opener( urllib2.HTTPHandler ( ) ) |
| elif win_host != None and username != None: |
| # Proxy-based auth |
| # MHL |
| raise Exception("Feature not yet implemented") |
| else: |
| # Use standard opener |
| self.urllibopener = urllib2.build_opener( urllib2.HTTPHandler ( ) ) |
| |
| # Public part of the Virtual Browser interface |
| |
| # Send the main window to a specific URL |
| def load_main_window( self, url, initial_dialog_answers=None ): |
| self.build_window( "", self.fetch_data_with_get( url ), None, url, initial_dialog_answers ) |
| |
| # Find a specific window by name. Use name=None |
| # for main window. Returns a VirtualBrowserWindow |
| # object, or None if the window doesn't exist. |
| def find_window( self, window_name="" ): |
| try: |
| return self.window_set[ window_name ] |
| except: |
| raise Exception("Can't find existing window %s" % window_name) |
| |
| # Private part of the Virtual Browser interface |
| |
| # Delete a window |
| def delete_window( self, window_name ): |
| del self.window_set[ window_name ] |
| |
| # Reload an existing window |
| def reload_window( self, window_name, window_data, full_url ): |
| old_window = self.find_window( window_name ) |
| old_window.close_window( ) |
| self.build_window( window_name, window_data, old_window.get_parent_window( ), full_url, old_window.get_dialog_answers( ) ) |
| |
| # Create a window, with a specific parent and data, and register it. |
| # If the window already exists, it will be replaced. |
| def build_window( self, window_name, window_data, parent_window, current_url, initial_dialog_answers=None ): |
| # Parse the data to create a window, with the specified parent virtual window |
| new_window = VirtualWindow( self, window_name, window_data, parent_window, current_url ) |
| # Put the new window into the window set. This may well replace an existing window. |
| self.window_set[ window_name ] = new_window |
| # Now execute start up stuff from that window (when frames are implemented, this is where they would go) |
| new_window.set_dialog_answers( initial_dialog_answers ) |
| new_window.initialize( ) |
| |
| # Read a url using all the connection parameters, cookies, authentication info etc. available. |
| # Creates a new window object accordingly. |
| def execute_action( self, window_name, method, parameters, files, url ): |
| window_data = None |
| if method == "GET": |
| if len(files) > 0: |
| raise Exception("File controls found in GET submit, for url '%s'" % url) |
| # Need to assemble parameters and tack them onto url |
| parameter_string = urllib.urlencode( parameters ) |
| fullurl = url + "?" + parameter_string |
| # Invoke! |
| window_data = self.fetch_data_with_get( fullurl ) |
| elif method == "POST": |
| if len(files) > 0: |
| raise Exception("File controls found in POST submit, for url '%s'" % url) |
| # Assemble parameters into form post |
| window_data = self.fetch_data_with_post( parameters, url ) |
| elif method == "MULTIPART": |
| # Assemble parameters into multipart form post |
| window_data = self.fetch_data_with_multipart_post( parameters, files, url ) |
| else: |
| raise Exception("Unknown action method: %s" % method) |
| |
| # Create a new window object with the result, and save it |
| self.reload_window( window_name, window_data, url ) |
| |
| # Read a preformed url into a window using get. |
| def execute_link( self, window_name, url ): |
| window_data = self.fetch_data_with_get( url ) |
| self.reload_window( window_name, window_data, url ) |
| |
| def fetch_and_decode( self, req ): |
| f = self.urllibopener.open( req ) |
| fetch_info = f.info() |
| encoding = "iso-8859-1" |
| if fetch_info != None and fetch_info.has_key("Content-type"): |
| content_type = fetch_info["Content-type"] |
| charset_index = content_type.find("charset=") |
| if charset_index != -1: |
| encoding = content_type[charset_index+8:len(content_type)] |
| return f.read( ).decode(encoding) |
| |
| # Read a url with get. Returns the data as a string. |
| def fetch_data_with_get( self, url ): |
| print "Getting url '%s'..." % url |
| req = urllib2.Request( url ) |
| if self.username != None: |
| base64string = base64.encodestring('%s:%s' % (self.username, self.password))[:-1] |
| req.add_header("Authorization", "Basic %s" % base64string) |
| # Add cookies appropriate to domain |
| # MHL - not yet implemented |
| # req.add_header('Referer', 'http://www.python.org/') |
| return self.fetch_and_decode( req ) |
| |
| # Read a url with post. Pass the parameters as an array of ( name, value ) tuples. |
| def fetch_data_with_post( self, parameters, url ): |
| paramstring = urllib.urlencode( parameters, doseq=True ) |
| print "Posting url '%s' with parameters '%s'..." % (url, paramstring) |
| req = urllib2.Request( url, paramstring ) |
| if self.username != None: |
| base64string = base64.encodestring('%s:%s' % (self.username, self.password))[:-1] |
| req.add_header("Authorization", "Basic %s" % base64string) |
| # Add cookies by domain |
| # MHL |
| return self.fetch_and_decode( req ) |
| |
| # Private method to post using multipart forms |
| def fetch_data_with_multipart_post( self, parameters, files, url ): |
| |
| paramstring = urllib.urlencode( parameters, doseq=True ) |
| filecount = 0 |
| if files != None: |
| filecount = len(files) |
| print "Multipart posting url '%s' with parameters '%s' and %d files..." % (url, paramstring, filecount) |
| |
| # Turn URL into protocol, host, and selector |
| urlpieces = url.split("://") |
| protocol = urlpieces[0] |
| uri = urlpieces[1] |
| # Split uri at the first / |
| uripieces = uri.split("/") |
| host = uripieces[0] |
| selector = uri[len(host):len(uri)] |
| |
| import httplib |
| |
| """ |
| Post fields and files to an http host as multipart/form-data. |
| fields is a sequence of (name, value) elements for regular form fields. |
| files is a sequence of (name, filename, value, content_type) elements for data to be uploaded as files |
| Return the server's response page. |
| """ |
| content_type, body = encode_multipart_formdata(parameters, files) |
| if protocol == "http": |
| h = httplib.HTTPConnection(host) |
| elif protocol == "https": |
| h = httplib.HTTPSConnection(host) |
| else: |
| raise Exception("Unknown protocol: %s" % protocol) |
| |
| h.connect() |
| try: |
| # Set the request type and url |
| h.putrequest("POST", selector) |
| |
| # Set the content type and length |
| h.putheader("content-type", content_type) |
| h.putheader("content-length", str(len(body))) |
| |
| # Add cookies by domain |
| # MHL |
| |
| # Add basic auth credentials, if needed. |
| if self.username != None: |
| base64string = base64.encodestring("%s:%s" % (self.username, self.password))[:-1] |
| h.putheader("Authorization", "Basic %s" % base64string) |
| |
| h.endheaders() |
| |
| # Send the body |
| h.send(body) |
| response = h.getresponse() |
| status = response.status |
| headers = response.getheaders() |
| encoding = "iso-8859-1" |
| content_type = response.getheader("Content-type","text/html; charset=iso-8859-1") |
| charset_index = content_type.find("charset=") |
| if charset_index != -1: |
| encoding = content_type[charset_index+8:len(content_type)] |
| |
| value = response.read().decode(encoding) |
| if status != 200: |
| raise Exception("Received an error response %d from url: '%s'" % (status,url) ) |
| return value |
| finally: |
| h.close() |
| |
| # Static method for multipart encoding |
| def encode_multipart_formdata(fields, files): |
| """ |
| fields is a sequence of (name, value) elements for regular form fields. |
| files is a sequence of (name, filename, value, content_type) elements for data to be uploaded as files |
| Return (content_type, body) |
| """ |
| import array |
| body = array.array('B') |
| BOUNDARY = "----------ThIs_Is_tHe_bouNdaRY_$" |
| CRLF = "\r\n" |
| if fields != None: |
| for (key, value) in fields: |
| body.fromstring("--" + BOUNDARY + CRLF) |
| body.fromstring(('Content-Disposition: form-data; name="%s"' % key) + CRLF) |
| body.fromstring("Content-Type: text/plain; charset=utf-8" + CRLF) |
| body.fromstring(CRLF) |
| body.fromstring(value.encode("utf-8")) |
| body.fromstring(CRLF) |
| |
| if files != None: |
| for (key, filename, value, content_type) in files: |
| body.fromstring("--" + BOUNDARY + CRLF) |
| body.fromstring(('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + CRLF) |
| body.fromstring(("Content-Type: %s" % content_type) + CRLF) |
| body.fromstring(CRLF) |
| body.fromstring(value) |
| body.fromstring(CRLF) |
| |
| body.fromstring("--" + BOUNDARY + "--" + CRLF) |
| body.fromstring(CRLF) |
| |
| return "multipart/form-data; boundary=%s" % BOUNDARY, body |
| |
| # Everything below here is not considered public at all |
| |
| # JS object that handles "confirm" method |
| class JSConfirmMethod( Javascript.JSObject ): |
| |
| def __init__( self, window_instance ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( window_instance, VirtualWindow ) |
| self.window_instance = window_instance |
| |
| def call( self, argset, context ): |
| # Check to be sure we have one string argument |
| if len(argset) != 1: |
| raise Exception("confirm method requires one string argument") |
| # Evaluate to string |
| message = argset[0].str_value( ) |
| print "CONFIRM: "+message |
| # Now, decide whether we return true or false. |
| return Javascript.JSBoolean( self.window_instance.get_answer( message, True ) ) |
| |
| # JS object that handles "alert" method |
| class JSAlertMethod( Javascript.JSObject ): |
| |
| def __init__( self, window_instance ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( window_instance, VirtualWindow ) |
| self.window_instance = window_instance |
| |
| def call( self, argset, context ): |
| # Check to be sure we have one string argument |
| if len(argset) != 1: |
| raise Exception("alert method requires one string argument") |
| # Evaluate just to be sure there's no error |
| message = argset[0].str_value( ) |
| print "ALERT: "+message |
| # Always click "OK" |
| return Javascript.JSBoolean( True ) |
| |
| # JS object that handles "eval" method |
| class JSEvalMethod( Javascript.JSObject ): |
| |
| def __init__( self, window_instance ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( window_instance, VirtualWindow ) |
| self.window_instance = window_instance |
| |
| def call( self, argset, context ): |
| # Check to be sure we have one string argument |
| if len(argset) != 1: |
| raise Exception("eval method requires one string argument") |
| # Evaluate to string |
| message = argset[0].str_value( ) |
| # Parse this as javascript |
| tokenstream = Javascript.JSTokenStream( message ) |
| return tokenstream.evaluate_expr( context, "Eval" ) |
| |
| # JS object that handles "submit" method |
| class JSSubmitMethod( Javascript.JSObject ): |
| |
| def __init__ ( self, form_instance ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( form_instance, VirtualForm ) |
| self.form_instance = form_instance |
| |
| def call( self, argset, context ): |
| # Check to be sure we have no arguments |
| if len(argset) != 0: |
| raise Exception("submit method has no arguments") |
| self.form_instance.submit( ) |
| return Javascript.JSBoolean( True ) |
| |
| # Class representing focus method |
| class JSFocusMethod( Javascript.JSObject ): |
| def __init__ ( self, owner ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( owner, VirtualFormDataElement ) |
| self.owner = owner |
| |
| def call( self, argset, context ): |
| print "FOCUS: On field '%s'" % self.owner.get_name( ) |
| return Javascript.JSBoolean( True ) |
| |
| # JS object representing a window in Javascript |
| class JSWindowObject( Javascript.JSObject ): |
| |
| def __init__( self, window_object ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( window_object, VirtualWindow ) |
| self.window_object = window_object |
| |
| def get_value( self, member_name ): |
| # Check to see if there is a js object saved with the |
| # specified name. |
| return Javascript.JSBoolean( self.window_object.check_exists( member_name ) ) |
| |
| def set_value( self, member_name, value ): |
| raise Exception("Cannot set properties of window object") |
| |
| |
| # JS object representing a document in Javascript |
| class JSDocObject( Javascript.JSObject ): |
| |
| def __init__( self ): |
| Javascript.JSObject.__init__( self ) |
| self.forms = { } |
| |
| def add_form( self, form_name, jsobject ): |
| self.forms[ form_name ] = jsobject |
| |
| def get_value( self, member_name ): |
| try: |
| return self.forms[ member_name ] |
| except: |
| return Javascript.JSObject.get_value( self, member_name ) |
| |
| def set_value( self, member_name, value ): |
| raise Exception("Cannot set properties of document object") |
| |
| # Class representing a form in Javascript |
| class JSFormObject( Javascript.JSObject ): |
| |
| def __init__( self, virtual_form_object ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( virtual_form_object, VirtualForm ) |
| self.virtual_form_object = virtual_form_object |
| |
| def get_value( self, member_name ): |
| # Properties and methods available for all forms include the elements of the |
| # forms (as objects), as well as methods. |
| # The only method available right now is submit() |
| if member_name == "submit": |
| return JSSubmitMethod( self.virtual_form_object ) |
| elif member_name == "action": |
| return Javascript.JSString( self.virtual_form_object.get_action_url() ) |
| |
| # Find the element on the form of this name |
| try: |
| form_element = self.virtual_form_object.find_element_by_dataname( member_name ) |
| except: |
| Javascript.JSObject.get_value( self, member_name ) |
| return JSElementObject( form_element ) |
| |
| def set_value( self, member_name, value ): |
| if member_name == "action": |
| self.virtual_form_object.set_action_url( value.str_value() ) |
| return |
| |
| # Can't set anything here |
| raise Exception("Cannot set properties of form object") |
| |
| # Class representing an element in Javascript |
| class JSElementObject( Javascript.JSObject ): |
| |
| def __init__( self, element_object ): |
| Javascript.JSObject.__init__( self ) |
| assert isinstance( element_object, VirtualFormDataElement ) |
| self.element_object = element_object |
| |
| def get_value( self, member_name ): |
| # The object itself knows what its javascript properties are, so call the right |
| # method inside. All properties are currently strings. |
| value = self.element_object.get_property( member_name ) |
| if isinstance( value, Javascript.JSObject ) == False: |
| raise Exception("Property returned from element %s member %s is not a JSObject, it is: %s" % (self.element_object.get_name(), member_name, str(value))) |
| return value |
| |
| def set_value( self, member_name, value ): |
| # The object itself knows what its javascript properties are. Pass them |
| # as strings, though. |
| self.element_object.set_property( member_name, value ) |
| |
| def bool_value( self ): |
| # Return true because the object clearly exists |
| return True |
| |
| class JSOptionClassInstance (Javascript.JSObject ): |
| |
| def __init__( self, text, value, selected=False ): |
| Javascript.JSObject.__init__( self ) |
| self.text = text |
| self.value = value |
| self.selected = Javascript.JSBoolean(selected) |
| |
| def get_value( self, member_name ): |
| if member_name == "text": |
| return self.text |
| elif member_name == "value": |
| return self.value |
| elif member_name == "selected": |
| return self.selected |
| return Javascript.JSObject.get_value( self, member_name ) |
| |
| def set_value( self, member_name, value ): |
| if member_name == "text": |
| self.text = value |
| elif member_name == "value": |
| self.value = value |
| elif member_name == "selected": |
| self.selected = value.bool_value() |
| else: |
| Javascript.JSObject.set_value( self, member_name, value ) |
| |
| class JSOptionClassDef( Javascript.JSObject ): |
| |
| def __init__( self ): |
| Javascript.JSObject.__init__( self ) |
| |
| def construct( self, argset, context ): |
| # We only accept the 2-argument form: (text, value) |
| if len(argset) != 2: |
| raise Exception("Expected two arguments to Option constructor, saw %d" % len(argset)) |
| # Save the two objects |
| return JSOptionClassInstance(argset[0],argset[1]) |
| |
| # This class handles our variety of HTML parsing. |
| # We are really interested only in A HREFs, FORM, INPUT, and SCRIPT tags at this time. |
| class VirtualActionParser( HTMLParser.HTMLParser ): |
| |
| def __init__( self, window_instance ): |
| HTMLParser.HTMLParser.__init__( self ) |
| self.window_instance = window_instance |
| # We do not allow nested forms right now |
| self.current_form_instance = None |
| self.current_selectbox = None |
| self.current_radio = None |
| self.current_checkbox = None |
| self.current_textarea = None |
| self.current_link = None |
| self.current_option_value = None |
| self.current_option_value_selected = None |
| self.current_data = None |
| self.current_comment = None |
| self.current_select_active = False |
| self.current_input_active = False |
| self.current_option_active = False |
| self.current_anchor_active = False |
| self.current_form_active = False |
| self.current_script_active = False |
| self.current_textarea_active = False |
| self.tagstack = [ ] |
| |
| def handle_starttag( self, tag, attributes ): |
| self.tagstack.append( tag ) |
| if tag == "a": |
| self.start_a( attributes ) |
| elif tag == "input": |
| self.start_input( attributes ) |
| elif tag == "option": |
| self.start_option( attributes ) |
| elif tag == "form": |
| self.start_form( attributes ) |
| elif tag == "select": |
| self.start_select( attributes ) |
| elif tag == "script": |
| self.start_script( attributes ) |
| elif tag == "textarea": |
| self.start_textarea( attributes ) |
| |
| def handle_endtag( self, tag ): |
| lastguy = self.tagstack[ len(self.tagstack) - 1 ] |
| if lastguy != tag: |
| raise Exception("Unmatched tag: %s. Found instead: %s" % (lastguy, tag) ) |
| del self.tagstack[ len(self.tagstack) - 1 ] |
| if tag == "a": |
| self.end_a( ) |
| elif tag == "input": |
| self.end_input( ) |
| elif tag == "option": |
| self.end_option( ) |
| elif tag == "form": |
| self.end_form( ) |
| elif tag == "select": |
| self.end_select( ) |
| elif tag == "script": |
| self.end_script( ) |
| elif tag == "textarea": |
| self.end_textarea( ) |
| |
| def start_a( self, attributes ): |
| # We only care about HREF= |
| if self.current_link != None: |
| raise Exception("Anchor within anchor: not allowed") |
| self.current_anchor_active = True |
| dict = make_dictionary( attributes ) |
| try: |
| href = dict[ "href" ] |
| # IE6, 7, and Firefox 3 all do a URL Decode at this point... |
| href = urllib.unquote(href) |
| alt = dict[ "alt" ] |
| onclick = None |
| try: |
| onclick = dict[ "onclick" ] |
| except: |
| if href.lower().startswith("javascript:"): |
| onclick = href |
| href = None |
| self.current_data = "" |
| self.current_link = VirtualLink( self.window_instance, alt, href, onclick ) |
| except: |
| pass |
| |
| def end_a( self ): |
| if self.current_anchor_active == False: |
| raise Exception("Unmatched anchor tag; end without start") |
| self.current_anchor_active == False |
| if self.current_link != None: |
| # Use the saved data as link body |
| self.current_link.set_bodytext( self.current_data ) |
| # Save the link |
| self.window_instance.add_link( self.current_link ) |
| self.current_link = None |
| self.current_data = None |
| |
| def start_form( self, attributes ): |
| if self.current_form_instance != None: |
| raise Exception("Nested forms not allowed") |
| dict = make_dictionary( attributes ) |
| self.current_form_active = True |
| try: |
| action = dict[ "action" ] |
| name = dict[ "name" ] |
| try: |
| method = dict[ "method" ] |
| method = method.upper( ) |
| except: |
| method = "GET" |
| try: |
| type_of_form = dict[ "enctype" ] |
| if type_of_form == "multipart/form-data": |
| multipart = True |
| else: |
| multipart = False |
| except: |
| multipart = False |
| if method == "POST" and multipart == True: |
| method = "MULTIPART" |
| print "Form of type %s detected" % method |
| self.current_form_instance = VirtualForm( self.window_instance, name, action, method ) |
| except: |
| pass |
| |
| def end_form( self ): |
| if self.current_form_active == False: |
| raise Exception("Error, form end without form start") |
| self.current_form_active = False |
| if self.current_form_instance != None: |
| self.window_instance.add_form( self.current_form_instance ) |
| self.current_form_instance = None |
| |
| def start_select( self, attributes ): |
| if self.current_select_active: |
| raise Exception("Error, can't nest selects") |
| if self.current_form_instance == None: |
| raise Exception("Select not legal outside form") |
| self.current_select_active = True |
| dict = make_dictionary( attributes ) |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Illegal select tag [no name attribute]") |
| try: |
| multiple = dict[ "multiple" ] |
| except: |
| multiple = "false" |
| self.current_selectbox = VirtualSelectbox( self.current_form_instance, name, multiple.lower() == "true" ) |
| |
| def end_select( self ): |
| if self.current_select_active == False: |
| raise Exception("Error, no starting select tag") |
| self.current_select_active = False |
| if self.current_selectbox != None: |
| self.current_form_instance.add_element( self.current_selectbox ) |
| self.current_selectbox = None |
| |
| |
| def start_textarea( self, attributes ): |
| if self.current_textarea_active: |
| raise Exception("Error, can't nest textareas") |
| if self.current_form_instance == None: |
| raise Exception("Textarea not legal outside form") |
| self.current_textarea_active = True |
| dict = make_dictionary( attributes ) |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Error, textarea has no name attribute") |
| self.current_textarea = VirtualTextarea( self.current_form_instance, name ) |
| self.current_data = "" |
| |
| def end_textarea( self ): |
| if self.current_textarea_active == False: |
| raise Exception("Error, no starting textarea tag") |
| self.current_textarea_active = False |
| if self.current_textarea != None: |
| self.current_textarea.set_value( self.current_data ) |
| self.current_form_instance.add_element( self.current_textarea ) |
| self.current_textarea = None |
| self.current_data = None |
| |
| def start_input( self, attributes ): |
| if self.current_input_active: |
| raise Exception("Error, can't nest inputs") |
| self.current_input_active = True |
| if self.current_form_instance != None: |
| dict = make_dictionary( attributes ) |
| try: |
| type = dict[ "type" ].lower() |
| except: |
| raise Exception("Illegal input tag [no type attribute]") |
| |
| if type == "button": |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type button must have a value") |
| try: |
| onclick = dict[ "onclick" ] |
| except: |
| raise Exception("Input type button must have an onclick script") |
| try: |
| alt = dict[ "alt" ] |
| self.window_instance.add_button( VirtualBasicbutton( self.current_form_instance, alt, value, onclick ) ) |
| except: |
| pass |
| |
| elif type == "password": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type password must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type password must have value attribute") |
| inst = VirtualTextarea( self.current_form_instance, name ) |
| inst.set_value( value ) |
| self.current_form_instance.add_element( inst ) |
| |
| elif type == "text": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type text must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type text must have value attribute") |
| inst = VirtualTextarea( self.current_form_instance, name ) |
| inst.set_value( value ) |
| self.current_form_instance.add_element( inst ) |
| |
| elif type == "hidden": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type hidden must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type hidden must have value attribute") |
| # Hiddens are special; they act like they can contain multiple values for a given name. |
| try: |
| existing_hidden = self.current_form_instance.find_element_by_dataname(name) |
| existing_hidden.add_value(value) |
| except: |
| self.current_form_instance.add_element( VirtualHiddenField( self.current_form_instance, name, value ) ) |
| |
| elif type == "submit": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type submit must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| value = "Submit" |
| try: |
| onclick = dict[ "onclick" ] |
| except: |
| onclick = None |
| try: |
| alt = dict[ "alt" ] |
| formobject = VirtualSubmitbutton( self.current_form_instance, name, value, alt, onclick ) |
| self.current_form_instance.add_element( formobject ) |
| self.window_instance.add_button( VirtualSubmitbuttonWrapper( formobject ) ) |
| except: |
| pass |
| |
| elif type == "radio": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type radio must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type radio must have value attribute") |
| try: |
| selected = dict[ "checked" ] |
| except: |
| selected = "false" |
| |
| self.current_radio = VirtualRadiobutton( self.current_form_instance, name, value, selected == "true" or selected == "" or selected == "yes" ) |
| self.current_data = "" |
| |
| elif type == "checkbox": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type checkbox must have name attribute") |
| try: |
| value = dict[ "value" ] |
| except: |
| raise Exception("Input type checkbox must have value attribute") |
| try: |
| selected = dict[ "checked" ] |
| except: |
| selected = "false" |
| |
| self.current_checkbox = VirtualCheckbox( self.current_form_instance, name, value, selected == "true" ) |
| self.current_data = "" |
| |
| elif type == "file": |
| try: |
| name = dict[ "name" ] |
| except: |
| raise Exception("Input type file must have name attribute") |
| self.current_form_instance.add_element( VirtualFileBrowser( self.current_form_instance, name ) ) |
| |
| else: |
| raise Exception("Unsupported input tag type: %s" % type) |
| |
| def end_input( self ): |
| if self.current_input_active == False: |
| raise Exception("Error, no starting input tag") |
| self.current_input_active = False |
| if self.current_form_instance: |
| # Action depends on what's set |
| if self.current_checkbox != None: |
| self.current_checkbox.set_bodytext( self.current_data ) |
| self.current_form_instance.add_element( self.current_checkbox ) |
| self.current_checkbox = None |
| elif self.current_radio != None: |
| self.current_radio.set_bodytext( self.current_data ) |
| self.current_form_instance.add_element( self.current_radio ) |
| self.current_radio = None |
| |
| self.current_data = None |
| |
| def start_option( self, attributes ): |
| if self.current_option_active: |
| raise Exception("Error, can't nest options") |
| if self.current_form_instance == None: |
| raise Exception("Option not legal outside form") |
| if self.current_select_active == False: |
| raise Exception("Option not legal outside select") |
| self.current_option_active = True |
| dict = make_dictionary( attributes ) |
| try: |
| self.current_option_value = dict[ "value" ] |
| except: |
| raise Exception("All options must have explicit values") |
| try: |
| self.current_option_value_selected = dict[ "selected" ] |
| except: |
| self.current_option_value_selected = None |
| |
| self.current_data = "" |
| |
| def end_option( self ): |
| if self.current_option_active == False: |
| raise Exception("Error, no starting option tag") |
| self.current_option_active = False |
| if self.current_option_value != None: |
| self.current_selectbox.add_selection( self.current_option_value, self.current_data, self.current_option_value_selected != None ) |
| self.current_option_value = None |
| self.current_option_value_selected = None |
| self.current_data = None |
| |
| def start_script( self, attributes ): |
| if self.current_script_active: |
| raise Exception("Nested scripts not legal") |
| self.current_script_active = True |
| dict = make_dictionary( attributes ) |
| try: |
| type = dict[ "type" ] |
| if type == "text/javascript": |
| self.current_data = "" |
| self.current_comment = "" |
| except: |
| pass |
| |
| def end_script( self ): |
| if self.current_script_active == False: |
| raise Exception("Error, script end without script start") |
| self.current_script_active = False |
| if self.current_comment != None: |
| javascript_text = self.current_data |
| self.current_data = None |
| # Feed the javascript to the JS engine via the window |
| self.window_instance.accept_javascript( javascript_text ) |
| elif self.current_data != None: |
| javascript_text = self.current_comment |
| self.current_comment = None |
| self.window_instance.accept_javascript( javascript_text ) |
| |
| |
| def handle_data( self, data ): |
| if self.current_data != None: |
| self.current_data = self.current_data + data |
| |
| # This is no longer needed; we don't grab stuff from comments, just from <!-- blocks |
| # def handle_comment( self, data ): |
| # if self.current_comment != None: |
| # self.current_comment = self.current_comment + data |
| |
| # Convert a parameter tuple list into a dictionary. |
| # The urllib2.urlencode( ) method probably does this, but that isn't certain, so I'll |
| # not remove this code until I am sure. |
| def make_dictionary( parameters ): |
| post_parameters = { } |
| # If a particular parameter appears more than once, make it be semicolon - separated |
| for parameter, value in parameters: |
| value = decode_attribute(value) |
| try: |
| current_value = post_parameters[ parameter ] |
| post_parameters[ parameter ] = current_value + ";" + value |
| except: |
| post_parameters[ parameter ] = value |
| return post_parameters |
| |
| # Decode HTML-encoded attribute value |
| def decode_attribute( value ): |
| output_value = "" |
| index = 0 |
| while True: |
| new_index = value.find("&",index) |
| if new_index == -1: |
| return output_value + value[index:len(value)] |
| output_value = output_value + value[index:new_index] |
| end_value = value.find(";",new_index) |
| if end_value == -1: |
| index = new_index + 1 |
| output_value = output_value + "&" |
| continue |
| char_description = value[new_index+1:end_value] |
| index = end_value + 1 |
| if char_description.lower() == "amp": |
| output_value = output_value + "&" |
| elif char_description.startswith("#"): |
| value_to_convert = char_description[1:len(char_description)] |
| output_value = output_value + chr(int(value_to_convert)) |
| |
| if __name__ == "__main__": |
| vb = VirtualBrowser( ) |
| vb.load_main_window( "http://mcweb.metacarta.com" ) |