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.
7 from xml.dom import minidom
8 from grit import lazy_re
9 from grit.format.policy_templates.writers import xml_formatted_writer
12 def GetWriter(config):
13 '''Factory method for creating DocWriter objects.
14 See the constructor of TemplateWriter for description of
17 return DocWriter(['*'], config)
20 class DocWriter(xml_formatted_writer.XMLFormattedWriter):
21 '''Class for generating policy templates in HTML format.
22 The intended use of the generated file is to upload it on
23 http://dev.chromium.org, therefore its format has some limitations:
24 - No HTML and body tags.
25 - Restricted set of element attributes: for example no 'class'.
26 Because of the latter the output is styled using the 'style'
27 attributes of HTML elements. This is supported by the dictionary
28 self._STYLES[] and the method self._AddStyledElement(), they try
29 to mimic the functionality of CSS classes. (But without inheritance.)
31 This class is invoked by PolicyTemplateGenerator to create the HTML
35 def _GetLocalizedMessage(self, msg_id):
36 '''Returns a localized message for this writer.
39 msg_id: The identifier of the message.
42 The localized message.
44 return self.messages['doc_' + msg_id]['text']
46 def _MapListToString(self, item_map, items):
47 '''Creates a comma-separated list.
50 item_map: A dictionary containing all the elements of 'items' as
52 items: A list of arbitrary items.
55 Looks up each item of 'items' in 'item_maps' and concatenates the
56 resulting items into a comma-separated list.
58 return ', '.join([item_map[x] for x in items])
60 def _AddTextWithLinks(self, parent, text):
61 '''Parse a string for URLs and add it to a DOM node with the URLs replaced
65 parent: The DOM node to which the text will be added.
66 text: The string to be added.
68 # Iterate through all the URLs and replace them with links.
71 # Look for the first URL.
72 res = self._url_matcher.search(text)
75 # Calculate positions of the substring of the URL.
79 # Add the text prior to the URL.
80 self.AddText(parent, text[:start])
81 # Add a link for the URL.
82 self.AddElement(parent, 'a', {'href': url}, url)
83 # Drop the part of text that is added.
85 self.AddText(parent, text)
88 def _AddStyledElement(self, parent, name, style_ids, attrs=None, text=None):
89 '''Adds an XML element to a parent, with CSS style-sheets included.
92 parent: The parent DOM node.
93 name: Name of the element to add.
94 style_ids: A list of CSS style strings from self._STYLE[].
95 attrs: Dictionary of attributes for the element.
96 text: Text content for the element.
101 style = ''.join([self._STYLE[x] for x in style_ids])
103 # Apply the style specified by style_ids.
104 attrs['style'] = style + attrs.get('style', '')
105 return self.AddElement(parent, name, attrs, text)
107 def _AddDescription(self, parent, policy):
108 '''Adds a string containing the description of the policy. URLs are
109 replaced with links and the possible choices are enumerated in case
110 of 'string-enum' and 'int-enum' type policies.
113 parent: The DOM node for which the feature list will be added.
114 policy: The data structure of a policy.
116 # Replace URLs with links in the description.
117 self._AddTextWithLinks(parent, policy['desc'])
118 # Add list of enum items.
119 if policy['type'] in ('string-enum', 'int-enum'):
120 ul = self.AddElement(parent, 'ul')
121 for item in policy['items']:
122 if policy['type'] == 'int-enum':
123 value_string = str(item['value'])
125 value_string = '"%s"' % item['value']
127 ul, 'li', {}, '%s = %s' % (value_string, item['caption']))
129 def _AddFeatures(self, parent, policy):
130 '''Adds a string containing the list of supported features of a policy
131 to a DOM node. The text will look like as:
132 Feature_X: Yes, Feature_Y: No
135 parent: The DOM node for which the feature list will be added.
136 policy: The data structure of a policy.
139 # The sorting is to make the order well-defined for testing.
140 keys = policy['features'].keys()
143 key_name = self._FEATURE_MAP[key]
144 if policy['features'][key]:
145 value_name = self._GetLocalizedMessage('supported')
147 value_name = self._GetLocalizedMessage('not_supported')
148 features.append('%s: %s' % (key_name, value_name))
149 self.AddText(parent, ', '.join(features))
151 def _AddListExampleMac(self, parent, policy):
152 '''Adds an example value for Mac of a 'list' policy to a DOM node.
155 parent: The DOM node for which the example will be added.
156 policy: A policy of type 'list', for which the Mac example value
159 example_value = policy['example_value']
160 self.AddElement(parent, 'dt', {}, 'Mac:')
161 mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
163 mac_text = ['<array>']
164 for item in example_value:
165 mac_text.append(' <string>%s</string>' % item)
166 mac_text.append('</array>')
167 self.AddText(mac, '\n'.join(mac_text))
169 def _AddListExampleWindows(self, parent, policy):
170 '''Adds an example value for Windows of a 'list' policy to a DOM node.
173 parent: The DOM node for which the example will be added.
174 policy: A policy of type 'list', for which the Windows example value
177 example_value = policy['example_value']
178 self.AddElement(parent, 'dt', {}, 'Windows:')
179 win = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
182 key_name = self.config['win_reg_mandatory_key_name']
183 for item in example_value:
185 '%s\\%s\\%d = "%s"' %
186 (key_name, policy['name'], cnt, item))
188 self.AddText(win, '\n'.join(win_text))
190 def _AddListExampleLinux(self, parent, policy):
191 '''Adds an example value for Linux of a 'list' policy to a DOM node.
194 parent: The DOM node for which the example will be added.
195 policy: A policy of type 'list', for which the Linux example value
198 example_value = policy['example_value']
199 self.AddElement(parent, 'dt', {}, 'Linux:')
200 linux = self._AddStyledElement(parent, 'dd', ['.monospace'])
202 for item in example_value:
203 linux_text.append('"%s"' % item)
204 self.AddText(linux, '[%s]' % ', '.join(linux_text))
206 def _AddListExample(self, parent, policy):
207 '''Adds the example value of a 'list' policy to a DOM node. Example output:
211 Software\Policies\Chromium\DisabledPlugins\0 = "Java"
212 Software\Policies\Chromium\DisabledPlugins\1 = "Shockwave Flash"
215 <dd>["Java", "Shockwave Flash"]</dd>
219 <string>Java</string>
220 <string>Shockwave Flash</string>
226 parent: The DOM node for which the example will be added.
227 policy: The data structure of a policy.
229 examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
230 if self.IsPolicySupportedOnPlatform(policy, 'win'):
231 self._AddListExampleWindows(examples, policy)
232 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
233 self._AddListExampleLinux(examples, policy)
234 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
235 self._AddListExampleMac(examples, policy)
237 def _PythonObjectToPlist(self, obj, indent=''):
238 '''Converts a python object to an equivalent XML plist.
240 Returns a list of lines.'''
243 return [ '%s<%s/>' % (indent, 'true' if obj else 'false') ]
244 elif obj_type == int:
245 return [ '%s<integer>%s</integer>' % (indent, obj) ]
246 elif obj_type == str:
247 return [ '%s<string>%s</string>' % (indent, obj) ]
248 elif obj_type == list:
249 result = [ '%s<array>' % indent ]
251 result += self._PythonObjectToPlist(item, indent + ' ')
252 result.append('%s</array>' % indent)
254 elif obj_type == dict:
255 result = [ '%s<dict>' % indent ]
256 for key in sorted(obj.keys()):
257 result.append('%s<key>%s</key>' % (indent + ' ', key))
258 result += self._PythonObjectToPlist(obj[key], indent + ' ')
259 result.append('%s</dict>' % indent)
262 raise Exception('Invalid object to convert: %s' % obj)
264 def _AddDictionaryExampleMac(self, parent, policy):
265 '''Adds an example value for Mac of a 'dict' policy to a DOM node.
268 parent: The DOM node for which the example will be added.
269 policy: A policy of type 'dict', for which the Mac example value
272 example_value = policy['example_value']
273 self.AddElement(parent, 'dt', {}, 'Mac:')
274 mac = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
275 mac_text = ['<key>%s</key>' % (policy['name'])]
276 mac_text += self._PythonObjectToPlist(example_value)
277 self.AddText(mac, '\n'.join(mac_text))
279 def _AddDictionaryExampleWindows(self, parent, policy):
280 '''Adds an example value for Windows of a 'dict' policy to a DOM node.
283 parent: The DOM node for which the example will be added.
284 policy: A policy of type 'dict', for which the Windows example value
287 self.AddElement(parent, 'dt', {}, 'Windows:')
288 win = self._AddStyledElement(parent, 'dd', ['.monospace', '.pre'])
289 key_name = self.config['win_reg_mandatory_key_name']
290 example = str(policy['example_value'])
291 self.AddText(win, '%s\\%s = "%s"' % (key_name, policy['name'], example))
293 def _AddDictionaryExampleLinux(self, parent, policy):
294 '''Adds an example value for Linux of a 'dict' policy to a DOM node.
297 parent: The DOM node for which the example will be added.
298 policy: A policy of type 'dict', for which the Linux example value
301 self.AddElement(parent, 'dt', {}, 'Linux:')
302 linux = self._AddStyledElement(parent, 'dd', ['.monospace'])
303 example = str(policy['example_value'])
304 self.AddText(linux, '%s: %s' % (policy['name'], example))
306 def _AddDictionaryExample(self, parent, policy):
307 '''Adds the example value of a 'dict' policy to a DOM node. Example output:
311 Software\Policies\Chromium\ProxySettings = "{ 'ProxyMode': 'direct' }"
314 <dd>"ProxySettings": {
315 "ProxyMode": "direct"
320 <key>ProxySettings</key>
323 <string>direct</string>
329 parent: The DOM node for which the example will be added.
330 policy: The data structure of a policy.
332 examples = self._AddStyledElement(parent, 'dl', ['dd dl'])
333 if self.IsPolicySupportedOnPlatform(policy, 'win'):
334 self._AddDictionaryExampleWindows(examples, policy)
335 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
336 self._AddDictionaryExampleLinux(examples, policy)
337 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
338 self._AddDictionaryExampleMac(examples, policy)
340 def _AddExample(self, parent, policy):
341 '''Adds the HTML DOM representation of the example value of a policy to
342 a DOM node. It is simple text for boolean policies, like
343 '0x00000001 (Windows), true (Linux), <true /> (Mac)' in case of boolean
344 policies, but it may also contain other HTML elements. (See method
348 parent: The DOM node for which the example will be added.
349 policy: The data structure of a policy.
352 Exception: If the type of the policy is unknown or the example value
353 of the policy is out of its expected range.
355 example_value = policy['example_value']
356 policy_type = policy['type']
357 if policy_type == 'main':
359 if self.IsPolicySupportedOnPlatform(policy, 'win'):
360 value = '0x00000001' if example_value else '0x00000000'
361 pieces.append(value + ' (Windows)')
362 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
363 value = 'true' if example_value else 'false'
364 pieces.append(value + ' (Linux)')
365 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
366 value = '<true />' if example_value else '<false />'
367 pieces.append(value + ' (Mac)')
368 self.AddText(parent, ', '.join(pieces))
369 elif policy_type == 'string':
370 self.AddText(parent, '"%s"' % example_value)
371 elif policy_type in ('int', 'int-enum'):
373 if self.IsPolicySupportedOnPlatform(policy, 'win'):
374 pieces.append('0x%08x (Windows)' % example_value)
375 if self.IsPolicySupportedOnPlatform(policy, 'linux'):
376 pieces.append('%d (Linux)' % example_value)
377 if self.IsPolicySupportedOnPlatform(policy, 'mac'):
378 pieces.append('%d (Mac)' % example_value)
379 self.AddText(parent, ', '.join(pieces))
380 elif policy_type == 'string-enum':
381 self.AddText(parent, '"%s"' % (example_value))
382 elif policy_type == 'list':
383 self._AddListExample(parent, policy)
384 elif policy_type == 'dict':
385 self._AddDictionaryExample(parent, policy)
387 raise Exception('Unknown policy type: ' + policy_type)
389 def _AddPolicyAttribute(self, dl, term_id,
390 definition=None, definition_style=None):
391 '''Adds a term-definition pair to a HTML DOM <dl> node. This method is
392 used by _AddPolicyDetails. Its result will have the form of:
393 <dt style="...">...</dt>
394 <dd style="...">...</dd>
397 dl: The DOM node of the <dl> list.
398 term_id: A key to self._STRINGS[] which specifies the term of the pair.
399 definition: The text of the definition. (Optional.)
400 definition_style: List of references to values self._STYLE[] that specify
401 the CSS stylesheet of the <dd> (definition) element.
404 The DOM node representing the definition <dd> element.
406 # Avoid modifying the default value of definition_style.
407 if definition_style == None:
408 definition_style = []
409 term = self._GetLocalizedMessage(term_id)
410 self._AddStyledElement(dl, 'dt', ['dt'], {}, term)
411 return self._AddStyledElement(dl, 'dd', definition_style, {}, definition)
413 def _AddSupportedOnList(self, parent, supported_on_list):
414 '''Creates a HTML list containing the platforms, products and versions
415 that are specified in the list of supported_on.
418 parent: The DOM node for which the list will be added.
419 supported_on_list: The list of supported products, as a list of
422 ul = self._AddStyledElement(parent, 'ul', ['ul'])
423 for supported_on in supported_on_list:
425 product = supported_on['product']
426 platforms = supported_on['platforms']
427 text.append(self._PRODUCT_MAP[product])
429 self._MapListToString(self._PLATFORM_MAP, platforms))
430 if supported_on['since_version']:
431 since_version = self._GetLocalizedMessage('since_version')
432 text.append(since_version.replace('$6', supported_on['since_version']))
433 if supported_on['until_version']:
434 until_version = self._GetLocalizedMessage('until_version')
435 text.append(until_version.replace('$6', supported_on['until_version']))
436 # Add the list element:
437 self.AddElement(ul, 'li', {}, ' '.join(text))
439 def _AddPolicyDetails(self, parent, policy):
440 '''Adds the list of attributes of a policy to the HTML DOM node parent.
441 It will have the form:
443 <dt>Attribute:</dt><dd>Description</dd>
448 parent: A DOM element for which the list will be added.
449 policy: The data structure of the policy.
452 dl = self.AddElement(parent, 'dl')
453 data_type = self._TYPE_MAP[policy['type']]
454 if (self.IsPolicySupportedOnPlatform(policy, 'win') and
455 self._REG_TYPE_MAP.get(policy['type'], None)):
456 data_type += ' (%s)' % self._REG_TYPE_MAP[policy['type']]
457 self._AddPolicyAttribute(dl, 'data_type', data_type)
458 if policy['type'] != 'external':
459 # All types except 'external' can be set through platform policy.
460 if self.IsPolicySupportedOnPlatform(policy, 'win'):
461 self._AddPolicyAttribute(
464 self.config['win_reg_mandatory_key_name'] + '\\' + policy['name'],
466 if (self.IsPolicySupportedOnPlatform(policy, 'linux') or
467 self.IsPolicySupportedOnPlatform(policy, 'mac')):
468 self._AddPolicyAttribute(
470 'mac_linux_pref_name',
473 dd = self._AddPolicyAttribute(dl, 'supported_on')
474 self._AddSupportedOnList(dd, policy['supported_on'])
475 dd = self._AddPolicyAttribute(dl, 'supported_features')
476 self._AddFeatures(dd, policy)
477 dd = self._AddPolicyAttribute(dl, 'description')
478 self._AddDescription(dd, policy)
479 if (self.IsPolicySupportedOnPlatform(policy, 'win') or
480 self.IsPolicySupportedOnPlatform(policy, 'linux') or
481 self.IsPolicySupportedOnPlatform(policy, 'mac')):
482 # Don't add an example for ChromeOS-only policies.
483 if policy['type'] != 'external':
484 # All types except 'external' can be set through platform policy.
485 dd = self._AddPolicyAttribute(dl, 'example_value')
486 self._AddExample(dd, policy)
488 def _AddPolicyNote(self, parent, policy):
489 '''If a policy has an additional web page assigned with it, then add
490 a link for that page.
493 policy: The data structure of the policy.
495 if 'problem_href' not in policy:
497 problem_href = policy['problem_href']
498 div = self._AddStyledElement(parent, 'div', ['div.note'])
499 note = self._GetLocalizedMessage('note').replace('$6', problem_href)
500 self._AddTextWithLinks(div, note)
502 def _AddPolicyRow(self, parent, policy):
503 '''Adds a row for the policy in the summary table.
506 parent: The DOM node of the summary table.
507 policy: The data structure of the policy.
509 tr = self._AddStyledElement(parent, 'tr', ['tr'])
510 indent = 'padding-left: %dpx;' % (7 + self._indent_level * 14)
511 if policy['type'] != 'group':
512 # Normal policies get two columns with name and caption.
513 name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
515 self.AddElement(name_td, 'a',
516 {'href': '#' + policy['name']}, policy['name'])
517 self._AddStyledElement(tr, 'td', ['td', 'td.right'], {},
520 # Groups get one column with caption.
521 name_td = self._AddStyledElement(tr, 'td', ['td', 'td.left'],
522 {'style': indent, 'colspan': '2'})
523 self.AddElement(name_td, 'a', {'href': '#' + policy['name']},
526 def _AddPolicySection(self, parent, policy):
527 '''Adds a section about the policy in the detailed policy listing.
530 parent: The DOM node of the <div> of the detailed policy list.
531 policy: The data structure of the policy.
533 # Set style according to group nesting level.
534 indent = 'margin-left: %dpx' % (self._indent_level * 28)
535 if policy['type'] == 'group':
539 parent2 = self.AddElement(parent, 'div', {'style': indent})
541 h2 = self.AddElement(parent2, heading)
542 self.AddElement(h2, 'a', {'name': policy['name']})
543 if policy['type'] != 'group':
544 # Normal policies get a full description.
545 policy_name_text = policy['name']
546 if 'deprecated' in policy and policy['deprecated'] == True:
547 policy_name_text += " ("
548 policy_name_text += self._GetLocalizedMessage('deprecated') + ")"
549 self.AddText(h2, policy_name_text)
550 self.AddElement(parent2, 'span', {}, policy['caption'])
551 self._AddPolicyNote(parent2, policy)
552 self._AddPolicyDetails(parent2, policy)
554 # Groups get a more compact description.
555 self.AddText(h2, policy['caption'])
556 self._AddStyledElement(parent2, 'div', ['div.group_desc'],
559 parent2, 'a', {'href': '#top'},
560 self._GetLocalizedMessage('back_to_top'))
563 # Implementation of abstract methods of TemplateWriter:
566 def IsDeprecatedPolicySupported(self, policy):
569 def WritePolicy(self, policy):
570 self._AddPolicyRow(self._summary_tbody, policy)
571 self._AddPolicySection(self._details_div, policy)
573 def BeginPolicyGroup(self, group):
574 self.WritePolicy(group)
575 self._indent_level += 1
577 def EndPolicyGroup(self):
578 self._indent_level -= 1
580 def BeginTemplate(self):
581 # Add a <div> for the summary section.
582 summary_div = self.AddElement(self._main_div, 'div')
583 self.AddElement(summary_div, 'a', {'name': 'top'})
584 self.AddElement(summary_div, 'br')
585 self._AddTextWithLinks(
587 self._GetLocalizedMessage('intro'))
588 self.AddElement(summary_div, 'br')
589 self.AddElement(summary_div, 'br')
590 self.AddElement(summary_div, 'br')
591 # Add the summary table of policies.
592 summary_table = self._AddStyledElement(summary_div, 'table', ['table'])
594 thead = self.AddElement(summary_table, 'thead')
595 tr = self._AddStyledElement(thead, 'tr', ['tr'])
596 self._AddStyledElement(
597 tr, 'td', ['td', 'td.left', 'thead td'], {},
598 self._GetLocalizedMessage('name_column_title'))
599 self._AddStyledElement(
600 tr, 'td', ['td', 'td.right', 'thead td'], {},
601 self._GetLocalizedMessage('description_column_title'))
602 self._summary_tbody = self.AddElement(summary_table, 'tbody')
604 # Add a <div> for the detailed policy listing.
605 self._details_div = self.AddElement(self._main_div, 'div')
608 dom_impl = minidom.getDOMImplementation('')
609 self._doc = dom_impl.createDocument(None, 'html', None)
610 body = self.AddElement(self._doc.documentElement, 'body')
611 self._main_div = self.AddElement(body, 'div')
612 self._indent_level = 0
614 # Human-readable names of supported platforms.
615 self._PLATFORM_MAP = {
619 'chrome_os': self.config['os_name'],
620 'android': 'Android',
623 # Human-readable names of supported products.
624 self._PRODUCT_MAP = {
625 'chrome': self.config['app_name'],
626 'chrome_frame': self.config['frame_name'],
627 'chrome_os': self.config['os_name'],
629 # Human-readable names of supported features. Each supported feature has
630 # a 'doc_feature_X' entry in |self.messages|.
631 self._FEATURE_MAP = {}
632 for message in self.messages:
633 if message.startswith('doc_feature_'):
634 self._FEATURE_MAP[message[12:]] = self.messages[message]['text']
635 # Human-readable names of types.
640 'int-enum': 'Integer',
641 'string-enum': 'String',
642 'list': 'List of strings',
643 'dict': 'Dictionary',
644 'external': 'External data reference',
646 self._REG_TYPE_MAP = {
650 'int-enum': 'REG_DWORD',
651 'string-enum': 'REG_SZ',
652 'dict': 'REG_SZ, encoded as a JSON string',
654 # The CSS style-sheet used for the document. It will be used in Google
655 # Sites, which strips class attributes from HTML tags. To work around this,
656 # the style-sheet is a dictionary and the style attributes will be added
657 # "by hand" for each element.
659 'table': 'border-style: none; border-collapse: collapse;',
660 'tr': 'height: 0px;',
661 'td': 'border: 1px dotted rgb(170, 170, 170); padding: 7px; '
662 'vertical-align: top; width: 236px; height: 15px;',
663 'thead td': 'font-weight: bold;',
664 'td.left': 'width: 200px;',
665 'td.right': 'width: 100%;',
666 'dt': 'font-weight: bold;',
667 'dd dl': 'margin-top: 0px; margin-bottom: 0px;',
668 '.monospace': 'font-family: monospace;',
669 '.pre': 'white-space: pre;',
670 'div.note': 'border: 2px solid black; padding: 5px; margin: 5px;',
671 'div.group_desc': 'margin-top: 20px; margin-bottom: 20px;',
672 'ul': 'padding-left: 0px; margin-left: 0px;'
675 # A simple regexp to search for URLs. It is enough for now.
676 self._url_matcher = lazy_re.compile('(http://[^\\s]*[^\\s\\.])')
678 def GetTemplateText(self):
679 # Return the text representation of the main <div> tag.
680 return self._main_div.toxml()
681 # To get a complete HTML file, use the following.
682 # return self._doc.toxml()