Robot Framework Integrated Development Environment (RIDE)
htmlformatters.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import re
17 from functools import partial
18 from itertools import cycle
19 
20 
21 class LinkFormatter():
22 
25  _image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp')
26 
29  _link = re.compile('\[(.+?\|.*?)\]')
30 
33  _url = re.compile('''
34 ((^|\ ) ["'\‍(\[]*) # begin of line or space and opt. any char "'([
35 ([a-z][\w+-.]*://[^\s|]+?) # url
36 (?=[\]\‍)|"'.,!?:;]* ($|\ )) # opt. any char ])"'.,!?:; and end of line or space
37 ''', re.VERBOSE|re.MULTILINE|re.IGNORECASE)
38 
39  def format_url(self, text):
40  return self._format_url_format_url(text, format_as_image=False)
41 
42  def _format_url(self, text, format_as_image=True):
43  if '://' not in text:
44  return text
45  return self._url_url.sub(partial(self._replace_url_replace_url, format_as_image), text)
46 
47  def _replace_url(self, format_as_image, match):
48  pre = match.group(1)
49  url = match.group(3)
50  if format_as_image and self._is_image_is_image(url):
51  return pre + self._get_image_get_image(url)
52  return pre + self._get_link_get_link(url)
53 
54  def _get_image(self, src, title=None):
55  return '<img src="%s" title="%s">' \
56  % (self._quot_quot(src), self._quot_quot(title or src))
57 
58  def _get_link(self, href, content=None):
59  return '<a href="%s">%s</a>' % (self._quot_quot(href), content or href)
60 
61  def _quot(self, attr):
62  return attr if '"' not in attr else attr.replace('"', '&quot;')
63 
64  def format_link(self, text):
65  # 2nd, 4th, etc. token contains link, others surrounding content
66  tokens = self._link_link.split(text)
67  formatters = cycle((self._format_url_format_url, self._format_link_format_link))
68  return ''.join(f(t) for f, t in zip(formatters, tokens))
69 
70  def _format_link(self, text):
71  link, content = [t.strip() for t in text.split('|', 1)]
72  if self._is_image_is_image(content):
73  content = self._get_image_get_image(content, link)
74  elif self._is_image_is_image(link):
75  return self._get_image_get_image(link, content)
76  return self._get_link_get_link(link, content)
77 
78  def _is_image(self, text):
79  return text.lower().endswith(self._image_exts_image_exts)
80 
81 
82 class LineFormatter():
83  handles = lambda self, line: True
84  newline = '\n'
85 
88  _bold = re.compile('''
89 ( # prefix (group 1)
90  (^|\ ) # begin of line or space
91  ["'(]* _? # optionally any char "'( and optional begin of italic
92 ) #
93 \* # start of bold
94 ([^\ ].*?) # no space and then anything (group 3)
95 \* # end of bold
96 (?= # start of postfix (non-capturing group)
97  _? ["').,!?:;]* # optional end of italic and any char "').,!?:;
98  ($|\ ) # end of line or space
99 )
100 ''', re.VERBOSE)
101 
104  _italic = re.compile('''
105 ( (^|\ ) ["'(]* ) # begin of line or space and opt. any char "'(
106 _ # start of italic
107 ([^\ _].*?) # no space or underline and then anything
108 _ # end of italic
109 (?= ["').,!?:;]* ($|\ ) ) # opt. any char "').,!?:; and end of line or space
110 ''', re.VERBOSE)
111 
114  _code = re.compile('''
115 ( (^|\ ) ["'(]* ) # same as above with _ changed to ``
116 ``
117 ([^\ `].*?)
118 ``
119 (?= ["').,!?:;]* ($|\ ) )
120 ''', re.VERBOSE)
121 
122  def __init__(self):
123  self._formatters_formatters = [('*', self._format_bold_format_bold),
124  ('_', self._format_italic_format_italic),
125  ('``', self._format_code_format_code),
126  ('', LinkFormatter().format_link)]
127 
128  def format(self, line):
129  for marker, formatter in self._formatters_formatters:
130  if marker in line:
131  line = formatter(line)
132  return line
133 
134  def _format_bold(self, line):
135  return self._bold_bold.sub('\\1<b>\\3</b>', line)
136 
137  def _format_italic(self, line):
138  return self._italic_italic.sub('\\1<i>\\3</i>', line)
139 
140  def _format_code(self, line):
141  return self._code_code.sub('\\1<code>\\3</code>', line)
142 
143 
145 
146  def __init__(self):
147  self._results_results = []
148  self._formatters_formatters = [TableFormatter(),
150  ListFormatter(),
151  HeaderFormatter(),
152  RulerFormatter()]
153  self._formatters_formatters.append(ParagraphFormatter(self._formatters_formatters[:]))
154  self._current_current = None
155 
156  def format(self, text):
157  for line in text.splitlines():
158  self._process_line_process_line(line)
159  self._end_current_end_current()
160  return '\n'.join(self._results_results)
161 
162  def _process_line(self, line):
163  if not line.strip():
164  self._end_current_end_current()
165  elif self._current_current and self._current_current.handles(line):
166  self._current_current.add(line)
167  else:
168  self._end_current_end_current()
169  self._current_current = self._find_formatter_find_formatter(line)
170  self._current_current.add(line)
171 
172  def _end_current(self):
173  if self._current_current:
174  self._results_results.append(self._current_current.end())
175  self._current_current = None
176 
177  def _find_formatter(self, line):
178  for formatter in self._formatters_formatters:
179  if formatter.handles(line):
180  return formatter
181 
182 
183 class _Formatter():
184 
187  _strip_lines = True
188 
189  def __init__(self):
190  self._lines_lines = []
191 
192  def handles(self, line):
193  return self._handles_handles(line.strip() if self._strip_lines_strip_lines else line)
194 
195  def _handles(self, line):
196  raise NotImplementedError
197 
198  def add(self, line):
199  self._lines_lines.append(line.strip() if self._strip_lines_strip_lines else line)
200 
201  def end(self):
202  result = self.formatformat(self._lines_lines)
203  self._lines_lines = []
204  return result
205 
206  def format(self, lines):
207  raise NotImplementedError
208 
209 
211 
212  def _handles(self, line):
213  return not self._lines_lines and self.matchmatch(line)
214 
215  def match(self, line):
216  raise NotImplementedError
217 
218  def format(self, lines):
219  return self.format_lineformat_line(lines[0])
220 
221  def format_line(self, line):
222  raise NotImplementedError
223 
224 
226  match = re.compile('^-{3,}$').match
227 
228  def format_line(self, line):
229  return '<hr>'
230 
231 
233  match = re.compile(r'^(={1,3})\s+(\S.*?)\s+\1$').match
234 
235  def format_line(self, line):
236  level, text = self.matchmatchmatch(line).groups()
237  level = len(level) + 1
238  return '<h%d>%s</h%d>' % (level, text, level)
239 
240 
242 
245  _format_line = LineFormatter().format
246 
247  def __init__(self, other_formatters):
248  _Formatter.__init__(self)
249  self._other_formatters_other_formatters = other_formatters
250 
251  def _handles(self, line):
252  return not any(other.handles(line)
253  for other in self._other_formatters_other_formatters)
254 
255  def format(self, lines):
256  return '<p>%s</p>' % self._format_line_format_line(' '.join(lines))
257 
258 
260 
263  _table_line = re.compile('^\| (.* |)\|$')
264 
267  _line_splitter = re.compile(' \|(?= )')
268 
271  _format_cell_content = LineFormatter().format
272 
273  def _handles(self, line):
274  return self._table_line_table_line.match(line) is not None
275 
276  def format(self, lines):
277  return self._format_table_format_table([self._split_to_cells_split_to_cells(l) for l in lines])
278 
279  def _split_to_cells(self, line):
280  return [cell.strip() for cell in self._line_splitter_line_splitter.split(line[1:-1])]
281 
282  def _format_table(self, rows):
283  maxlen = max(len(row) for row in rows)
284  table = ['<table border="1">']
285  for row in rows:
286  row += [''] * (maxlen - len(row)) # fix ragged tables
287  table.append('<tr>')
288  table.extend(self._format_cell_format_cell(cell) for cell in row)
289  table.append('</tr>')
290  table.append('</table>')
291  return '\n'.join(table)
292 
293  def _format_cell(self, content):
294  if content.startswith('=') and content.endswith('='):
295  tx = 'th'
296  content = content[1:-1].strip()
297  else:
298  tx = 'td'
299  return '<%s>%s</%s>' % (tx, self._format_cell_content_format_cell_content(content), tx)
300 
301 
303 
306  _format_line = LineFormatter().format
307 
308  def _handles(self, line):
309  return line.startswith('| ') or line == '|'
310 
311  def format(self, lines):
312  lines = [self._format_line_format_line(line[2:]) for line in lines]
313  return '\n'.join(['<pre>'] + lines + ['</pre>'])
314 
315 
317 
320  _strip_lines = False
321 
324  _format_item = LineFormatter().format
325 
326  def _handles(self, line):
327  return line.strip().startswith('- ') or \
328  line.startswith(' ') and self._lines_lines
329 
330  def format(self, lines):
331  items = ['<li>%s</li>' % self._format_item_format_item(line)
332  for line in self._combine_lines_combine_lines(lines)]
333  return '\n'.join(['<ul>'] + items + ['</ul>'])
334 
335  def _combine_lines(self, lines):
336  current = []
337  for line in lines:
338  line = line.strip()
339  if not line.startswith('- '):
340  current.append(line)
341  continue
342  if current:
343  yield ' '.join(current)
344  current = [line[2:].strip()]
345  yield ' '.join(current)
def _format_url(self, text, format_as_image=True)
def _replace_url(self, format_as_image, match)