2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
8 from xml.dom import minidom
9 from grit import lazy_re
10 from grit.format.policy_templates.writers import xml_formatted_writer
13 def GetWriter(config):
14 '''Factory method for creating DocWriter objects.
15 See the constructor of TemplateWriter for description of
18 return DocWriter(['*'], config)
21 class DocWriter(xml_formatted_writer.XMLFormattedWriter):
22 '''Class for generating policy templates in HTML format.
23 The intended use of the generated file is to upload it on
24 http://dev.chromium.org, therefore its format has some limitations:
25 - No HTML and body tags.
26 - Restricted set of element attributes: for example no 'class'.
27 Because of the latter the output is styled using the 'style'
28 attributes of HTML elements. This is supported by the dictionary
29 self._STYLES[] and the method self._AddStyledElement(), they try
30 to mimic the functionality of CSS classes. (But without inheritance.)
32 This class is invoked by PolicyTemplateGenerator to create the HTML
36 def _GetLocalizedMessage(self, msg_id):
37 '''Returns a localized message for this writer.
40 msg_id: The identifier of the message.
43 The localized message.
45 return self.messages['doc_' + msg_id]['text']
47 def _MapListToString(self, item_map, items):
48 '''Creates a comma-separated list.
51 item_map: A dictionary containing all the elements of 'items' as
53 items: A list of arbitrary items.
56 Looks up each item of 'items' in 'item_maps' and concatenates the
57 resulting items into a comma-separated list.
59 return ', '.join([item_map[x] for x in items])
61 def _AddTextWithLinks(self, parent, text):
62 '''Parse a string for URLs and add it to a DOM node with the URLs replaced
66 parent: The DOM node to which the text will be added.
67 text: The string to be added.
69 # Iterate through all the URLs and replace them with links.
72 # Look for the first URL.
73 res = self._url_matcher.search(text)
76 # Calculate positions of the substring of the URL.
80 # Add the text prior to the URL.
81 self.AddText(parent, text[:start])
82 # Add a link for the URL.
83 self.AddElement(parent, 'a', {'href': url}, url)
84 # Drop the part of text that is added.
86 self.AddText(parent, text)
89 def _AddStyledElement(self, parent, name, style_ids, attrs=None, text=None):
90 '''Adds an XML element to a parent, with CSS style-sheets included.
93 parent: The parent DOM node.
94 name: Name of the element to add.
95 style_ids: A list of CSS style strings from self._STYLE[].
96 attrs: Dictionary of attributes for the element.
97 text: Text content for the element.
102 style = ''.join([self._STYLE[x] for x in style_ids])
104 # Apply the style specified by style_ids.
105 attrs['style'] = style + attrs.get('style', '')
106 return self.AddElement(parent, name, attrs, text)
108 def _AddDescription(self, parent, policy):
109 '''Adds a string containing the description of the policy. URLs are
110 replaced with links and the possible choices are enumerated in case
111 of 'string-enum' and 'int-enum' type policies.
114 parent: The DOM node for which the feature list will be added.
115 policy: The data structure of a policy.
117 # Replace URLs with links in the description.
118 self._AddTextWithLinks(parent, policy['desc'])
119 # Add list of enum items.
120 if policy['type'] in ('string-enum', 'int-enum', 'string-enum-list'):
121 ul = self.AddElement(parent, 'ul')
122 for item in policy['items']:
123 if policy['type'] == 'int-enum':
124 value_string = str(item['value'])
126 value_string = '"%s"' % item['value']
128 ul, 'li', {}, '%s = %s' % (value_string, item['caption']))
130 def _AddFeatures(self, parent, policy):
131 '''Adds a string containing the list of supported features of a policy
132 to a DOM node. The text will look like as:
133 Feature_X: Yes, Feature_Y: No
136 parent: The DOM node for which the feature list will be added.
137 policy: The data structure of a policy.
140 # The sorting is to make the order well-defined for testing.
141 keys = policy['features'].keys()
144 key_name = self._FEATURE_MAP[key]
145 if policy['features'][key]:
146 value_name = self._GetLocalizedMessage('supported')
148 value_name = self._GetLocalizedMessage('not_supported')
149 features.append('%s: %s' % (key_name, value_name))
150 self.AddText(parent, ', '.join(features))
152 def _AddListExampleMac(self, parent, policy):
153 '''Adds an example value for Mac of a 'list' policy to a DOM node.
156 parent: The DOM node for which the example will be added.
157 policy: A policy of type 'list', for which the Mac example value
160 example_value = policy['example_value']
161 self.AddElement(parent, 'dt', {}, 'Mac:')
162 mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
164 mac_text = ['<array>']
165 for item in example_value:
166 mac_text.append(' <string>%s</string>' % item)
167 mac_text.append('</array>')
168 self.AddText(mac, '\n'.join(mac_text))
170 def _AddListExampleWindows(self, parent, policy):
171 '''Adds an example value for Windows of a 'list' policy to a DOM node.
174 parent: The DOM node for which the example will be added.
175 policy: A policy of type 'list', for which the Windows example value
178 example_value = policy['example_value']
179 self.AddElement(parent, 'dt', {}, 'Windows:')
180 win = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
183 if self.CanBeRecommended(policy) and not self.CanBeMandatory(policy):
184 key_name = self.config['win_reg_recommended_key_name']
186 key_name = self.config['win_reg_mandatory_key_name']
187 for item in example_value:
189 '%s\\%s\\%d = "%s"' %
190 (key_name, policy['name'], cnt, item))
192 self.AddText(win, '\n'.join(win_text))
194 def _AddListExampleLinux(self, parent, policy):
195 '''Adds an example value for Linux of a 'list' policy to a DOM node.
198 parent: The DOM node for which the example will be added.
199 policy: A policy of type 'list', for which the Linux example value
202 example_value = policy['example_value']
203 self.AddElement(parent, 'dt', {}, 'Linux:')
204 linux = self._AddStyledElement(parent, 'dd', ['.monospace'])
206 for item in example_value:
207 linux_text.append('"%s"' % item)
208 self.AddText(linux, '[%s]' % ', '.join(linux_text))
210 def _AddListExample(self, parent, policy):
211 '''Adds the example value of a 'list' policy to a DOM node. Example output:
215 Software\Policies\Chromium\DisabledPlugins\0 = "Java"
216 Software\Policies\Chromium\DisabledPlugins\1 = "Shockwave Flash"
219 <dd>["Java", "Shockwave Flash"]</dd>
223 <string>Java</string>
224 <string>Shockwave Flash</string>
230 parent: The DOM node for which the example will be added.
231 policy: The data structure of a policy.
233 examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
234 if self.IsPolicySupportedOnPlatform(policy, 'win'):
235 self._AddListExampleWindows(examples, policy)
236 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
237 self._AddListExampleLinux(examples, policy)
238 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
239 self._AddListExampleMac(examples, policy)
241 def _PythonObjectToPlist(self, obj, indent=''):
242 '''Converts a python object to an equivalent XML plist.
244 Returns a list of lines.'''
247 return [ '%s<%s/>' % (indent, 'true' if obj else 'false') ]
248 elif obj_type == int:
249 return [ '%s<integer>%s</integer>' % (indent, obj) ]
250 elif obj_type == str:
251 return [ '%s<string>%s</string>' % (indent, obj) ]
252 elif obj_type == list:
253 result = [ '%s<array>' % indent ]
255 result += self._PythonObjectToPlist(item, indent + ' ')
256 result.append('%s</array>' % indent)
258 elif obj_type == dict:
259 result = [ '%s<dict>' % indent ]
260 for key in sorted(obj.keys()):
261 result.append('%s<key>%s</key>' % (indent + ' ', key))
262 result += self._PythonObjectToPlist(obj[key], indent + ' ')
263 result.append('%s</dict>' % indent)
266 raise Exception('Invalid object to convert: %s' % obj)
268 def _AddDictionaryExampleMac(self, parent, policy):
269 '''Adds an example value for Mac of a 'dict' policy to a DOM node.
272 parent: The DOM node for which the example will be added.
273 policy: A policy of type 'dict', for which the Mac example value
276 example_value = policy['example_value']
277 self.AddElement(parent, 'dt', {}, 'Mac:')
278 mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
279 mac_text = ['<key>%s</key>' % (policy['name'])]
280 mac_text += self._PythonObjectToPlist(example_value)
281 self.AddText(mac, '\n'.join(mac_text))
283 def _AddDictionaryExampleWindows(self, parent, policy):
284 '''Adds an example value for Windows of a 'dict' policy to a DOM node.
287 parent: The DOM node for which the example will be added.
288 policy: A policy of type 'dict', for which the Windows example value
291 self.AddElement(parent, 'dt', {}, 'Windows:')
292 win = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
293 if self.CanBeRecommended(policy) and not self.CanBeMandatory(policy):
294 key_name = self.config['win_reg_recommended_key_name']
296 key_name = self.config['win_reg_mandatory_key_name']
297 example = json.dumps(policy['example_value'])
298 self.AddText(win, '%s\\%s = %s' % (key_name, policy['name'], example))
300 def _AddDictionaryExampleLinux(self, parent, policy):
301 '''Adds an example value for Linux of a 'dict' policy to a DOM node.
304 parent: The DOM node for which the example will be added.
305 policy: A policy of type 'dict', for which the Linux example value
308 self.AddElement(parent, 'dt', {}, 'Linux:')
309 linux = self._AddStyledElement(parent, 'dd', ['.monospace'])
310 example = json.dumps(policy['example_value'])
311 self.AddText(linux, '%s: %s' % (policy['name'], example))
313 def _AddDictionaryExample(self, parent, policy):
314 '''Adds the example value of a 'dict' policy to a DOM node. Example output:
318 Software\Policies\Chromium\ProxySettings = "{ 'ProxyMode': 'direct' }"
321 <dd>"ProxySettings": {
322 "ProxyMode": "direct"
327 <key>ProxySettings</key>
330 <string>direct</string>
336 parent: The DOM node for which the example will be added.
337 policy: The data structure of a policy.
339 examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
340 if self.IsPolicySupportedOnPlatform(policy, 'win'):
341 self._AddDictionaryExampleWindows(examples, policy)
342 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
343 self._AddDictionaryExampleLinux(examples, policy)
344 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
345 self._AddDictionaryExampleMac(examples, policy)
347 def _AddExample(self, parent, policy):
348 '''Adds the HTML DOM representation of the example value of a policy to
349 a DOM node. It is simple text for boolean policies, like
350 '0x00000001 (Windows), true (Linux), <true /> (Mac)' in case of boolean
351 policies, but it may also contain other HTML elements. (See method
355 parent: The DOM node for which the example will be added.
356 policy: The data structure of a policy.
359 Exception: If the type of the policy is unknown or the example value
360 of the policy is out of its expected range.
362 example_value = policy['example_value']
363 policy_type = policy['type']
364 if policy_type == 'main':
366 if self.IsPolicySupportedOnPlatform(policy, 'win'):
367 value = '0x00000001' if example_value else '0x00000000'
368 pieces.append(value + ' (Windows)')
369 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
370 value = 'true' if example_value else 'false'
371 pieces.append(value + ' (Linux)')
372 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
373 value = '<true />' if example_value else '<false />'
374 pieces.append(value + ' (Mac)')
375 self.AddText(parent, ', '.join(pieces))
376 elif policy_type == 'string':
377 self.AddText(parent, '"%s"' % example_value)
378 elif policy_type in ('int', 'int-enum'):
380 if self.IsPolicySupportedOnPlatform(policy, 'win'):
381 pieces.append('0x%08x (Windows)' % example_value)
382 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
383 pieces.append('%d (Linux)' % example_value)
384 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
385 pieces.append('%d (Mac)' % example_value)
386 self.AddText(parent, ', '.join(pieces))
387 elif policy_type == 'string-enum':
388 self.AddText(parent, '"%s"' % (example_value))
389 elif policy_type in ('list', 'string-enum-list'):
390 self._AddListExample(parent, policy)
391 elif policy_type == 'dict':
392 self._AddDictionaryExample(parent, policy)
394 raise Exception('Unknown policy type: ' + policy_type)
396 def _AddPolicyAttribute(self, dl, term_id,
397 definition=None, definition_style=None):
398 '''Adds a term-definition pair to a HTML DOM <dl> node. This method is
399 used by _AddPolicyDetails. Its result will have the form of:
400 <dt style="...">...</dt>
401 <dd style="...">...</dd>
404 dl: The DOM node of the <dl> list.
405 term_id: A key to self._STRINGS[] which specifies the term of the pair.
406 definition: The text of the definition. (Optional.)
407 definition_style: List of references to values self._STYLE[] that specify
408 the CSS stylesheet of the <dd> (definition) element.
411 The DOM node representing the definition <dd> element.
413 # Avoid modifying the default value of definition_style.
414 if definition_style == None:
415 definition_style = []
416 term = self._GetLocalizedMessage(term_id)
417 self._AddStyledElement(dl, 'dt', ['dt'], {}, term)
418 return self._AddStyledElement(dl, 'dd', definition_style, {}, definition)
420 def _AddSupportedOnList(self, parent, supported_on_list):
421 '''Creates a HTML list containing the platforms, products and versions
422 that are specified in the list of supported_on.
425 parent: The DOM node for which the list will be added.
426 supported_on_list: The list of supported products, as a list of
429 ul = self._AddStyledElement(parent, 'ul', ['ul'])
430 for supported_on in supported_on_list:
432 product = supported_on['product']
433 platforms = supported_on['platforms']
434 text.append(self._PRODUCT_MAP[product])
436 self._MapListToString(self._PLATFORM_MAP, platforms))
437 if supported_on['since_version']:
438 since_version = self._GetLocalizedMessage('since_version')
439 text.append(since_version.replace('$6', supported_on['since_version']))
440 if supported_on['until_version']:
441 until_version = self._GetLocalizedMessage('until_version')
442 text.append(until_version.replace('$6', supported_on['until_version']))
443 # Add the list element:
444 self.AddElement(ul, 'li', {}, ' '.join(text))
446 def _AddPolicyDetails(self, parent, policy):
447 '''Adds the list of attributes of a policy to the HTML DOM node parent.
448 It will have the form:
450 <dt>Attribute:</dt><dd>Description</dd>
455 parent: A DOM element for which the list will be added.
456 policy: The data structure of the policy.
459 dl = self.AddElement(parent, 'dl')
460 data_type = self._TYPE_MAP[policy['type']]
461 if (self.IsPolicySupportedOnPlatform(policy, 'win') and
462 self._REG_TYPE_MAP.get(policy['type'], None)):
463 data_type += ' (%s)' % self._REG_TYPE_MAP[policy['type']]
464 self._AddPolicyAttribute(dl, 'data_type', data_type)
465 if policy['type'] != 'external':
466 # All types except 'external' can be set through platform policy.
467 if self.IsPolicySupportedOnPlatform(policy, 'win'):
468 if self.CanBeRecommended(policy) and not self.CanBeMandatory(policy):
469 key_name = self.config['win_reg_recommended_key_name']
471 key_name = self.config['win_reg_mandatory_key_name']
472 self._AddPolicyAttribute(
475 key_name + '\\' + policy['name'],
477 if (self.IsPolicySupportedOnPlatform(policy, 'linux') or
478 self.IsPolicySupportedOnPlatform(policy, 'mac')):
479 self._AddPolicyAttribute(
481 'mac_linux_pref_name',
484 dd = self._AddPolicyAttribute(dl, 'supported_on')
485 self._AddSupportedOnList(dd, policy['supported_on'])
486 dd = self._AddPolicyAttribute(dl, 'supported_features')
487 self._AddFeatures(dd, policy)
488 dd = self._AddPolicyAttribute(dl, 'description')
489 self._AddDescription(dd, policy)
490 if (self.IsPolicySupportedOnPlatform(policy, 'win') or
491 self.IsPolicySupportedOnPlatform(policy, 'linux') or
492 self.IsPolicySupportedOnPlatform(policy, 'mac')):
493 # Don't add an example for ChromeOS-only policies.
494 if policy['type'] != 'external':
495 # All types except 'external' can be set through platform policy.
496 dd = self._AddPolicyAttribute(dl, 'example_value')
497 self._AddExample(dd, policy)
499 def _AddPolicyNote(self, parent, policy):
500 '''If a policy has an additional web page assigned with it, then add
501 a link for that page.
504 policy: The data structure of the policy.
506 if 'problem_href' not in policy:
508 problem_href = policy['problem_href']
509 div = self._AddStyledElement(parent, 'div', ['div.note'])
510 note = self._GetLocalizedMessage('note').replace('$6', problem_href)
511 self._AddTextWithLinks(div, note)
513 def _AddPolicyRow(self, parent, policy):
514 '''Adds a row for the policy in the summary table.
517 parent: The DOM node of the summary table.
518 policy: The data structure of the policy.
520 tr = self._AddStyledElement(parent, 'tr', ['tr'])
521 indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14)
522 if policy['type'] != 'group':
523 # Normal policies get two columns with name and caption.
524 name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
526 self.AddElement(name_td, 'a',
527 {'href': '#' + policy['name']}, policy['name'])
528 self._AddStyledElement(tr, 'td', ['td', 'td.right'], {},
531 # Groups get one column with caption.
532 name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
533 {'style': indent, 'colspan': '2'})
534 self.AddElement(name_td, 'a', {'href': '#' + policy['name']},
537 def _AddPolicySection(self, parent, policy):
538 '''Adds a section about the policy in the detailed policy listing.
541 parent: The DOM node of the <div> of the detailed policy list.
542 policy: The data structure of the policy.
544 # Set style according to group nesting level.
545 indent = 'margin-left: %dpx' % (self._indent_level * 28)
546 if policy['type'] == 'group':
550 parent2 = self.AddElement(parent, 'div', {'style': indent})
552 h2 = self.AddElement(parent2, heading)
553 self.AddElement(h2, 'a', {'name': policy['name']})
554 if policy['type'] != 'group':
555 # Normal policies get a full description.
556 policy_name_text = policy['name']
557 if 'deprecated' in policy and policy['deprecated'] == True:
558 policy_name_text += " ("
559 policy_name_text += self._GetLocalizedMessage('deprecated') + ")"
560 self.AddText(h2, policy_name_text)
561 self.AddElement(parent2, 'span', {}, policy['caption'])
562 self._AddPolicyNote(parent2, policy)
563 self._AddPolicyDetails(parent2, policy)
565 # Groups get a more compact description.
566 self.AddText(h2, policy['caption'])
567 self._AddStyledElement(parent2, 'div', ['div.group_desc'],
570 parent2, 'a', {'href': '#top'},
571 self._GetLocalizedMessage('back_to_top'))
574 # Implementation of abstract methods of TemplateWriter:
577 def IsDeprecatedPolicySupported(self, policy):
580 def WritePolicy(self, policy):
581 self._AddPolicyRow(self._summary_tbody, policy)
582 self._AddPolicySection(self._details_div, policy)
584 def BeginPolicyGroup(self, group):
585 self.WritePolicy(group)
586 self._indent_level += 1
588 def EndPolicyGroup(self):
589 self._indent_level -= 1
591 def BeginTemplate(self):
592 # Add a <div> for the summary section.
593 if self._GetChromiumVersionString() is not None:
594 self.AddComment(self._main_div, self.config['build'] + \
595 ' version: ' + self._GetChromiumVersionString())
597 summary_div = self.AddElement(self._main_div, 'div')
598 self.AddElement(summary_div, 'a', {'name': 'top'})
599 self.AddElement(summary_div, 'br')
600 self._AddTextWithLinks(
602 self._GetLocalizedMessage('intro'))
603 self.AddElement(summary_div, 'br')
604 self.AddElement(summary_div, 'br')
605 self.AddElement(summary_div, 'br')
606 # Add the summary table of policies.
607 summary_table = self._AddStyledElement(summary_div, 'table', ['table'])
609 thead = self.AddElement(summary_table, 'thead')
610 tr = self._AddStyledElement(thead, 'tr', ['tr'])
611 self._AddStyledElement(
612 tr, 'td', ['td', 'td.left', 'thead td'], {},
613 self._GetLocalizedMessage('name_column_title'))
614 self._AddStyledElement(
615 tr, 'td', ['td', 'td.right', 'thead td'], {},
616 self._GetLocalizedMessage('description_column_title'))
617 self._summary_tbody = self.AddElement(summary_table, 'tbody')
619 # Add a <div> for the detailed policy listing.
620 self._details_div = self.AddElement(self._main_div, 'div')
623 dom_impl = minidom.getDOMImplementation('')
624 self._doc = dom_impl.createDocument(None, 'html', None)
625 body = self.AddElement(self._doc.documentElement, 'body')
626 self._main_div = self.AddElement(body, 'div')
627 self._indent_level = 0
629 # Human-readable names of supported platforms.
630 self._PLATFORM_MAP = {
634 'chrome_os': self.config['os_name'],
635 'android': 'Android',
638 # Human-readable names of supported products.
639 self._PRODUCT_MAP = {
640 'chrome': self.config['app_name'],
641 'chrome_frame': self.config['frame_name'],
642 'chrome_os': self.config['os_name'],
644 # Human-readable names of supported features. Each supported feature has
645 # a 'doc_feature_X' entry in |self.messages|.
646 self._FEATURE_MAP = {}
647 for message in self.messages:
648 if message.startswith('doc_feature_'):
649 self._FEATURE_MAP[message[12:]] = self.messages[message]['text']
650 # Human-readable names of types.
655 'int-enum': 'Integer',
656 'string-enum': 'String',
657 'list': 'List of strings',
658 'string-enum-list': 'List of strings',
659 'dict': 'Dictionary',
660 'external': 'External data reference',
662 reg_dict = 'REG_SZ; %s' % self._GetLocalizedMessage(
663 'complex_policies_on_windows')
664 self._REG_TYPE_MAP = {
668 'int-enum': 'REG_DWORD',
669 'string-enum': 'REG_SZ',
672 # The CSS style-sheet used for the document. It will be used in Google
673 # Sites, which strips class attributes from HTML tags. To work around this,
674 # the style-sheet is a dictionary and the style attributes will be added
675 # "by hand" for each element.
677 'table': 'border-style: none; border-collapse: collapse;',
678 'tr': 'height: 0px;',
679 'td': 'border: 1px dotted rgb(170, 170, 170); padding: 7px; '
680 'vertical-align: top; width: 236px; height: 15px;',
681 'thead td': 'font-weight: bold;',
682 'td.left': 'width: 200px;',
683 'td.right': 'width: 100%;',
684 'dt': 'font-weight: bold;',
685 'dd dl': 'margin-top: 0px; margin-bottom: 0px;',
686 '.monospace': 'font-family: monospace;',
687 '.pre': 'white-space: pre;',
688 'div.note': 'border: 2px solid black; padding: 5px; margin: 5px;',
689 'div.group_desc': 'margin-top: 20px; margin-bottom: 20px;',
690 'ul': 'padding-left: 0px; margin-left: 0px;'
693 # A simple regexp to search for URLs. It is enough for now.
694 self._url_matcher = lazy_re.compile('(http://[^\\s]*[^\\s\\.])')
696 def GetTemplateText(self):
697 # Return the text representation of the main <div> tag.
698 return self._main_div.toxml()
699 # To get a complete HTML file, use the following.
700 # return self._doc.toxml()