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 import wikiutil 15 from MoinMoin.action import ActionBase 16 from MoinMoin.PageEditor import PageEditor 17 from MoinContentSupport import ActionSupport 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 # NOTE: Insist on remote URLs! 81 82 try: 83 f = urllib.urlopen(link) 84 except IOError: 85 return None 86 87 try: 88 s = f.read() 89 90 # Look for metadata. 91 92 title = None 93 intro = None 94 95 for meta_match in meta_pattern.finditer(s): 96 name = meta_match.group("name") 97 content = meta_match.group("content") 98 if name == "title": 99 title = content 100 elif name == "description": 101 intro = content 102 103 if title and intro: 104 return get_text(title), get_text(intro) 105 106 # Look for titles/headings and accompanying text. 107 108 first_title = "" 109 110 for title_match in title_pattern.finditer(s): 111 title = title_match.group("title").strip() 112 start, end = title_match.span() 113 114 if not first_title: 115 first_title = title 116 117 for intro_match in paragraph_pattern.finditer(s[end:]): 118 intro = get_flattened_content(intro_match.group("text")).strip() 119 if intro: 120 return get_text(title), get_text(intro) 121 finally: 122 f.close() 123 124 return get_text(first_title), u"" 125 126 def get_flattened_content(s): 127 128 "Get HTML or XHTML without the tags." 129 130 l = [] 131 last = 0 132 for match in tag_pattern.finditer(s): 133 start, end = match.span() 134 l.append(s[last:start]) 135 last = end 136 l.append(s[last:]) 137 return get_text("".join(l).replace("\n", " ")) 138 139 # Action class and supporting functions. 140 141 class AddLinkToPage(ActionBase, ActionSupport): 142 143 "An action adding links to pages." 144 145 def get_form_html(self, buttons_html): 146 _ = self._ 147 request = self.request 148 page = self.page 149 form = self.get_form() 150 151 identifier = form.get("identifier", [None])[0] 152 link = form.get("link", [None])[0] 153 insert_before = form.get('insert_before', [""])[0] 154 title = "" 155 introduction = "" 156 157 # Acquire information from the link. 158 159 if link is not None: 160 link_info = get_link_info(link) 161 162 # NOTE: Perhaps show a message upon success/failure. 163 164 if link_info is not None: 165 title, introduction = link_info 166 167 d = { 168 "identifier" : wikiutil.escape(identifier, 1), 169 "insert_before" : insert_before and "true" or "", 170 "link" : wikiutil.escape(link, 1), 171 "title" : wikiutil.escape(title, 1), 172 "intro" : wikiutil.escape(introduction, 1), 173 "url_label" : wikiutil.escape(_("URL")), 174 "title_label" : wikiutil.escape(_("Title")), 175 "intro_label" : wikiutil.escape(_("Introduction")), 176 "description_label" : wikiutil.escape(_("Description")), 177 "submit_label" : wikiutil.escape(_("Submit link")), 178 "script_name" : request.getScriptname(), 179 "page_url" : wikiutil.quoteWikinameURL(page.page_name) 180 } 181 182 html = u''' 183 <form class="macro" method="POST" action="%(script_name)s/%(page_url)s"> 184 <input type="hidden" name="identifier" value="%(identifier)s" /> 185 <input type="hidden" name="doit" value="1" /> 186 <input type="hidden" name="insert_before" value="%(insert_before)s" /> 187 <input type="hidden" name="action" value="AddLinkToPage" /> 188 <table> 189 <tr> 190 <td class="label">%(url_label)s</td> 191 <td><input type="text" name="link" value="%(link)s" size="40" /></td> 192 </tr> 193 <tr> 194 <td class="label">%(title_label)s</td> 195 <td><input type="text" name="title" value="%(title)s" size="40" /></td> 196 </tr> 197 <tr> 198 <td class="label">%(intro_label)s</td> 199 <td><textarea name="introduction" cols="40" rows="3">%(intro)s</textarea></td> 200 </tr> 201 <tr> 202 <td class="label">%(description_label)s</td> 203 <td><input type="text" name="description" size="40" /></td> 204 </tr> 205 <tr> 206 <td colspan="2"><input type="submit" value="%(submit_label)s" /></td> 207 </tr> 208 </table> 209 </form>''' % d 210 211 return html 212 213 def do_action(self): 214 215 "Create the new event." 216 217 _ = self._ 218 form = self.get_form() 219 220 # If no title exists in the request, an error message is returned. 221 222 identifier = form.get("identifier", [None])[0] 223 link = form.get("link", [None])[0] 224 225 if not identifier: 226 return 0, _("No identifier specified.") 227 228 if not link: 229 return 0, _("No link specified.") 230 231 return self.add_link_to_page(identifier, link) 232 233 def render_success(self, msg, msgtype=None): 234 235 """ 236 Render neither 'msg' nor 'msgtype' since redirection should occur 237 instead. 238 NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. 239 """ 240 241 pass 242 243 def add_link_to_page(self, identifier, link): 244 245 """ 246 For the macro with the given 'identifier', add 'link' to the current 247 page. 248 """ 249 250 _ = self._ 251 request = self.request 252 page = self.page 253 formatter = request.formatter 254 form = self.get_form() 255 256 # Get the link details. 257 258 title = form.get('title', [link])[0] 259 introduction = form.get('introduction', [""])[0] 260 description = form.get('description', [""])[0] 261 insert_before = form.get('insert_before', [""])[0] 262 263 # Encode the link details. 264 # NOTE: Should support different formatting options. 265 266 link_details = "%s[[%s%s]]%s" % ( 267 introduction and ('"%s" ' % get_verbatim(introduction)) or "", 268 link, 269 title and ('|%s' % title) or "", 270 description and (" - ''%s''" % description) or "" 271 ) 272 273 # Open the page for editing. 274 275 new_page = PageEditor(request, page.page_name) 276 277 # Parse the page. 278 279 page_body = page.get_raw_body() 280 281 for match in macro_pattern.finditer(page_body): 282 macro_identifier = match.group("identifier") 283 leading_text = match.group("leading") 284 trailing_text = match.group("trailing") 285 start, end = match.span() 286 287 # Where this identifier matches this macro's identifier, insert the 288 # link details. 289 290 if macro_identifier == identifier: 291 if insert_before: 292 page_body = page_body[:start] + leading_text + link_details + trailing_text + "\n" + page_body[start:] 293 else: 294 page_body = page_body[:end] + "\n" + leading_text + link_details + trailing_text + page_body[end:] 295 296 # Save the new version of the page. 297 298 new_page.saveText(page_body, 0) 299 break 300 301 # NOTE: Perhaps show a message upon failure. 302 303 request.http_redirect(page.url(request)) 304 return 1, None 305 306 def get_verbatim(s): 307 308 "Return 's' encoded as verbatim text." 309 310 output = [] 311 312 for part in s.split('"'): 313 if part: 314 output.append('<<Verbatim("%s")>>' % part) 315 else: 316 output.append('') 317 318 return '"'.join(output) 319 320 # Action function. 321 322 def execute(pagename, request): 323 AddLinkToPage(pagename, request).render() 324 325 # vim: tabstop=4 expandtab shiftwidth=4