| #!/usr/bin/env python |
| |
| # Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions |
| # are met: |
| # |
| # 1. Redistributions of source code must retain the above |
| # copyright notice, this list of conditions and the following |
| # disclaimer. |
| # 2. Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following |
| # disclaimer in the documentation and/or other materials |
| # provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY |
| # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE |
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, |
| # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
| # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF |
| # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| # SUCH DAMAGE. |
| |
| import json |
| import logging |
| import re |
| |
| from webkitpy.common.host import Host |
| from webkitpy.common.webkit_finder import WebKitFinder |
| from HTMLParser import HTMLParser |
| |
| _log = logging.getLogger(__name__) |
| |
| |
| def convert_for_webkit(new_path, filename, reference_support_info, host=Host(), convert_test_harness_links=True): |
| """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit. |
| |
| Returns the list of modified properties and the modified text if the file was modifed, None otherwise.""" |
| contents = host.filesystem.read_binary_file(filename) |
| converter = _W3CTestConverter(new_path, filename, reference_support_info, host, convert_test_harness_links) |
| if filename.endswith('.css'): |
| return converter.add_webkit_prefix_to_unprefixed_properties_and_values(contents) |
| else: |
| converter.feed(contents) |
| converter.close() |
| return converter.output() |
| |
| |
| class _W3CTestConverter(HTMLParser): |
| def __init__(self, new_path, filename, reference_support_info, host=Host(), convert_test_harness_links=True): |
| HTMLParser.__init__(self) |
| |
| self._host = host |
| self._filesystem = self._host.filesystem |
| self._webkit_root = WebKitFinder(self._filesystem).webkit_base() |
| |
| self.converted_data = [] |
| self.converted_properties = [] |
| self.converted_property_values = [] |
| self.in_style_tag = False |
| self.style_data = [] |
| self.filename = filename |
| self.reference_support_info = reference_support_info |
| |
| resources_path = self.path_from_webkit_root('LayoutTests', 'resources') |
| resources_relpath = self._filesystem.relpath(resources_path, new_path) |
| self.new_test_harness_path = resources_relpath |
| self.convert_test_harness_links = convert_test_harness_links |
| |
| # These settings might vary between WebKit and Blink |
| css_property_file = self.path_from_webkit_root('Source', 'WebCore', 'css', 'CSSProperties.json') |
| css_property_value_file = self.path_from_webkit_root('Source', 'WebCore', 'css', 'CSSValueKeywords.in') |
| |
| self.test_harness_re = re.compile('/resources/testharness') |
| |
| self.prefixed_properties = self.read_webkit_prefixed_css_property_list(css_property_file) |
| prop_regex = '([\s{]|^)(' + "|".join(prop.replace('-webkit-', '') for prop in self.prefixed_properties) + ')(\s+:|:)' |
| self.prop_re = re.compile(prop_regex) |
| |
| self.prefixed_property_values = self.legacy_read_webkit_prefixed_css_property_list(css_property_value_file) |
| prop_value_regex = '(:\s*|^\s*)(' + "|".join(value.replace('-webkit-', '') for value in self.prefixed_property_values) + ')(\s*;|\s*}|\s*$)' |
| self.prop_value_re = re.compile(prop_value_regex) |
| |
| def output(self): |
| return (self.converted_properties, self.converted_property_values, ''.join(self.converted_data)) |
| |
| def path_from_webkit_root(self, *comps): |
| return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps)) |
| |
| def read_webkit_prefixed_css_property_list(self, file_name): |
| contents = self._filesystem.read_text_file(file_name) |
| if not contents: |
| return [] |
| properties = json.loads(contents)['properties'] |
| property_names = [] |
| for property_name, property_dict in properties.iteritems(): |
| property_names.append(property_name) |
| if 'codegen-properties' in property_dict: |
| codegen_options = property_dict['codegen-properties'] |
| if 'aliases' in codegen_options: |
| property_names.extend(codegen_options['aliases']) |
| |
| prefixed_properties = [] |
| unprefixed_properties = set() |
| for property_name in property_names: |
| # Find properties starting with the -webkit- prefix. |
| match = re.match('-webkit-([\w|-]*)', property_name) |
| if match: |
| prefixed_properties.append(match.group(1)) |
| else: |
| unprefixed_properties.add(property_name) |
| |
| # Ignore any prefixed properties for which an unprefixed version is supported |
| return [prop for prop in prefixed_properties if prop not in unprefixed_properties] |
| |
| def legacy_read_webkit_prefixed_css_property_list(self, file_name): |
| contents = self._filesystem.read_text_file(file_name) |
| prefixed_properties = [] |
| unprefixed_properties = set() |
| |
| for line in contents.splitlines(): |
| if re.match('^(#|//)', line) or len(line.strip()) == 0: |
| # skip comments and preprocessor directives and empty lines. |
| continue |
| # Property name is always first on the line. |
| property_name = line.split(' ', 1)[0] |
| # Find properties starting with the -webkit- prefix. |
| match = re.match('-webkit-([\w|-]*)', property_name) |
| if match: |
| prefixed_properties.append(match.group(1)) |
| else: |
| unprefixed_properties.add(property_name.strip()) |
| |
| # Ignore any prefixed properties for which an unprefixed version is supported |
| return [prop for prop in prefixed_properties if prop not in unprefixed_properties] |
| |
| def add_webkit_prefix_to_unprefixed_properties_and_values(self, text): |
| """ Searches |text| for instances of properties and values requiring the -webkit- prefix and adds the prefix to them. |
| |
| Returns the list of converted properties, values and the modified text.""" |
| |
| converted_properties = self.add_webkit_prefix_following_regex(text, self.prop_re) |
| converted_property_values = self.add_webkit_prefix_following_regex(converted_properties[1], self.prop_value_re) |
| |
| # FIXME: Handle the JS versions of these properties and values and GetComputedStyle, too. |
| return (converted_properties[0], converted_property_values[0], converted_property_values[1]) |
| |
| def add_webkit_prefix_following_regex(self, text, regex): |
| converted_list = set() |
| text_chunks = [] |
| cur_pos = 0 |
| for m in regex.finditer(text): |
| text_chunks.extend([text[cur_pos:m.start()], m.group(1), '-webkit-', m.group(2), m.group(3)]) |
| converted_list.add(m.group(2)) |
| cur_pos = m.end() |
| text_chunks.append(text[cur_pos:]) |
| |
| for item in converted_list: |
| _log.info(' converting %s', item) |
| |
| return (converted_list, ''.join(text_chunks)) |
| |
| def convert_reference_relpaths(self, text): |
| """ Searches |text| for instances of files in reference_support_info and updates the relative path to be correct for the new ref file location""" |
| converted = text |
| for path in self.reference_support_info['files']: |
| if text.find(path) != -1: |
| # FIXME: This doesn't handle an edge case where simply removing the relative path doesn't work. |
| # See http://webkit.org/b/135677 for details. |
| new_path = re.sub(self.reference_support_info['reference_relpath'], '', path, 1) |
| converted = re.sub(path, new_path, text) |
| |
| return converted |
| |
| def convert_style_data(self, data): |
| converted = self.add_webkit_prefix_to_unprefixed_properties_and_values(data) |
| if converted[0]: |
| self.converted_properties.extend(list(converted[0])) |
| if converted[1]: |
| self.converted_property_values.extend(list(converted[1])) |
| |
| if self.reference_support_info is None or self.reference_support_info == {}: |
| return converted[2] |
| |
| return self.convert_reference_relpaths(converted[2]) |
| |
| def convert_attributes_if_needed(self, tag, attrs): |
| converted = self.get_starttag_text() |
| if self.convert_test_harness_links and tag in ('script', 'link'): |
| attr_name = 'src' |
| if tag != 'script': |
| attr_name = 'href' |
| for attr in attrs: |
| if attr[0] == attr_name: |
| new_path = re.sub(self.test_harness_re, self.new_test_harness_path + '/testharness', attr[1]) |
| converted = re.sub(re.escape(attr[1]), new_path, converted) |
| |
| for attr in attrs: |
| if attr[0] == 'style': |
| new_style = self.convert_style_data(attr[1]) |
| converted = re.sub(re.escape(attr[1]), new_style, converted) |
| |
| src_tags = ('script', 'style', 'img', 'frame', 'iframe', 'input', 'layer', 'textarea', 'video', 'audio') |
| if tag in src_tags and self.reference_support_info is not None and self.reference_support_info != {}: |
| for attr_name, attr_value in attrs: |
| if attr_name == 'src': |
| new_path = self.convert_reference_relpaths(attr_value) |
| converted = re.sub(re.escape(attr_value), new_path, converted) |
| |
| self.converted_data.append(converted) |
| |
| def handle_starttag(self, tag, attrs): |
| if tag == 'style': |
| self.in_style_tag = True |
| self.convert_attributes_if_needed(tag, attrs) |
| |
| def handle_endtag(self, tag): |
| if tag == 'style': |
| self.converted_data.append(self.convert_style_data(''.join(self.style_data))) |
| self.in_style_tag = False |
| self.style_data = [] |
| self.converted_data.extend(['</', tag, '>']) |
| |
| def handle_startendtag(self, tag, attrs): |
| self.convert_attributes_if_needed(tag, attrs) |
| |
| def handle_data(self, data): |
| if self.in_style_tag: |
| self.style_data.append(data) |
| else: |
| self.converted_data.append(data) |
| |
| def handle_entityref(self, name): |
| self.converted_data.extend(['&', name, ';']) |
| |
| def handle_charref(self, name): |
| self.converted_data.extend(['&#', name, ';']) |
| |
| def handle_comment(self, data): |
| self.converted_data.extend(['<!-- ', data, ' -->']) |
| |
| def handle_decl(self, decl): |
| self.converted_data.extend(['<!', decl, '>']) |
| |
| def handle_pi(self, data): |
| self.converted_data.extend(['<?', data, '>']) |