Package gavo :: Package web :: Module htmltable
[frames] | no frames]

Source Code for Module gavo.web.htmltable

  1  """ 
  2  A renderer for Data to HTML/stan 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import itertools 
 12  import os 
 13  import re 
 14  import urlparse 
 15  import urllib 
 16   
 17  from nevow import flat 
 18  from nevow import loaders 
 19  from nevow import rend 
 20  from nevow import tags as T 
 21   
 22  from gavo import base 
 23  from gavo import formats 
 24  from gavo import rsc 
 25  from gavo import svcs 
 26  from gavo import utils 
 27  from gavo.base import valuemappers 
 28  from gavo.formats import texttable 
 29  from gavo.protocols import products 
 30  from gavo.rscdef import rmkfuncs 
 31  from gavo.utils import serializers 
 32  from gavo.utils import typeconversions 
 33  from gavo.web import common 
 34   
 35   
 36  _htmlMFRegistry = texttable.displayMFRegistry.clone() 
 37  _registerHTMLMF = _htmlMFRegistry.registerFactory 
 38   
 39   
40 -def _barMapperFactory(colDesc):
41 if colDesc["displayHint"].get("type")!="bar": 42 return 43 def coder(val): 44 if val: 45 return T.hr(style="width: %dpx"%int(val), title="%.2f"%val, 46 class_="scoreBar") 47 return ""
48 return coder 49 _registerHTMLMF(_barMapperFactory) 50 51
52 -def _productMapperFactory(colDesc):
53 if colDesc["displayHint"].get("type")!="product": 54 return 55 if colDesc["displayHint"].get("nopreview"): 56 mouseoverHandler = None 57 else: 58 mouseoverHandler = "insertPreview(this, null)" 59 fixedArgs = "" 60 def coder(val): 61 if val: 62 anchor = re.sub(r"\?.*", "", 63 os.path.basename(urllib.unquote_plus(str(val)[4:]))) 64 if not anchor: 65 anchor = "File" 66 67 if isinstance(val, basestring) and utils.looksLikeURLPat.match(val): 68 # readily inserted URLs probably can't do previews, and 69 # we must not direct them through the product table anway. 70 return T.a(href=val)[anchor] 71 72 else: 73 return T.a(href=products.makeProductLink(val)+fixedArgs, 74 onmouseover=mouseoverHandler, 75 class_="productlink")[anchor] 76 else: 77 return ""
78 return coder 79 _registerHTMLMF(_productMapperFactory) 80 81
82 -def _simbadMapperFactory(colDesc):
83 """is a mapper yielding links to simbad. 84 85 To make this work, you need to furnish the OutputField with a 86 select="array[alphaFloat, deltaFloat]" or similar. 87 88 You can give a coneMins displayHint to specify the search radius in 89 minutes. 90 """ 91 if colDesc["displayHint"].get("type")!="simbadlink": 92 return 93 radius = float(colDesc["displayHint"].get("coneMins", "1")) 94 def coder(data): 95 alpha, delta = data[0], data[1] 96 if alpha and delta: 97 return T.a(href="http://simbad.u-strasbg.fr/simbad/sim-coo?Coord=%s" 98 "&Radius=%f"%(urllib.quote("%.5fd%+.5fd"%(alpha, delta)), 99 radius))["[Simbad]"] 100 else: 101 return ""
102 return coder 103 _registerHTMLMF(_simbadMapperFactory) 104 105
106 -def _bibcodeMapperFactory(colDesc):
107 if colDesc["displayHint"].get("type")!="bibcode": 108 return 109 def coder(data): 110 if data: 111 for item in data.split(","): 112 yield T.a(href=base.getConfig("web", "adsMirror")+ 113 "/abs/"+urllib.quote(item.strip()))[ 114 item.strip()] 115 yield ", " 116 else: 117 yield ""
118 return coder 119 _registerHTMLMF(_bibcodeMapperFactory) 120 121
122 -def _keepHTMLMapperFactory(colDesc):
123 if colDesc["displayHint"].get("type")!="keephtml": 124 return 125 def coder(data): 126 if data: 127 return T.raw(data) 128 return ""
129 return coder 130 _registerHTMLMF(_keepHTMLMapperFactory) 131 132
133 -def _imageURLMapperFactory(colDesc):
134 if colDesc["displayHint"].get("type")!="imageURL": 135 return 136 width = colDesc["displayHint"].get("width") 137 def coder(data): 138 if data: 139 res = T.img(src=data, alt="Image at %s"%data) 140 if width: 141 res(width=width) 142 return res 143 return ""
144 return coder 145 _registerHTMLMF(_imageURLMapperFactory) 146 147
148 -def _urlMapperFactory(colDesc):
149 if colDesc["displayHint"].get("type")!="url": 150 return 151 152 anchorText = colDesc.original.getProperty("anchorText", None) 153 if anchorText: 154 def makeAnchor(data): 155 return anchorText
156 else: 157 def makeAnchor(data): #noflake: conditional definition 158 return urllib.unquote( 159 urlparse.urlparse(data)[2].split("/")[-1]) 160 161 def coder(data): 162 if data: 163 return T.a(href=data)[makeAnchor(data)] 164 return "" 165 return coder 166 _registerHTMLMF(_urlMapperFactory) 167 168
169 -def _booleanCheckmarkFactory(colDesc):
170 """inserts mappers for values with displayHint type=checkmark. 171 172 These render a check mark if the value is python-true, else nothing. 173 """ 174 if colDesc["displayHint"].get("type")!="checkmark": 175 return 176 def coder(data): 177 if data: 178 return u"\u2713" 179 return ""
180 return coder 181 _registerHTMLMF(_booleanCheckmarkFactory) 182 183
184 -def _pgSphereMapperFactory(colDesc):
185 """do a reasonable representation of arrays in HTML: 186 """ 187 if not colDesc["dbtype"] in serializers.GEOMETRY_ARRAY_TYPES: 188 return 189 190 def mapper(val): 191 if val is None: 192 return None 193 return T.span(class_="array")["[%s]"%" ".join( 194 "%s"%v for v in val.asDALI())]
195 196 colDesc["datatype"], colDesc["arraysize"], colDesc["xtype" 197 ] = typeconversions.sqltypeToVOTable(colDesc["dbtype"]) 198 199 return mapper 200 _registerHTMLMF(_pgSphereMapperFactory) 201 202 203 # Insert new, more specific factories here 204 205
206 -class HeadCellsMixin(object):
207 """A mixin providing renders for table headings. 208 209 The class mixing in must give the SerManager used in a serManager 210 attribute. 211 """
212 - def data_fielddefs(self, ctx, ignored):
213 return self.serManager.table.tableDef.columns
214
215 - def render_headCell(self, ctx, colDef):
216 cd = self.serManager.getColumnByName(colDef.key) 217 cont = colDef.getLabel() 218 desc = cd["description"] 219 if not desc: 220 desc = cont 221 tag = ctx.tag(title=desc)[T.xml(cont)] 222 if cd["unit"]: 223 tag[T.br, "[%s]"%cd["unit"]] 224 note = cd["note"] 225 if note: 226 noteURL = "#note-%s"%note.tag 227 ctx.tag[T.sup[T.a(href=noteURL)[note.tag]]] 228 return tag
229 230
231 -class HeadCells(rend.Page, HeadCellsMixin):
232 - def __init__(self, serManager):
233 self.serManager = serManager
234 235 docFactory = loaders.stan( 236 T.tr(data=T.directive("fielddefs"), render=rend.sequence) [ 237 T.th(pattern="item", render=T.directive("headCell"), 238 class_="thVertical") 239 ])
240 241 242 _htmlMetaBuilder = common.HTMLMetaBuilder() 243 244
245 -def _compileRenderer(source, queryMeta, rd):
246 """returns a function object from source. 247 248 Source must be the function body of a renderer. The variable data 249 contains the entire row, and the thing must return a string or at 250 least stan (it can use T.tag). 251 """ 252 code = ("def format(data):\n"+ 253 utils.fixIndentation(source, " ")+"\n") 254 return rmkfuncs.makeProc("format", code, "", None, 255 queryMeta=queryMeta, source=source, T=T, rd=rd)
256 257
258 -class HTMLDataRenderer(rend.Fragment):
259 """A base class for rendering tables and table lines. 260 261 Both HTMLTableFragment (for complete tables) and HTMLKeyValueFragment 262 (for single rows) inherit from this. 263 """
264 - def __init__(self, table, queryMeta):
265 self.table, self.queryMeta = table, queryMeta 266 super(HTMLDataRenderer, self).__init__() 267 self._computeDefaultTds() 268 self._computeHeadCellsStan()
269
271 """creates the serialization manager and the formatter sequence. 272 273 These are in the attributes serManager and formatterSeq, respectively. 274 formatterSeq consists of triples of (name, formatter, fullRow), where 275 fullRow is true if the formatter wants to be passed the full row rather 276 than just the column value. 277 """ 278 self.serManager = valuemappers.SerManager(self.table, withRanges=False, 279 mfRegistry=_htmlMFRegistry, acquireSamples=False) 280 self.formatterSeq = [] 281 for index, (desc, field) in enumerate( 282 zip(self.serManager, self.table.tableDef)): 283 formatter = self.serManager.mappers[index] 284 if isinstance(field, svcs.OutputField): 285 if field.wantsRow: 286 desc["wantsRow"] = True 287 if field.formatter: 288 formatter = _compileRenderer( 289 field.formatter, 290 self.queryMeta, 291 self.table.tableDef.rd) 292 self.formatterSeq.append( 293 (desc["name"], formatter, desc.get("wantsRow", False)))
294
295 - def _computeDefaultTds(self):
296 """leaves a sequence of children for each row in the 297 defaultTds attribute. 298 299 This calls _computeSerializationRules. The function was 300 (and can still be) used for stan-based serialization of HTML tables, 301 but beware that that is dead slow. The normal rendering doesn't 302 use defaultTds any more. 303 """ 304 self._computeSerializationRules() 305 self.defaultTds = [] 306 for (name, formatter, wantsRow) in self.formatterSeq: 307 if wantsRow: 308 self.defaultTds.append( 309 T.td(formatter=formatter, render=T.directive("useformatter"))) 310 else: 311 self.defaultTds.append(T.td( 312 data=T.slot(unicode(name)), 313 formatter=formatter, 314 render=T.directive("useformatter")))
315
316 - def render_footnotes(self, ctx, data):
317 """renders the footnotes as a definition list. 318 """ 319 if self.serManager.notes: 320 yield T.hr(class_="footsep") 321 yield T.dl(class_="footnotes")[[ 322 T.xml(note.getContent(targetFormat="html", 323 macroPackage=self.serManager.table.tableDef)) 324 for tag, note in sorted(self.serManager.notes.items())]]
325
326 - def render_useformatter(self, ctx, data):
327 attrs = ctx.tag.attributes 328 formatVal = attrs["formatter"] 329 if formatVal is None: 330 formatVal = str 331 del ctx.tag.attributes["formatter"] 332 val = formatVal(data) 333 if val is None: 334 val = "N/A" 335 return ctx.tag[val]
336
337 - def _computeHeadCellsStan(self):
338 self.headCells = HeadCells(self.serManager) 339 self.headCellsStan = T.xml(self.headCells.renderSynchronously())
340
341 - def render_headCells(self, ctx, data):
342 """returns the header line for this table as an XML string. 343 """ 344 # The head cells are prerendered and memoized since they might occur 345 # quite frequently in long tables. 346 return ctx.tag[self.headCellsStan]
347
348 - def data_fielddefs(self, ctx, data):
349 return self.table.tableDef.columns
350
351 - def render_meta(self, ctx, data):
352 metaKey = ctx.tag.children[0] 353 if self.table.getMeta(metaKey, propagate=False): 354 ctx.tag.clear() 355 _htmlMetaBuilder.clear() 356 return ctx.tag[self.table.buildRepr(metaKey, _htmlMetaBuilder)] 357 else: 358 return ""
359 360
361 -class HTMLTableFragment(HTMLDataRenderer):
362 """A nevow renderer for result tables. 363 """ 364 rowsPerDivision = 25 365
366 - def _getRowFormatter(self):
367 """returns a callable returning a rendered row in HTML (as used for the 368 stan xml tag). 369 """ 370 source = [ 371 "def formatRow(row, rowAttrs=''):", 372 " res = ['<tr%s>'%rowAttrs]",] 373 for index, (name, _, wantsRow) in enumerate(self.formatterSeq): 374 if wantsRow: 375 source.append(" val = formatters[%d](row)"%index) 376 else: 377 source.append(" val = formatters[%d](row[%s])"%(index, repr(name))) 378 source.extend([ 379 # " import code;code.interact(local=locals())", 380 " if val is None:", 381 " val = 'N/A'", 382 " if isinstance(val, basestring):", 383 " serFct = escapeForHTML", 384 " else:", 385 " serFct = flatten", 386 " res.append('<td>%s</td>'%serFct(val))",]) 387 source.extend([ 388 " res.append('</tr>')", 389 " return ''.join(res)"]) 390 391 return utils.compileFunction("\n".join(source), "formatRow", { 392 "formatters": [p[1] for p in self.formatterSeq], 393 "escapeForHTML": common.escapeForHTML, 394 "flatten": flat.flatten})
395
396 - def render_rowSet(self, ctx, items):
397 # slow, use render_tableBody 398 return ctx.tag(render=rend.mapping)[self.defaultTds]
399
400 - def render_tableBody(self, ctx, data):
401 """returns HTML-rendered table rows in chunks of rowsPerDivision. 402 403 We don't use stan here since we can concat all those tr/td much faster 404 ourselves. 405 """ 406 rowAttrsIterator = itertools.cycle([' class="data"', ' class="data even"']) 407 formatRow = self._getRowFormatter() 408 rendered = [] 409 yield T.xml("<tbody>") 410 for row in self.table: 411 rendered.append(formatRow(row, rowAttrsIterator.next())) 412 if len(rendered)>=self.rowsPerDivision: 413 yield T.xml("\n".join(rendered)) 414 yield self.headCellsStan 415 rendered = [] 416 yield T.xml("\n".join(rendered)+"\n</tbody>")
417 418 docFactory = loaders.stan(T.div(class_="tablewrap")[ 419 T.div(render=T.directive("meta"), class_="warning")["_warning"], 420 T.table(class_="results") [ 421 T.thead(render=T.directive("headCells")), 422 T.tbody(render=T.directive("tableBody"))], 423 T.invisible(render=T.directive("footnotes")), 424 ] 425 )
426 427
428 -class HTMLKeyValueFragment(HTMLDataRenderer, HeadCellsMixin):
429 """A nevow renderer for single-row result tables. 430 """
431 - def data_firstrow(self, ctx, data):
432 return self.table.rows[0]
433
434 - def makeDocFactory(self):
435 return loaders.stan([ 436 T.div(render=T.directive("meta"), class_="warning")["_warning"], 437 T.table(class_="keyvalue", render=rend.mapping, 438 data=T.directive("firstrow")) [ 439 [[T.tr[ 440 T.th(data=colDef, render=T.directive("headCell"), 441 class_="thHorizontal"), 442 td], 443 T.tr(class_="keyvaluedesc")[T.td(colspan=2)[ 444 colDef.description]]] 445 for colDef, td in zip(self.serManager.table.tableDef.columns, 446 self.defaultTds)]], 447 T.invisible(render=T.directive("footnotes")), 448 ])
449 450 docFactory = property(makeDocFactory)
451 452
453 -def writeDataAsHTML(data, outputFile, acquireSamples=False):
454 """writes data's primary table to outputFile. 455 456 (acquireSamples is actually ignored; it is just present for compatibility 457 with the other writers until I rip out the samples stuff altogether). 458 """ 459 if isinstance(data, rsc.Data): 460 data = data.getPrimaryTable() 461 fragment = HTMLTableFragment(data, svcs.emptyQueryMeta) 462 outputFile.write(flat.flatten(fragment))
463 464 465 formats.registerDataWriter("html", writeDataAsHTML, "text/html", "HTML", 466 ".html") 467