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 os
6import re
7
8from tvcm import module
9from tvcm import js_utils
10from tvcm import parse_html_deps
11from tvcm import style_sheet
12
13
14def IsHTMLResourceTheModuleGivenConflictingResourceNames(
15    js_resource, html_resource):  # pylint: disable=unused-argument
16  return 'polymer-element' in html_resource.contents
17
18
19class HTMLModule(module.Module):
20
21  @property
22  def _module_dir_name(self):
23    return os.path.dirname(self.resource.absolute_path)
24
25  def Parse(self):
26    try:
27      parser_results = parse_html_deps.HTMLModuleParser().Parse(self.contents)
28    except Exception, ex:
29      raise Exception('While parsing %s: %s' % (self.name, str(ex)))
30
31    self.dependency_metadata = Parse(self.loader,
32                                     self.name, self._module_dir_name,
33                                     self.isComponent(),
34                                     parser_results)
35    self._parser_results = parser_results
36
37  def Load(self):
38    super(HTMLModule, self).Load()
39
40    reachable_names = set([m.name
41                           for m in self.all_dependent_modules_recursive])
42    if 'tr.exportTo' in self.contents:
43      if 'base.base' not in reachable_names:
44        raise Exception('%s: Does not have a dependency on base' %
45                        os.path.relpath(self.resource.absolute_path))
46
47  def GetTVCMDepsModuleType(self):
48    return 'tvcm.HTML_MODULE_TYPE'
49
50  def AppendJSContentsToFile(self,
51                             f,
52                             use_include_tags_for_scripts,
53                             dir_for_include_tag_root):
54    super(HTMLModule, self).AppendJSContentsToFile(f,
55                                                   use_include_tags_for_scripts,
56                                                   dir_for_include_tag_root)
57    for inline_script in self._parser_results.inline_scripts:
58      if not HasPolymerCall(inline_script.stripped_contents):
59        js = inline_script.contents
60      else:
61        js = GetInlineScriptContentWithPolymerizingApplied(inline_script)
62
63      js = js_utils.EscapeJSIfNeeded(js)
64
65      f.write(js)
66      f.write('\n')
67
68  def AppendHTMLContentsToFile(self, f, ctl, minify=False):
69    super(HTMLModule, self).AppendHTMLContentsToFile(f, ctl)
70
71    ctl.current_module = self
72    try:
73      for piece in self._parser_results.YieldHTMLInPieces(ctl, minify=minify):
74        f.write(piece)
75    finally:
76      ctl.current_module = None
77
78  def HRefToResource(self, href, tag_for_err_msg):
79    return _HRefToResource(self.loader, self.name, self._module_dir_name,
80                           href, tag_for_err_msg)
81
82  def AppendDirectlyDependentFilenamesTo(
83      self, dependent_filenames, include_raw_scripts=True):
84    super(HTMLModule, self).AppendDirectlyDependentFilenamesTo(
85        dependent_filenames, include_raw_scripts)
86    for contents in self._parser_results.inline_stylesheets:
87      module_dirname = os.path.dirname(self.resource.absolute_path)
88      ss = style_sheet.ParsedStyleSheet(
89          self.loader, module_dirname, contents)
90      ss.AppendDirectlyDependentFilenamesTo(dependent_filenames)
91
92
93def GetInlineScriptContentWithPolymerizingApplied(inline_script):
94  polymer_element_name = GetPolymerElementNameFromOpenTags(
95      inline_script.open_tags)
96  if polymer_element_name is None:
97    raise module.DepsException(
98        'Tagless Polymer() call must be made inside a <polymer-element> tag')
99
100  return UpdatePolymerCallsGivenElementName(
101      inline_script.stripped_contents, polymer_element_name)
102
103
104def GetPolymerElementNameFromOpenTags(open_tags):
105  found_tag = None
106  for tag in reversed(open_tags):
107    if tag.tag == 'polymer-element':
108      found_tag = tag
109      break
110
111  if not found_tag:
112    return None
113
114  for attr in found_tag.attrs:
115    if attr[0] == 'name':
116      return attr[1]
117
118  return None
119
120_POLYMER_RE_1 = 'Polymer(\s*?)\((\s*?)\{'
121_POLYMER_RE_2 = 'Polymer(\s*?)\((\s*?)\)'
122
123
124def HasPolymerCall(js):
125  if re.search(_POLYMER_RE_1, js) is not None:
126    return True
127  if re.search(_POLYMER_RE_2, js) is not None:
128    return True
129  return False
130
131
132def UpdatePolymerCallsGivenElementName(js, polymer_element_name):
133  if re.search(_POLYMER_RE_1, js) is not None:
134    return re.sub(_POLYMER_RE_1,
135                  'Polymer\g<1>(\g<2>\'%s\', {' % polymer_element_name,
136                  js, 0, re.DOTALL)
137  if re.search(_POLYMER_RE_2, js) is not None:
138    return re.sub(_POLYMER_RE_2,
139                  'Polymer\g<1>(\g<2>\'%s\')' % polymer_element_name,
140                  js, 0, re.DOTALL)
141  assert False, 'This should never be reached'
142
143
144def _HRefToResource(
145    loader, module_name, module_dir_name, href, tag_for_err_msg):
146  if href[0] == '/':
147    resource = loader.FindResourceGivenRelativePath(
148      os.path.normpath(href[1:]))
149  else:
150    abspath = os.path.normpath(os.path.join(module_dir_name,
151      os.path.normpath(href)))
152    resource = loader.FindResourceGivenAbsolutePath(abspath)
153
154  if not resource:
155    raise module.DepsException(
156        'In %s, the %s cannot be loaded because '
157        'it is not in the search path' % (module_name, tag_for_err_msg))
158  try:
159    resource.contents
160  except:
161    raise module.DepsException('In %s, %s points at a nonexistent file ' % (
162        module_name, tag_for_err_msg))
163  return resource
164
165
166def Parse(loader, module_name, module_dir_name, is_component, parser_results):
167  res = module.ModuleDependencyMetadata()
168  if is_component:
169    return res
170
171  if parser_results.has_decl is False:
172    raise Exception('%s must have <!DOCTYPE html>' % module_name)
173
174  # External script references..
175  for href in parser_results.scripts_external:
176    resource = _HRefToResource(loader, module_name, module_dir_name,
177                               href,
178                               tag_for_err_msg='<script src="%s">' % href)
179    res.dependent_raw_script_relative_paths.append(
180        resource.unix_style_relative_path)
181
182  # External imports. Mostly the same as <script>, but we know its a module.
183  for href in parser_results.imports:
184    if not href.endswith('.html'):
185      raise Exception(
186          'In %s, the <link rel="import" href="%s"> must point at a '
187          'file with an html suffix' % (module_name, href))
188
189    resource = _HRefToResource(
190        loader, module_name, module_dir_name, href,
191        tag_for_err_msg='<link rel="import" href="%s">' % href)
192    res.dependent_module_names.append(resource.name)
193
194  # Validate the in-line scripts.
195  for inline_script in parser_results.inline_scripts:
196    stripped_text = inline_script.stripped_contents
197    try:
198      js_utils.ValidateUsesStrictMode('_', stripped_text)
199    except:
200      raise Exception('%s has an inline script tag that is missing '
201                      'a \'use strict\' directive.' % module_name)
202
203  # Style sheets
204  for href in parser_results.stylesheets:
205    resource = _HRefToResource(
206        loader, module_name, module_dir_name, href,
207        tag_for_err_msg='<link rel="stylesheet" href="%s">' % href)
208    res.style_sheet_names.append(resource.name)
209
210  return res
211