############################################################################## # # Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved. # Yoshinori Okuji <yo@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface from Products.ERP5.Document.Document import Document from zLOG import LOG import cgi import re class Wiki( Document ): """ Bug means a bug report, a feature request or an issue. """ # Default Properties property_sheets = ( PropertySheet.Base , PropertySheet.XMLObject , PropertySheet.CategoryCore , PropertySheet.DublinCore , PropertySheet.Document ) # CMF Type Definition meta_type='ERP5 Wiki' portal_type='Wiki' add_permission = Permissions.AddPortalContent isPortalContent = 1 isRADContent = 1 # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) # Compile regular expressions at load time. table_expr = re.compile(r"^\|\|(.+)\|\|$") heading_expr = re.compile(r"^(={1,5}) (.+) \1$") horizontal_line_expr = re.compile(r"^-{4,}$") enumerated_list_expr = re.compile(r"^(\s+)[0-9]+\.(.*)") item_list_expr = re.compile(r"^(\s+)\*(.*)") preformatted_text_expr = re.compile(r"^{{{$") empty_line_expr = re.compile(r"^\s*$") line_expr_map = { 'table' : table_expr, 'heading' : heading_expr, 'horizontal_line' : horizontal_line_expr, 'enumerated_list' : enumerated_list_expr, 'item_list' : item_list_expr, 'preformatted_text' : preformatted_text_expr, 'empty_line' : empty_line_expr, } wiki_name_expr = re.compile(r"!?([A-Z][a-z]+){2,}(/([A-Z][a-z]+){2,})*") underscore_expr = re.compile(r"__([^_]+)__") bold_expr = re.compile(r"'''([^']+)'''") italic_expr = re.compile(r"(?<!')''([^']+)''(?!')") strike_expr = re.compile(r"~~([^~]+)~~") url_expr = re.compile(r"(!?[a-z]+://[^ ]+)") link_expr = re.compile(r"(?<!\[)\[([^\] ]+) ([^\]]+)\](?!\])") tales_expr = re.compile(r"\[\[([^\]]+)\]\]") text_expr_map = { 'wiki_name' : wiki_name_expr, 'underscore': underscore_expr, 'bold' : bold_expr, 'italic' : italic_expr, 'strike' : strike_expr, 'url' : url_expr, 'link' : link_expr, 'tales' : tales_expr, } def _getTopLevelUrl(self): o = self.aq_parent while o.meta_type == 'ERP5 Wiki': o = o.aq_parent return o.absolute_url() def _render_text(self, text): """ Render a piece of text. """ text_list = [] append = text_list.append while text: min_start = len(text) match = None min_key = None for key, expr in self.text_expr_map.items(): m = expr.search(text) if m is not None: start = m.start(0) if min_start > start: min_start = start min_key = key match = m if match is None: # Nothing special. append(cgi.escape(text)) break else: if min_start > 0: append(text[:min_start]) if min_key == 'wiki_name': name = match.string[match.start(0):match.end(0)] if name.startswith('!'): append(name[1:]) else: # FIMXE: Create a new wiki instance if not present. append('<a href="%s/%s/view">%s</a>' % (self._getTopLevelUrl(), name, name)) elif min_key == 'url': url = match.string[match.start(1):match.end(1)] if url.startswith('!'): append(url[1:]) else: # XXX Language-specific evil hack. if url[-1] in r",.')]}": extra = url[-1] url = url[:-1] else: extra = None append('<a href="%s">%s</a>' % (url, cgi.escape(url))) if extra is not None: append(extra) elif min_key == 'tales': append(self._render_tales(match.string[match.start(1):match.end(1)])) elif min_key == 'link': link, label = match.groups() label = self._render_text(label) if self.wiki_name_expr.search(link): # FIXME: Create a new wiki instance if not present append('<a href="%s/%s/view">%s</a>' % (self._getTopLevelUrl(), link, label)) else: append('<a href="%s">%s</a>' % (link, label)) else: word = self._render_text(match.string[match.start(1):match.end(1)]) if min_key == 'underscore': append('<u>%s</u>' % word) elif min_key == 'italic': append('<i>%s</i>' % word) elif min_key == 'bold': append('<b>%s</b>' % word) elif min_key == 'strike': append('<del>%s</del>' % word) text = text[match.end(0):] return ''.join(text_list) def _render_tales(self, expr): """ Evaluate the expression. FIXME """ return '' def _render_table_row(self, text): """ Render a row of a table. """ text_list = ['<tr>'] append = text_list.append for cell in text.split('||'): append('<td>') append(self._render_text(cell)) append('</td>') append('</tr>') return ''.join(text_list) security.declareProtected(Permissions.View, 'render') def render(self, content=None, format=None): """ Render the contents and generate HTML. FIXME: Very dirty. Better to rewrite this with a state machine. """ if content is None: content = self.getTextContent() if content is None: content = '' preformatted = False in_table = False in_paragraph = False list_stack = [] line_list = [] append = line_list.append for line in content.split('\r\n'): if preformatted: if line == '}}}': preformatted = False append('</pre>') else: append(cgi.escape(line)) continue if in_table: m = self.table_expr.search(line) if m is not None: append(self._render_table_row(m.string[m.start(1):m.end(1)])) continue else: append('</table>') if list_stack: for key, expr in (('enumerated_list', self.enumerated_list_expr), ('item_list', self.item_list_expr)): m = expr.search(line) if m is not None: break else: key = None if key is None: level = 0 else: level = m.end(1) - m.start(1) while list_stack and list_stack[-1][1] > level: prev_key, prev_level = list_stack.pop() if prev_key == 'enumerated_list': append('</ol>') else: append('</ul>') if list_stack and list_stack[-1][1] == level: if key == list_stack[-1][0]: append('<li>') append(self._render_text(m.string[m.start(2):m.end(2)])) continue else: prev_key, prev_level = list_stack.pop() if prev_key == 'enumerated_list': append('</ol>') else: append('</ul>') if key is not None: list_stack.append((key, level)) if key == 'enumerated_list': append('<ol><li>') append(self._render_text(m.string[m.start(2):m.end(2)])) else: append('<ul><li>') append(self._render_text(m.string[m.start(2):m.end(2)])) continue for key, expr in self.line_expr_map.items(): m = expr.search(line) if m is not None: break else: key = None if in_paragraph and key is not None: in_paragraph = False append('</p>') if key == 'preformatted_text': preformatted = True append('<pre>') elif key == 'horizontal_line': append('<hr>') elif key == 'table': in_table = True append('<table>') append(self._render_table_row(m.string[m.start(1):m.end(1)])) elif key == 'heading': level = m.end(1) - m.start(1) append('<h%d>' % level) append(self._render_text(m.string[m.start(2):m.end(2)])) append('</h%d>' % level) elif key == 'enumerated_list': list_stack.append((key, m.end(1) - m.start(1))) append('<ol><li>') append(self._render_text(m.string[m.start(2):m.end(2)])) elif key == 'item_list': list_stack.append((key, m.end(1) - m.start(1))) append('<ul><li>') append(self._render_text(m.string[m.start(2):m.end(2)])) elif key == 'empty_line': pass else: if not in_paragraph: in_paragraph = True append('<p>') append(self._render_text(line)) while list_stack: key, level = list_stack.pop() if key == 'enumerated_list': append('</ol>') else: append('</ul>') if in_paragraph: append('</p>') if in_table: append('</table>') if preformatted: append('</pre>') return '\n'.join(line_list)