1# Copyright 2013 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 logging 6import os 7from document_parser import ParseDocument 8from third_party.json_schema_compiler.model import UnixName 9 10 11class DocumentRenderer(object): 12 '''Performs document-level rendering such as the title, references, 13 and table of contents: pulling that data out of the document, then 14 replacing the $(title), $(ref:...) and $(table_of_contents) tokens with them. 15 16 This can be thought of as a parallel to TemplateRenderer; while 17 TemplateRenderer is responsible for interpreting templates and rendering files 18 within the template engine, DocumentRenderer is responsible for interpreting 19 higher-level document concepts like the title and TOC, then performing string 20 replacement for them. The syntax for this replacement is $(...) where ... is 21 the concept. Currently title and table_of_contents are supported. 22 ''' 23 24 def __init__(self, table_of_contents_renderer, ref_resolver): 25 self._table_of_contents_renderer = table_of_contents_renderer 26 self._ref_resolver = ref_resolver 27 28 def _RenderLinks(self, document, path): 29 ''' Replaces all $(ref:...) references in |document| with html links. 30 31 References have two forms: 32 33 $(ref:api.node) - Replaces the reference with a link to node on the 34 API page. The title is set to the name of the node. 35 36 $(ref:api.node Title) - Same as the previous form, but title is set 37 to "Title". 38 ''' 39 START_REF = '$(ref:' 40 END_REF = ')' 41 MAX_REF_LENGTH = 256 42 43 new_document = [] 44 45 # Keeps track of position within |document| 46 cursor_index = 0 47 start_ref_index = document.find(START_REF) 48 49 while start_ref_index != -1: 50 end_ref_index = document.find(END_REF, start_ref_index) 51 52 if (end_ref_index == -1 or 53 end_ref_index - start_ref_index > MAX_REF_LENGTH): 54 end_ref_index = document.find(' ', start_ref_index) 55 logging.error('%s:%s has no terminating ) at line %s' % ( 56 path, 57 document[start_ref_index:end_ref_index], 58 document.count('\n', 0, end_ref_index))) 59 60 new_document.append(document[cursor_index:end_ref_index + 1]) 61 else: 62 ref = document[start_ref_index:end_ref_index] 63 ref_parts = ref[len(START_REF):].split(None, 1) 64 65 # Guess the api name from the html name, replacing '_' with '.' (e.g. 66 # if the page is app_window.html, guess the api name is app.window) 67 api_name = os.path.splitext(os.path.basename(path))[0].replace('_', '.') 68 title = ref_parts[0] if len(ref_parts) == 1 else ref_parts[1] 69 70 ref_dict = self._ref_resolver.SafeGetLink(ref_parts[0], 71 namespace=api_name, 72 title=title) 73 74 new_document.append(document[cursor_index:start_ref_index]) 75 new_document.append('<a href=%s>%s</a>' % (ref_dict['href'], 76 ref_dict['text'])) 77 78 cursor_index = end_ref_index + 1 79 start_ref_index = document.find(START_REF, cursor_index) 80 81 new_document.append(document[cursor_index:]) 82 83 return ''.join(new_document) 84 85 def Render(self, document, path, render_title=False): 86 # Render links first so that parsing and later replacements aren't 87 # affected by $(ref...) substitutions 88 document = self._RenderLinks(document, path) 89 90 parsed_document = ParseDocument(document, expect_title=render_title) 91 toc_text, toc_warnings = self._table_of_contents_renderer.Render( 92 parsed_document.sections) 93 94 # Only 1 title and 1 table of contents substitution allowed; in the common 95 # case, save necessarily running over the entire file. 96 if parsed_document.title: 97 document = document.replace('$(title)', parsed_document.title, 1) 98 return (document.replace('$(table_of_contents)', toc_text, 1), 99 parsed_document.warnings + toc_warnings) 100