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