1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import base64
6import os
7import re
8
9
10class Image(object):
11
12  def __init__(self, resource):
13    self.resource = resource
14    self.aliases = []
15
16  @property
17  def relative_path(self):
18    return self.resource.relative_path
19
20  @property
21  def absolute_path(self):
22    return self.resource.absolute_path
23
24  @property
25  def contents(self):
26    return self.resource.contents
27
28
29class ParsedStyleSheet(object):
30
31  def __init__(self, loader, containing_dirname, contents):
32    self.loader = loader
33    self.contents = contents
34    self._images = None
35    self._Load(containing_dirname)
36
37  @property
38  def images(self):
39    return self._images
40
41  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
42    for i in self.images:
43      dependent_filenames.append(i.resource.absolute_path)
44
45  @property
46  def contents_with_inlined_images(self):
47    images_by_url = {}
48    for i in self.images:
49      for a in i.aliases:
50        images_by_url[a] = i
51
52    def InlineUrl(m):
53      url = m.group('url')
54      image = images_by_url[url]
55
56      ext = os.path.splitext(image.absolute_path)[1]
57      data = base64.standard_b64encode(image.contents)
58
59      return 'url(data:image/%s;base64,%s)' % (ext[1:], data)
60
61    # I'm assuming we only have url()'s associated with images
62    return re.sub('url\((?P<quote>"|\'|)(?P<url>[^"\'()]*)(?P=quote)\)',
63                  InlineUrl, self.contents)
64
65  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
66    for i in self.images:
67      dependent_filenames.append(i.resource.absolute_path)
68
69  def _Load(self, containing_dirname):
70    if self.contents.find('@import') != -1:
71      raise Exception('@imports are not supported')
72
73    matches = re.findall(
74        'url\((?:["|\']?)([^"\'()]*)(?:["|\']?)\)',
75        self.contents)
76
77    def resolve_url(url):
78      if os.path.isabs(url):
79        # FIXME: module is used here, but py_vulcanize.module is never imported.
80        # However, py_vulcanize.module cannot be imported since py_vulcanize.module may import
81        # style_sheet, leading to an import loop.
82        raise module.DepsException('URL references must be relative')
83      # URLS are relative to this module's directory
84      abs_path = os.path.abspath(os.path.join(containing_dirname, url))
85      image = self.loader.LoadImage(abs_path)
86      image.aliases.append(url)
87      return image
88
89    self._images = [resolve_url(x) for x in matches]
90
91
92class StyleSheet(object):
93  """Represents a stylesheet resource referenced by a module via the
94  base.requireStylesheet(xxx) directive."""
95
96  def __init__(self, loader, name, resource):
97    self.loader = loader
98    self.name = name
99    self.resource = resource
100    self._parsed_style_sheet = None
101
102  @property
103  def filename(self):
104    return self.resource.absolute_path
105
106  @property
107  def contents(self):
108    return self.resource.contents
109
110  def __repr__(self):
111    return 'StyleSheet(%s)' % self.name
112
113  @property
114  def images(self):
115    self._InitParsedStyleSheetIfNeeded()
116    return self._parsed_style_sheet.images
117
118  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
119    self._InitParsedStyleSheetIfNeeded()
120
121    dependent_filenames.append(self.resource.absolute_path)
122    self._parsed_style_sheet.AppendDirectlyDependentFilenamesTo(
123        dependent_filenames)
124
125  @property
126  def contents_with_inlined_images(self):
127    self._InitParsedStyleSheetIfNeeded()
128    return self._parsed_style_sheet.contents_with_inlined_images
129
130  def load(self):
131    self._InitParsedStyleSheetIfNeeded()
132
133  def _InitParsedStyleSheetIfNeeded(self):
134    if self._parsed_style_sheet:
135      return
136    module_dirname = os.path.dirname(self.resource.absolute_path)
137    self._parsed_style_sheet = ParsedStyleSheet(
138        self.loader, module_dirname, self.contents)
139