module PageModelExtensions # this method (and its sub method -- parse_w3_messages) is specific to the # w3 validator's SOAP output. Essentially, this submits the page's content # to the validator and parses the response into a more useful structure for # the templates and controller to use. # # the SOAP interface was chosen because the w3 web interface... # * has relative css commands that cause a locally rendered copy to not # display properly. # * contains relative links which break when rendered locally (confusing the # user) # * the web interface inhibits us dealing directly with the results (which # will be more beneficial in future changes to this extension). # # A fair number of workarounds can be found here due to the fact that the w3's # SOAP interface is not a poor correlation to their web interface (and the way # their validator 'thinks'). We have made suggestions to the w3 and they are # looking to improve their SOAP output. In the meantime, this works ok. def validate_html_markup rendered_page = self.render validator_uri = 'http://validator.w3.org/check' validator_params = {'ss' => 0, # don't show rendered_page (irrelevant for SOAP) 'fragment' => rendered_page, 'output' => 'soap12'} response = Net::HTTP.post_form(URI.parse(validator_uri), validator_params) # Try again if redirected (only one redirect will be handled) response = Net::HTTP.post_form(URI.parse(response['location']), validator_params) if response.is_a?(Net::HTTPRedirection) if response.is_a?(Net::HTTPSuccess) then # parse w3's XML(SOAP) output into something more useful w3_xml_struct = XmlSimple.xml_in(response.body) validation_data = {'source' => rendered_page, 'doctype' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['doctype'][0].to_s, 'charset' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['charset'][0].to_s, 'checked_by' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['checkedby'][0].to_s, 'valid' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['validity'][0].downcase == 'true' ? true : false, 'errorcount' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['errors'][0]['errorcount'][0].to_i, 'warningcount' => w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['warnings'][0]['warningcount'][0].to_i, 'prevalidation_messages' => []} # trim leading '-//W3C//DTD ' and trailing '//EN' from doctype validation_data['doctype'].slice!(/^-\/\/W3C\/\/DTD/i) validation_data['doctype'].slice!(/\/\/[A-Z]{2}$/i) validation_data['doctype'].strip! # parse errors and warnings as stored in w3's SOAP struct into messages validation_data['messages'] = parse_w3_messages(w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['warnings'][0], 'warning', 0) validation_data['messages'] += parse_w3_messages(w3_xml_struct['Body'][0]['markupvalidationresponse'][0]['errors'][0], 'error', 1) # sort messages by line number then column, then type (error, msg, etc). validation_data['messages'] = validation_data['messages'].sort_by { |message| [ message['sortable_line'], message['sortable_col'], message['sortable_type'] ] } # iterate over messages and slice out those that are pre-validation msgs i = 0; while i < validation_data['messages'].length if validation_data['messages'][i]['mid'] =~ /^[Ww][0-9]+/ then validation_data['prevalidation_messages'] << validation_data['messages'].slice!(i) i -= 1 end i += 1 end # mark validation process as completed validation_data['completed'] = true else # some sort of http problem getting the response, validation not complete validation_data = {'completed' => false, 'error_code' => response.code, 'error_code_description' => response.message, 'source' => rendered_page} # add redirection location if applicable validation_data['error_code_description'] += " (redirect: " + response['location'] + ")" if (response.is_a?(Net::HTTPRedirection) && !response['location'].blank?) end return validation_data end private def parse_w3_messages(w3_messages, message_type, sortable_type) messages = [] if w3_messages[message_type + 'count'][0].to_i > 0 then w3_messages[message_type + 'list'][0][message_type].each {|curr_message| messages << {'type' => message_type, 'sortable_type' => sortable_type, #way to force 'warning' to sort before 'error' 'line' => (curr_message['line'][0] unless curr_message['line'].nil?), 'sortable_line' => (curr_message['line'].nil? ? -1 : curr_message['line'][0].to_i), 'col' => (curr_message['col'][0] unless curr_message['col'].nil?), 'sortable_col' => (curr_message['col'].nil? ? -1 : curr_message['col'][0].to_i), 'summary' => (curr_message['message'][0] unless curr_message['message'].nil?), 'mid' => (curr_message['messageid'][0] unless curr_message['messageid'].nil?), 'source' => (curr_message['source'][0].sub(' (curr_message['explanation'][0].sub('