1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - AddLinkToPage 4 5 Add a link using a form in the page, getting details of the linked document 6 and inserting them with the link itself. 7 8 @copyright: 2010, 2013 Paul Boddie <paul@boddie.org.uk> 9 @license: GNU GPL, see COPYING for details. 10 """ 11 12 Dependencies = ['pages'] 13 14 from MoinMoin.action import ActionBase 15 from MoinMoin.PageEditor import PageEditor 16 from MoinMoin.wikiutil import escape 17 from MoinSupport import ActionSupport, escattr 18 import urllib 19 import re 20 21 # Page parsing. 22 23 macro_pattern = re.compile( 24 ur'^(?P<leading>.*?)' # leading text on the line 25 ur'<<AddLinkToPage\(' # macro prologue 26 ur'(?P<identifier>[^\s,)]+).*?' # identifier 27 ur'\)>>' # macro epilogue 28 ur'(?P<trailing>.*)$', # trailing text on the line 29 re.MULTILINE | re.UNICODE) 30 31 # Link visiting and parsing. 32 33 def attr_pattern(agroup, attrname, qgroup, vgroup): 34 return ( 35 ur'''(?P<%s>%s)''' # attribute name 36 ur'''\s*=\s*''' # = 37 ur'''(?P<%s>['"])''' # opening quote 38 ur'''(?P<%s>.*?)''' # value 39 ur'''(?P=%s)''' # closing quote 40 % (agroup, attrname, qgroup, vgroup, qgroup) 41 ) 42 43 meta_pattern = re.compile( 44 ur'<meta' 45 ur'([^>]*?' 46 ur'(' 47 ur'(' + attr_pattern("nattr", "name", "nquote", "name") + ')' 48 ur'|(' + attr_pattern("cattr", "content", "cquote", "content") + ')' 49 ur')' 50 ur')*' 51 ur'[^>]*?>', 52 re.MULTILINE | re.DOTALL) 53 54 title_pattern = re.compile( 55 ur'<(?P<tag>title|h\d)(\s.*?)?>' 56 ur'(?P<title>.*?)' 57 ur'</(?P=tag)>', 58 re.MULTILINE | re.DOTALL) 59 60 paragraph_pattern = re.compile( 61 ur'<p(\s.*?)?>' 62 ur'(?P<text>.*?)' 63 ur'(?=<p(\s.*?)?>|</p>)', 64 re.MULTILINE | re.DOTALL) 65 66 tag_pattern = re.compile( 67 ur'<.*?>', 68 re.MULTILINE | re.DOTALL) 69 70 def get_text(s): 71 try: 72 return unicode(s, "utf-8") 73 except UnicodeError: 74 return unicode(s, "iso-8859-1") 75 76 def get_link_info(link): 77 78 "Get information from the given 'link'." 79 80 # Insist on remote URLs! 81 # NOTE: This could probably be done better. 82 83 if not link.startswith("http:"): 84 return None 85 86 try: 87 f = urllib.urlopen(link) 88 except IOError: 89 return None 90 91 try: 92 s = get_text(f.read()) 93 94 # Look for metadata. 95 96 title = None 97 intro = None 98 99 for meta_match in meta_pattern.finditer(s): 100 name = meta_match.group("name") 101 content = meta_match.group("content") 102 if name == "title": 103 title = content 104 elif name == "description": 105 intro = content 106 107 if title and intro: 108 return title, intro 109 110 # Look for titles/headings and accompanying text. 111 112 first_title = "" 113 114 for title_match in title_pattern.finditer(s): 115 title = title_match.group("title").strip() 116 start, end = title_match.span() 117 118 if not first_title: 119 first_title = title 120 121 for intro_match in paragraph_pattern.finditer(s[end:]): 122 intro = get_flattened_content(intro_match.group("text")).strip() 123 if intro: 124 return title, intro 125 finally: 126 f.close() 127 128 return first_title, u"" 129 130 def get_flattened_content(s): 131 132 "Get HTML or XHTML without the tags." 133 134 l = [] 135 last = 0 136 for match in tag_pattern.finditer(s): 137 start, end = match.span() 138 l.append(s[last:start]) 139 last = end 140 l.append(s[last:]) 141 return "".join(l).replace("\n", " ") 142 143 # Action class and supporting functions. 144 145 class AddLinkToPage(ActionBase, ActionSupport): 146 147 "An action adding links to pages." 148 149 def get_form_html(self, buttons_html): 150 _ = self._ 151 request = self.request 152 page = self.page 153 form = self.get_form() 154 155 identifier = form.get("identifier", [""])[0] 156 identifier_list = [] 157 158 if not identifier: 159 160 # Show all macro identifiers by parsing the page. 161 162 page_body = page.get_raw_body() 163 164 for match in macro_pattern.finditer(page_body): 165 found_identifier = match.group("identifier") 166 identifier_list.append('<option value="%s">%s</option>' % ( 167 escattr(found_identifier), escape(found_identifier))) 168 169 link = form.get("link", [""])[0] 170 insert_before = form.get('insert_before', [""])[0] 171 title = "" 172 introduction = "" 173 174 # Acquire information from the link. 175 176 if link: 177 link_info = get_link_info(link) 178 179 # NOTE: Perhaps show a message upon success/failure. 180 181 if link_info is not None: 182 title, introduction = link_info 183 184 d = { 185 "buttons_html" : buttons_html, 186 "identifiers_label" : escape(_("Add to position...")), 187 "identifier" : escattr(identifier), 188 "insert_before" : insert_before and "true" or "", 189 "link" : escattr(link), 190 "title" : escattr(title), 191 "intro" : escattr(introduction), 192 "url_label" : escape(_("URL")), 193 "title_label" : escape(_("Title")), 194 "intro_label" : escape(_("Introduction")), 195 "description_label" : escape(_("Description")), 196 "preview_label" : escape(_("Preview link")), 197 } 198 199 # Given an identifier, preserve the state in a hidden field. 200 201 if not identifier_list: 202 html = ''' 203 <input type="hidden" name="identifier" value="%(identifier)s" />''' % d 204 else: 205 html = '' 206 207 # Start the rest of the form. 208 209 html += u''' 210 <input type="hidden" name="insert_before" value="%(insert_before)s" /> 211 <table> 212 <tr> 213 <td class="label">%(url_label)s</td> 214 <td><input type="text" name="link" value="%(link)s" size="40" /></td> 215 </tr>''' % d 216 217 # Given no identifier, show the choice of insertion positions. 218 219 if identifier_list: 220 html += ''' 221 <tr> 222 <td class="label">%(identifiers_label)s</td> 223 <td> 224 <select name="identifier">''' % d 225 226 for identifier_option in identifier_list: 227 html += ''' 228 %s''' % identifier_option 229 230 html += ''' 231 </select> 232 </td> 233 </tr>''' 234 235 # Finish the form. 236 237 html += ''' 238 <tr> 239 <td class="label">%(title_label)s</td> 240 <td><input type="text" name="title" value="%(title)s" size="40" /></td> 241 </tr> 242 <tr> 243 <td class="label">%(intro_label)s</td> 244 <td><textarea name="introduction" cols="40" rows="3">%(intro)s</textarea></td> 245 </tr> 246 <tr> 247 <td class="label">%(description_label)s</td> 248 <td><input type="text" name="description" size="40" /></td> 249 </tr> 250 <tr> 251 <td></td> 252 <td class="buttons"> 253 <input type="submit" value="%(preview_label)s" />%(buttons_html)s 254 </td> 255 </tr> 256 </table>''' % d 257 258 return html 259 260 def do_action(self): 261 262 "Create the new event." 263 264 _ = self._ 265 form = self.get_form() 266 267 # If no title exists in the request, an error message is returned. 268 269 identifier = form.get("identifier", [None])[0] 270 link = form.get("link", [None])[0] 271 272 if not identifier: 273 return 0, _("No identifier specified.") 274 275 if not link: 276 return 0, _("No link specified.") 277 278 return self.add_link_to_page(identifier, link) 279 280 def render_success(self, msg, msgtype=None): 281 282 """ 283 Render neither 'msg' nor 'msgtype' since redirection should occur 284 instead. 285 NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. 286 """ 287 288 pass 289 290 def add_link_to_page(self, identifier, link): 291 292 """ 293 For the macro with the given 'identifier', add 'link' to the current 294 page. 295 """ 296 297 _ = self._ 298 request = self.request 299 page = self.page 300 formatter = request.formatter 301 form = self.get_form() 302 303 # Get the link details. 304 305 title = form.get('title', [link])[0] 306 introduction = form.get('introduction', [""])[0] 307 description = form.get('description', [""])[0] 308 insert_before = form.get('insert_before', [""])[0] 309 310 # Encode the link details. 311 # NOTE: Should support different formatting options. 312 313 link_details = "%s[[%s%s]]%s" % ( 314 introduction and ('"%s" ' % get_verbatim(introduction)) or "", 315 link, 316 title and ('|%s' % title) or "", 317 description and (" - ''%s''" % description) or "" 318 ) 319 320 # Open the page for editing. 321 322 new_page = PageEditor(request, page.page_name) 323 324 # Parse the page. 325 326 page_body = page.get_raw_body() 327 328 for match in macro_pattern.finditer(page_body): 329 macro_identifier = match.group("identifier") 330 leading_text = match.group("leading") 331 trailing_text = match.group("trailing") 332 start, end = match.span() 333 334 # Where this identifier matches this macro's identifier, insert the 335 # link details. 336 337 if macro_identifier == identifier: 338 if insert_before: 339 page_body = page_body[:start] + leading_text + link_details + trailing_text + "\n" + page_body[start:] 340 else: 341 page_body = page_body[:end] + "\n" + leading_text + link_details + trailing_text + page_body[end:] 342 343 # Save the new version of the page. 344 345 new_page.saveText(page_body, 0) 346 break 347 348 # NOTE: Perhaps show a message upon failure. 349 350 request.http_redirect(page.url(request)) 351 return 1, None 352 353 def get_verbatim(s): 354 355 "Return 's' encoded as verbatim text." 356 357 output = [] 358 359 for part in s.split('"'): 360 if part: 361 output.append('<<Verbatim("%s")>>' % part) 362 else: 363 output.append('') 364 365 return '"'.join(output) 366 367 # Action function. 368 369 def execute(pagename, request): 370 AddLinkToPage(pagename, request).render() 371 372 # vim: tabstop=4 expandtab shiftwidth=4