Create a script to import W3C tests
[WebKit-https.git] / Tools / Scripts / webkitpy / w3c / test_converter.py
1 #!/usr/bin/env python
2
3 # Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 #
9 # 1. Redistributions of source code must retain the above
10 #    copyright notice, this list of conditions and the following
11 #    disclaimer.
12 # 2. Redistributions in binary form must reproduce the above
13 #    copyright notice, this list of conditions and the following
14 #    disclaimer in the documentation and/or other materials
15 #    provided with the distribution.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
18 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
21 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22 # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
26 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 # SUCH DAMAGE.
29 import re
30 import os
31
32 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, Tag
33
34 SOURCE_SUBDIR = 'Source'
35 WEBCORE_SUBDIR = 'WebCore'
36 CSS_SUBDIR = 'css'
37 CSS_PROPERTY_NAMES_IN = 'CSSPropertyNames.in'
38 LAYOUT_TESTS_DIRECTORY = 'LayoutTests'
39 RESOURCES_DIRECTORY = 'resources'
40 WEBKIT_ROOT = __file__.split(os.path.sep + 'Tools')[0]
41
42
43 class TestConverter(object):
44
45     def __init__(self):
46         self.prefixed_properties = self.load_prefixed_prop_list()
47
48     def load_prefixed_prop_list(self):
49         """ Reads the current list of CSS properties requiring the -webkit- prefix from Source/WebCore/CSS/CSSPropertyNames.in and stores them in an instance variable """
50
51         prefixed_prop_list = []
52
53         # Open CSSPropertyNames.in to get the current list of things requiring prefixes
54         f = open(os.path.join(os.path.sep, WEBKIT_ROOT, SOURCE_SUBDIR, WEBCORE_SUBDIR,
55                                            CSS_SUBDIR, CSS_PROPERTY_NAMES_IN))
56         lines = f.readlines()
57         for line in lines:
58             # Look for anything with the -webkit- prefix
59             match = re.search('^-webkit-[\w|-]*', line)
60             if match is not None:
61                 # Ignore the ones where both the prefixed and non-prefixed property
62                 # are supported - denoted by -webkit-some-property = some-property
63                 if len(line.split('=')) == 2 and \
64                    line.split('=')[1].strip() in line.split('=')[0].strip():
65                     continue
66                 else:
67                     prefixed_prop_list.append(match.group(0))
68
69         return prefixed_prop_list
70
71     def load_file(self, filename):
72         """ Opens the test file |filename| to convert """
73
74         f = open(filename)
75         contents = f.read()
76         f.close()
77
78         return contents
79
80     def convert_for_webkit(self, new_path, contents=None, filename=None):
81         """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit. Returns the list of prefixed CSS properties and the modified contents."""
82
83         if contents is None and filename is None:
84             return None
85
86         if contents is None:
87             contents = self.load_file(filename)
88
89         # Handle plain old CSS files
90         if filename.endswith('.css'):
91             return self.scrub_unprefixed_props(contents)
92
93         # Otherwise, parse it as if HTML
94         doc = BeautifulSoup(contents)
95         jstest = self.convert_testharness_paths(doc, new_path)
96         converted = self.convert_prefixed_properties(doc)
97
98         if len(converted[0]) > 0 or jstest is True:
99             return converted
100         else:
101             return None
102
103     def convert_testharness_paths(self, doc, new_path):
104         """ Update links to testharness.js in the BeautifulSoup |doc| to point to the copy in |new_path|. Returns whether the document was modified."""
105
106         # Look for the W3C-style path to any testharness files - scripts (.js) or links (.css)
107         pattern = re.compile('/resources/testharness')
108         script_tags = doc.findAll(src=pattern)
109         link_tags = doc.findAll(href=pattern)
110         testharness_tags = script_tags + link_tags
111
112         if len(testharness_tags) != 0:
113
114             # Construct the relative path from the new directory to LayoutTests/resources
115             resources_path = os.path.join(os.path.sep, WEBKIT_ROOT, LAYOUT_TESTS_DIRECTORY, RESOURCES_DIRECTORY)
116             resources_relpath = os.path.relpath(resources_path, new_path)
117
118             for tag in testharness_tags:
119                 # Build a new tag with the correct path
120                 attr = 'src'
121                 if tag.name != 'script':
122                     attr = 'href'
123
124                 # Build a new tag with the correct relative path
125                 old_path = tag[attr]
126                 new_tag = Tag(doc, tag.name, tag.attrs)
127                 new_tag[attr] = re.sub(pattern, resources_relpath + '/testharness', old_path)
128
129                 # Update the doc with the new tag
130                 self.replace_tag(doc, tag, new_tag)
131
132             return True
133         else:
134             return False
135
136     def convert_prefixed_properties(self, doc):
137         """ Searches a BeautifulSoup |doc| for any CSS properties requiring the -webkit- prefix and converts them. Returns the list of converted properties and the modified document as a string """
138
139         converted_props = []
140
141         # Look for inline and document styles
142         inline_styles = doc.findAll(style=re.compile('.*'))
143         style_tags = doc.findAll('style')
144         all_styles = inline_styles + style_tags
145
146         for tag in all_styles:
147
148             # Get the text whether in a style tag or style attribute
149             style_text = ''
150             if tag.name == 'style':
151                 if len(tag.contents) == 0:
152                     continue
153                 else:
154                     style_text = tag.contents[0]
155             else:
156                 style_text = tag['style']
157
158             # Scrub it for props requiring prefixes
159             scrubbed_style = self.scrub_unprefixed_props(style_text)
160
161             # Rewrite tag only if changes were made
162             if len(scrubbed_style[0]) > 0:
163                 converted_props.extend(scrubbed_style[0])
164
165                 # Build a new tag with the modified text
166                 new_tag = Tag(doc, tag.name, tag.attrs)
167                 new_tag.insert(0, scrubbed_style[1])
168
169                 # Update the doc with the new tag
170                 self.replace_tag(doc, tag, new_tag)
171
172         return (converted_props, doc.prettify())
173
174     def scrub_unprefixed_props(self, text):
175         """ Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix. Returns the list of converted properties and the modified string """
176
177         converted_props = []
178
179         # Spin through the whole list of prefixed properties
180         for prefixed_prop in self.prefixed_properties:
181
182             # Pull the prefix off
183             unprefixed_prop = prefixed_prop.replace('-webkit-', '')
184
185             # Look for the various ways it might be in the CSS
186             # Match the the property preceded by either whitespace or left curly brace
187             # or at the beginning of the string (for inline style attribute)
188             pattern = '([\s{]|^)' + unprefixed_prop + '(\s+:|:)'
189             if re.search(pattern, text):
190                 print 'converting ' + unprefixed_prop + ' -> ' + prefixed_prop
191                 converted_props.append(prefixed_prop)
192                 text = re.sub(pattern, prefixed_prop + ':', text)
193
194         # TODO: Handle the JS versions of these properties, too.
195
196         return (converted_props, text)
197
198     def replace_tag(self, doc, old_tag, new_tag):
199         """ Inserts a |new_tag| into a BeautifulSoup |doc| and removes the |old_tag| """
200
201         # Replace the previous tag with the new one
202         index = old_tag.parent.contents.index(old_tag)
203         old_tag.parent.insert(index, new_tag)
204         old_tag.extract()