193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#!/usr/bin/env python
293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
493ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#
593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# Redistribution and use in source and binary forms, with or without
693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# modification, are permitted provided that the following conditions
793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# are met:
893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#
993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# 1. Redistributions of source code must retain the above
1093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#    copyright notice, this list of conditions and the following
1193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#    disclaimer.
1293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# 2. Redistributions in binary form must reproduce the above
1393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#    copyright notice, this list of conditions and the following
1493ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#    disclaimer in the documentation and/or other materials
1593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#    provided with the distribution.
1693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)#
1793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
1893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
2193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
2293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
2393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
2493ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
2693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
2793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)# SUCH DAMAGE.
2993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
3093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)import logging
3193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)import re
3293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
3393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)from webkitpy.common.host import Host
34521d96ec04ace82590870fb04353ec4f82bb150fTorne (Richard Coles)from webkitpy.common.webkit_finder import WebKitFinder
3506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)from HTMLParser import HTMLParser
3693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
3793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
3893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)_log = logging.getLogger(__name__)
3993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
4093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
4106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)def convert_for_webkit(new_path, filename, host=Host()):
4206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    """ Converts a file's |contents| so it will function correctly in its |new_path| in Webkit.
4393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
4406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    Returns the list of modified properties and the modified text if the file was modifed, None otherwise."""
4506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    contents = host.filesystem.read_binary_file(filename)
4606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    converter = _W3CTestConverter(new_path, filename, host)
4706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    if filename.endswith('.css'):
4806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        return converter.add_webkit_prefix_to_unprefixed_properties(contents)
4906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    else:
5006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        converter.feed(contents)
5106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        converter.close()
5206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        return converter.output()
5306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
5406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
5506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)class _W3CTestConverter(HTMLParser):
5606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def __init__(self, new_path, filename, host=Host()):
5706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        HTMLParser.__init__(self)
5806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
5906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self._host = host
6093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        self._filesystem = self._host.filesystem
61521d96ec04ace82590870fb04353ec4f82bb150fTorne (Richard Coles)        self._webkit_root = WebKitFinder(self._filesystem).webkit_base()
6293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
6306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data = []
6406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_properties = []
6506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.in_style_tag = False
6606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.style_data = []
6706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.filename = filename
6806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
6906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        resources_path = self.path_from_webkit_root('LayoutTests', 'resources')
7006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        resources_relpath = self._filesystem.relpath(resources_path, new_path)
71197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch        self.resources_relpath = resources_relpath
7206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
7393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        # These settings might vary between WebKit and Blink
74e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        self._css_property_file = self.path_from_webkit_root('Source', 'core', 'css', 'CSSProperties.in')
7593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
7693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        self.prefixed_properties = self.read_webkit_prefixed_css_property_list()
7793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
7806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.prefixed_properties = self.read_webkit_prefixed_css_property_list()
7923e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        prop_regex = '([\s{]|^)(' + "|".join(prop.replace('-webkit-', '') for prop in self.prefixed_properties) + ')(\s+:|:)'
8023e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        self.prop_re = re.compile(prop_regex)
8123e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch
8206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def output(self):
8306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        return (self.converted_properties, ''.join(self.converted_data))
8406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
8593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)    def path_from_webkit_root(self, *comps):
8693ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        return self._filesystem.abspath(self._filesystem.join(self._webkit_root, *comps))
8793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
8893ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)    def read_webkit_prefixed_css_property_list(self):
8993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        prefixed_properties = []
90f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        unprefixed_properties = set()
9193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
9293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        contents = self._filesystem.read_text_file(self._css_property_file)
9393ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        for line in contents.splitlines():
94e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            if re.match('^(#|//|$)', line):
95f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)                # skip comments and preprocessor directives
96f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)                continue
97e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            prop = line.split()[0]
98e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            # Find properties starting with the -webkit- prefix.
99e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            match = re.match('-webkit-([\w|-]*)', prop)
100e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            if match:
101e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)                prefixed_properties.append(match.group(1))
102e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            else:
103e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)                unprefixed_properties.add(prop.strip())
104f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)
105f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        # Ignore any prefixed properties for which an unprefixed version is supported
106f5e4ad553afbc08dd2e729bb77e937a9a94d5827Torne (Richard Coles)        return [prop for prop in prefixed_properties if prop not in unprefixed_properties]
10793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
10806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def add_webkit_prefix_to_unprefixed_properties(self, text):
10993ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        """ Searches |text| for instances of properties requiring the -webkit- prefix and adds the prefix to them.
11093ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
11193ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        Returns the list of converted properties and the modified text."""
11293ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
11323e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        converted_properties = set()
11423e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        text_chunks = []
11523e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        cur_pos = 0
11623e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        for m in self.prop_re.finditer(text):
11723e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch            text_chunks.extend([text[cur_pos:m.start()], m.group(1), '-webkit-', m.group(2), m.group(3)])
11823e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch            converted_properties.add(m.group(2))
11923e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch            cur_pos = m.end()
12023e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        text_chunks.append(text[cur_pos:])
12123e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch
12223e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch        for prop in converted_properties:
12323e46e0f045bc1935a09565578b448d36cfc5b8cBen Murdoch            _log.info('  converting %s', prop)
12493ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
12593ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)        # FIXME: Handle the JS versions of these properties and GetComputedStyle, too.
12606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        return (converted_properties, ''.join(text_chunks))
12706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
12806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def convert_style_data(self, data):
12906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        converted = self.add_webkit_prefix_to_unprefixed_properties(data)
13006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        if converted[0]:
13106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.converted_properties.extend(list(converted[0]))
13206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        return converted[1]
13306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
13406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def convert_attributes_if_needed(self, tag, attrs):
13506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        converted = self.get_starttag_text()
13606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        if tag in ('script', 'link'):
137197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch            target_attr = 'src'
13806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            if tag != 'script':
139197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                target_attr = 'href'
140197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch            for attr_name, attr_value in attrs:
141197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                if attr_name == target_attr:
142197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                    new_path = re.sub('/resources/testharness',
143197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                                      self.resources_relpath + '/testharness',
144197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                                      attr_value)
145197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                    converted = re.sub(attr_value, new_path, converted)
146197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                    new_path = re.sub('/common/vendor-prefix',
147197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                                      self.resources_relpath + '/vendor-prefix',
148197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                                      attr_value)
149197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                    converted = re.sub(attr_value, new_path, converted)
150197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch
151197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch        for attr_name, attr_value in attrs:
152197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch            if attr_name == 'style':
153197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                new_style = self.convert_style_data(attr_value)
154197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                converted = re.sub(attr_value, new_style, converted)
155197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch            if attr_name == 'class' and 'instructions' in attr_value:
156197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                # Always hide instructions, they're for manual testers.
157197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                converted = re.sub(' style=".*?"', '', converted)
158197021e6b966cfb06891637935ef33fff06433d1Ben Murdoch                converted = re.sub('\>', ' style="display:none">', converted)
15906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
16006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.append(converted)
16106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
16206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_starttag(self, tag, attrs):
16306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        if tag == 'style':
16406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.in_style_tag = True
16506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.convert_attributes_if_needed(tag, attrs)
16606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
16706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_endtag(self, tag):
16806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        if tag == 'style':
16906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.converted_data.append(self.convert_style_data(''.join(self.style_data)))
17006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.in_style_tag = False
17106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.style_data = []
17206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['</', tag, '>'])
17306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
17406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_startendtag(self, tag, attrs):
17506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.convert_attributes_if_needed(tag, attrs)
17606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
17706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_data(self, data):
17806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        if self.in_style_tag:
17906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.style_data.append(data)
18006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        else:
18106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)            self.converted_data.append(data)
18206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
18306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_entityref(self, name):
18406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['&', name, ';'])
18506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
18606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_charref(self, name):
18706f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['&#', name, ';'])
18806f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
18906f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_comment(self, data):
19006f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['<!-- ', data, ' -->'])
19106f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
19206f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_decl(self, decl):
19306f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['<!', decl, '>'])
19406f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)
19506f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)    def handle_pi(self, data):
19606f816c7c76bc45a15e452ade8a34e8af077693eTorne (Richard Coles)        self.converted_data.extend(['<?', data, '>'])
19793ac45cfc74041c8ae536ce58a9534d46db2024eTorne (Richard Coles)
198