galleryMaker.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 import urllib2
4 import urllib
5 import HTMLParser
6 import os, sys
7 import xml.parsers.expat as expat
8 from xml.dom import minidom
9 import datetime
10 import cgi
11 import optparse
12 import codecs
13 import tarfile
14 from collections import OrderedDict
15 # Set the umask so that all files are user and group writable
16 os.umask(002)
17 
18 ############# FUNCTIONS #############
19 # The first part of this file will define functions and create objects that
20 # will aid in web interface.
21 xmlOut = "&outformat=xml" #string we add to URLs to get XML output
22 endl = "\n" # newline character for less (or quicker) typing
23 hendl = "<br>"
24 
25 #password manager object
26 pwMan = urllib2.HTTPBasicAuthHandler()
27 
28 
29 # Function to handle authentication, we need to add a password for every
30 # webpage that we're going to access.
31 def authenticate(urlStr):
32  pwMan.add_password( realm ="PasswordRequired",
33  uri = urlStr,
34  user = 'nova',
35  passwd = pw)
36  opener = urllib2.build_opener(pwMan)
37  urllib2.install_opener(opener)
38 
39 # Pages on DocDB will be behind a password. getPage() will add the password to the manager
40 # and return the webpage as a (web) file object.
41 def getPage(url):
42  authenticate(url)
43  req = urllib2.Request(url)
44  page = urllib2.urlopen(url)
45  return page
46 
47 # FileNBE objects hold the full name, base and extension
48 class FileNBE():
49  def __init__(self, name, base, ext, url, tarPath=None):
50  self.name = name.decode("utf-8")
51  self.base = base.decode("utf-8")
52  self.ext = ext.decode("utf-8")
53  self.url = url.decode("utf-8")
54  self.tarPath = tarPath
55 
56 # Returns a filename given a DOM for a docdb file xml object
57 def getFileNBE(fileDom):
58  # Find out if there is a "description" (for renamed files)
59  descriptions = fileDom.getElementsByTagName("description")
60  if len(descriptions) > 0:
61  fileName = descriptions[0].lastChild.nodeValue
62  #Otherwise use the filename
63  else:
64  fileName = fileDom.getElementsByTagName("name")[0].lastChild.nodeValue
65 
66  url = fileDom.getAttribute("href")
67  baseExt = os.path.splitext(fileName)
68  return FileNBE(fileName, baseExt[0], baseExt[1], url)
69 
70 def loadStatusCache(filePath):
71  cacheFile = open(filePath, 'r')
72  cache = OrderedDict()
73  for line in cacheFile:
74  splitLine = line.strip().split("@")
75  cache[splitLine[0]] = splitLine[1]
76  return cache
77 
79  def __init__(self, topic, thumbWebPath, caption, formats, docURL, docTitle, wrapperWebPath, wrapperPath, fileBaseName):
80  self.topic = topic # Name of docdb topic
81  self.thumbWebPath = thumbWebPath # Location of thumbnail relative to web dir
82  self.caption = caption # Caption from gallery
83  self.formats = formats # list of FileNBEs
84  self.docURL = docURL # URL of docdb entry
85  self.docTitle = docTitle # Title of docdb entry
86  self.wrapperWebPath = wrapperWebPath # Location of wrapper relative to web dir
87  self.wrapperPath = wrapperPath # Location of wrapper relative to root of filesystem
88  self.fileBaseName = fileBaseName # Location of wrapper relative to root of filesystem
89 
90  def makeWrapper(self):
91  wrapper = codecs.open(self.wrapperPath, 'w', 'utf-8')
92  wrapper.write(heading1(self.docTitle))
93  wrapper.write(heading2(self.fileBaseName))
94  wrapper.write(self.formatsBar())
95  wrapper.write(hrule())
96  wrapper.write(hendl)
97  wrapper.write(paragraph(self.caption, fullWidth))
98  wrapper.write('<img src=../"' + self.thumbWebPath + '" style="border-style: none" Title="'+fig.caption+'" />')
99  wrapper.write(hendl)
100  wrapper.write(self.formatsBar())
101  wrapper.write(paragraph("Disclaimer: this .png version of the image is for browsing purposes only. Please find the official versions of this plot through the links above.", fullWidth))
102 
103 
104  wrapper.close()
105 
106 
107  def formatsBar(self):
108  bar = ""
109  bar += ("[" + link("DocDB" , self.docURL) + "] " )
110 
111  for format in self.formats:
112  bar += ("[" + link(format.ext , format.url) + "] " )
113  return bar
114 
116  return """
117  <div class="row">
118  <div class="col-sm-4">
119  """
121  return """
122  </div><!-- /.col-sm-4 -->
123  </div>
124 
125  """
126 
127 # HTML Helper functions
128 def title(str):
129  return "<title>" + str + "</title>" + endl
130 def heading1(str):
131  return "<h1>" + str + "</h1>" + endl
132 def heading2(str):
133  return "<h2>" + str + "</h2>" + endl
134 def heading3(str):
135  return "<h3>" + str + "</h3>"+ endl
136 def heading4(str):
137  return "<h4>" + str + "</h4>" + endl
138 def link(text, url):
139  return '<a href="'+ url + '">' + text + "</a>" + endl
140 def paragraph(str, width=None):
141  p = ""
142  if width: p += '<p style="width:'+width+'px;">'
143  else: p += '<p>'
144 
145  p+= str + "</p>" + endl
146  return p
147 def entry(str):
148  return "<li>" + str + "</li>" + endl
149 def hrule():
150  return '<hr width="100%" align="left" noshade color="#000000">' + endl
151 
152 def hruleWide():
153  return '<hr align="left" noshade color="#000000">' + endl
154 
155 def gridEntry(fig, thumbWidth):
156  #creates table entry with image and text
157  entry = '<td valign="bottom" style="border:1px solid black" >'
158  entry += link('<img src="' + fig.thumbWebPath + '" width="'+ thumbWidth +'" style="border-style: none" Title="'+fig.caption+'" />', fig.wrapperWebPath)
159  entry += '<center>'
160  entry += '<p>'
161  entry += fig.formatsBar()
162  entry += '</p>'
163  entry += '</center>'
164  entry += '</td>'
165 
166  entry += endl
167  return entry
168 
169 def bootStrap():
170  return """
171  <head>
172  <meta charset="utf-8">
173  <meta http-equiv="X-UA-Compatible" content="IE=edge">
174  <meta name="viewport" content="width=device-width, initial-scale=1">
175  <meta name="description" content="">
176  <meta name="author" content="">
177  <link rel="icon" href="../../favicon.ico">
178 
179 
180 
181  <!-- Bootstrap core CSS -->
182  <link href="static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
183 
184  <!-- Custom styles for this template -->
185  <link href="static/bootstrap/css/theme.css" rel="stylesheet">
186 
187 
188 
189  """
190 
191 def bodyOpen():
192  return '<body role="document">'
193 
194 def bodyClose():
195  return """
196  <!-- Bootstrap core JavaScript
197  ================================================== -->
198  <!-- Placed at the end of the document so the pages load faster -->
199  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
200  <script src="static/bootstrap/js/bootstrap.min.js"></script>
201 
202  </body>'
203  """
204 
205 def jumbo(content):
206  t = ' <div class="jumbotron">' + endl
207  t+= " " + content + endl
208  t+= '</div>' + endl
209  return t
210 
211 
212 def navBar(categories=None):
213  t = """
214  <!-- Fixed navbar -->
215  <div class="navbar navbar-default navbar-fixed-top" role="navigation">
216  <div class="container">
217  <div class="navbar-header">
218  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
219  <span class="sr-only">Toggle navigation</span>
220  <span class="icon-bar"></span>
221  <span class="icon-bar"></span>
222  <span class="icon-bar"></span>
223  </button>
224  <a class="navbar-brand" href="#">Blessed Plots</a>
225  </div>
226  <div class="navbar-collapse collapse">
227  <ul class="nav navbar-nav">
228  """
229  t+= ' <li>' + link("Grid View", "sortgrid.html") + '</li>' + endl
230  t+= ' <li>' + link("Unsorted GridView", "grid.html") + '</li>' + endl
231  t+= ' <li>' + link("Captioned View", "gallery.html") + '</li>' + endl
232  if categories:
233  t+= """
234  <li class="dropdown">
235  <a href="#" class="dropdown-toggle" data-toggle="dropdown">Categories <span class="caret"></span></a>
236  <ul class="dropdown-menu" role="menu">
237 
238  """
239  for cat in categories:
240  t +=' <li><a href="#' + cat.strip().replace(" ", "").lower() + '">' + cat + '</a></li>' + endl
241  t+="""
242  </ul>
243  </li>
244  """
245  t+= """
246  </ul>
247  </div><!--/.nav-collapse -->
248  </div>
249  </div>
250  """
251  return t
252 
253 
254 """
255  <!-- Fixed navbar -->
256  <div class="navbar navbar-default navbar-fixed-top" role="navigation">
257  <div class="container">
258  <div class="navbar-header">
259  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
260  <span class="sr-only">Toggle navigation</span>
261  <span class="icon-bar"></span>
262  <span class="icon-bar"></span>
263  <span class="icon-bar"></span>
264  </button>
265  <a class="navbar-brand" href="#">Project name</a>
266  </div>
267  <div class="navbar-collapse collapse">
268 
269  </ul>
270  </div><!--/.nav-collapse -->
271  </div>
272  </div>
273 """
274 
275 
276 ############# CONFIGURABLE PARAMETERS #############
277 webPath = "/nusoft/app/web/htdoc/nova/blessedplots/"
278 officialURL = "http://nova-docdb.fnal.gov:8080/cgi-bin/Search?topics=370" + xmlOut
279 thumbsDir = "thumbs/" #name of directory for thumbnails
280 wrappersDir = "wrappers/" #name of directory for thumbnails
281 thumbsPath = webPath + thumbsDir
282 wrappersPath = webPath + wrappersDir
283 tempDir = "temp/" #name of directory for thumbnails
284 tempPath = webPath + tempDir
285 thumbSize = str(225)
286 pageTitle = "NOvA Blessed Plots and Figures Gallery"
287 gridTitle = "NOvA Blessed Plots and Figures Grid"
288 fullWidth = str(1000)
289 #Get NOvA DocDB password from the hidden file, you can do it some other way
290 pw = open(os.environ['NOVADOCPWDFILE'], 'r').readlines()[0].strip()
291 
292 ############# MAIN EXECUTIING BODY #############
293 
294 optParser = optparse.OptionParser()
295 optParser.add_option("--nothumbs", action="store_true",
296  help="Do not remake the thumbnails, runs more quickly. This is pretty dangerous, could lead to borked HTML pages. Only meant to debug HTML writing.")
297 optParser.add_option("--nocaps", action="store_true",
298  help="Do not download the captions, runs more quickly. Only meant to debug HTML writing.")
299 optParser.add_option("--nocache", action="store_true",
300  help="Ignore the fact that the cached version is current, run anyway")
301 
302 
303 (options, args) = optParser.parse_args()
304 #Open the Topics page for "Official" blessed plots
305 # This is how we access the plots we want, they must be in the "Blessed Plots:Official" topic
306 officialPage = getPage(officialURL)
307 
308 # Parse the xml output and find the document tags
309 officialDom = minidom.parse(officialPage)
310 documents = officialDom.getElementsByTagName("document")
311 # The DOM object will return all of the XML objects with a "document" tag.
312 # We can then loop over the documents.
313 
314 # We will first have a loop to compare the state of the page to a chached version.
315 # This will allow us to decide whether or not we actually need to run the page again
316 statusPath = webPath + "status.txt"
317 cacheStatus = loadStatusCache(statusPath)
318 status = OrderedDict()
319 for doc in documents:
320  id = doc.getAttribute("id")
321  rev = doc.getElementsByTagName("docrevision")[0]
322  mod = rev.getAttribute("modified")
323  status[id] = mod
324 
325 changed = False # Assume page has not changed until we prove it has
326 for id in status.keys():
327  if id in cacheStatus.keys():
328  if not cacheStatus[id] == status[id]:
329  changed = True # because the mod time has changed
330  else:
331  changed = True # because there is a new id in the status
332 # Last thing to check is whether or not we removed any documents
333 for id in cacheStatus.keys():
334  if not id in status.keys():
335  changed = True # because
336 
337 if not changed and not options.nocache:
338  print "The page has not changed, exiting"
339  os._exit(0)
340 # write the status to a file
341 statusFile = open(statusPath, 'w')
342 for key in status.keys():
343  statusFile.write(key + "@" + status[key] + endl)
344 statusFile.close()
345 # Our first task will be to generate a dictionary to map topics to documents
346 # The dictionary will map topic name strings to lists DOM(xml parsed) objects
347 topicMap = dict()
348 for doc in documents:
349  # Get the link for the document
350  docURL = doc.getAttribute("href") + xmlOut
351  print "DOC ID : ", doc.getAttribute("id")
352  # Load the xml page for that document and parse it to extract topics.
353  docPage = getPage(docURL)
354  docDom = minidom.parse(docPage)
355  topics = docDom.getElementsByTagName("topic")
356 
357  topicNames = []
358  #Get all the topic names for this document
359  for topic in topics:
360  topicNames.append(topic.getElementsByTagName("name")[0].lastChild.nodeValue)
361 
362  #skip this doc if it's not official, this is just a double check for sanity
363  if not "Official" in topicNames:
364  continue
365 
366  #Loop over topics to add urls to topicMap
367  for topic in topics:
368  # Access the name tag for each topic
369  nameDom = topic.getElementsByTagName("name")
370  # Read out the name
371  name = nameDom[0].lastChild.nodeValue
372  print name
373  # Don't make entries in the map for "Blessed Plots" or "Official" topics.
374  # Hopefully these are the only ones worth special casing
375  if name == "Blessed Plots" or name == "Official":
376  continue
377 
378  #Create an entry in the topic map if that topic isn't there yet
379  if not name in topicMap.keys():
380  topicMap[name] = []
381  # append the document URL to the list for that topic
382  topicMap[name].append(docDom.getElementsByTagName("document")[0])
383 
384 print "###########################################################"
385 
386 topics = topicMap.keys()
387 topics.sort()
388 
389 
390 # Now we will begin to write the HTML. As we do it, we will convert files to thumbnails.
391 
392 gallery = codecs.open(webPath + "gallery_new.html", 'w', "utf-8")
393 
394 
395 if not options.nothumbs:
396  # clear out the temp and tumbnail directory, but only if we plan on making them again
397  os.system("rm -f " + tempPath + "*")
398  os.system("rm -f " + thumbsPath + "*")
399 
400 # First we write the page heading
401 gallery.write(bootStrap())
402 gallery.write(navBar(topics))
403 gallery.write(bodyOpen())
404 gallery.write("<title>" + pageTitle + "</title>")
405 gallery.write(jumbo(heading1(pageTitle) + heading2("Captioned View" +endl)) )
406 gallery.write(paragraph("To find out how to add a plot to this page, read this " + link("guide", "howto.html") + "."))
407 
408 
409 gallery.write('<a name="toc"> </a>')
410 gallery.write(heading2("Categories"))
411 gallery.write("<ul>")
412 #Loop over topics to make Table of Contents
413 for topic in topics:
414  gallery.write(entry(link(topic + hendl, "gallery.html#" + topic.strip().replace(" ", "").lower())))
415 gallery.write("</ul>")
416 
417 gallery.write(hrule())
418 savedFigs = [] # flat list of saved figures, used for grid view below
419 savedFigsByTopic = OrderedDict()
420 #Loop over topics to make the actual body
421 for topic in topics:
422  print topic
423  savedFigsByTopic[topic] = []
424  #Make an anchor and a header
425  gallery.write('<a name="' + topic.strip().replace(" ", "").lower() + '"> </a>')
426  gallery.write(heading2(hendl))
427  gallery.write(hrule())
428  gallery.write(heading2(topic) + hrule() + endl)
429  #Start a table
430  gallery.write('<table border=0 class="fixed" cellpadding="20"> <col width="' + thumbSize + 'px" /> <col width="740px" />' + endl)
431  ## Loop over docs in each topic
432  for docDom in topicMap[topic]:
433  #Get the files in each doc
434  docId = docDom.getAttribute("id")
435  docURL = docDom.getAttribute("href")
436  docTitle = docDom.getElementsByTagName("title")[0].lastChild.nodeValue
437  files = docDom.getElementsByTagName("file")
438 
439  ## Construct list of files from DOM objects
440  nbes = [ getFileNBE(fileDom) for fileDom in files]
441 
442 
443  # There can also be documents with all the files in one "Document Archive"
444  for fileDom in files:
445  fileNBE = getFileNBE(fileDom)
446  if fileNBE.name == 'Document Archive':
447  arcUrl = fileDom.getAttribute("href")
448  tempName = tempPath + 'docArchive.tar.gz'
449  open(tempName, 'w').write(getPage(arcUrl).read())
450  tar = tarfile.open(tempName, 'r')
451  for name in tar.getnames():
452  base, ext = os.path.splitext(name)
453  # Skip any strange files in the archive
454  if "._" in base: continue
455 
456  # Direct links to the files still work, even though they're inside
457  # the tarball.
458 
459  url = 'http://nova-docdb.fnal.gov:8080/cgi-bin/RetrieveFile?docid='+docId+'&filename='+name
460  nbes.append(FileNBE(name, base, ext, url, tempName))
461 
462 
463  ## loop over files
464  for fileNBE in nbes:
465  if fileNBE.ext == ".txt":
466  foundOnceAlready = False # for now, will reset later
467  print "Found CAPTION : ", fileNBE.name, fileNBE.base, docId
468  #### THUMBNAIL BLOCK: We need to make a thumbnail for each
469  shortName = "doc" + docId + "_" + fileNBE.base
470  thumbName = shortName + ".png"
471  thumbPath = thumbsPath + thumbName
472  thumbWebPath = thumbsDir + thumbName # location of thumbnail, relative to the page directory
473  wrapperName = shortName + ".html"
474  wrapperPath = wrappersPath + wrapperName
475  wrapperWebPath = wrappersDir + wrapperName
476 
477  if not os.path.isfile(thumbPath) and not options.nothumbs:# Check to see if the thumbnail exists , or if we asked to not make thumbnails
478  print "MAKING THUMBNAIL "
479  for otherFileNBE in nbes:
480  # Create the thumbnail based on the first file with the same base and not .txt extension
481  if otherFileNBE.base == fileNBE.base and not otherFileNBE.ext == ".txt":
482  # Then we make the caption by downloading the file to temp and converting to .png in thumb
483  imgUrl = otherFileNBE.url
484  tempName = tempPath + otherFileNBE.name # name of file used to produce thumbnail
485  if otherFileNBE.tarPath:
486  tar = tarfile.open(otherFileNBE.tarPath, 'r')
487  tar.extract(otherFileNBE.name, tempPath)
488  else:
489  open(tempName, 'w').write(getPage(imgUrl).read())
490  os.system("convert -trim -resize " + fullWidth + " " + tempName + " " + thumbPath) # resize to something reasonable
491  continue # Because we have the thumbnail now
492  else:
493  print "THUMBNAIL ALREADY EXISTS"
494  foundOnceAlready = (True and not options.nothumbs)
495  #Now we have all the tools to write the table
496 
497  gallery.write("<tr>") #opens table row
498  gallery.write('<td>' + link('<img src="' + thumbWebPath + '" width="'+ thumbSize +'" style="border-style: none"/ >', wrapperWebPath) + '</td>' ) #creates table entry with image
499  gallery.write('<td valign="top">')
500  gallery.write("<p>")
501  gallery.write(heading3(docTitle)) # writes document title in cell
502  gallery.write(heading4(fileNBE.base))
503  gallery.write(link("[DocDB]", docURL) + " " )
504  formats = []
505  for otherFileNBE in nbes:
506  if otherFileNBE.base == fileNBE.base and not otherFileNBE.ext == ".txt":
507  gallery.write("[" + link(otherFileNBE.ext , otherFileNBE.url) + "] " )
508  formats.append(otherFileNBE)
509 
510  gallery.write("</p>")
511  if not options.nocaps:
512  if fileNBE.tarPath:
513  capFile = tarfile.open(fileNBE.tarPath, 'r')\
514  .extractfile(fileNBE.name)
515  else:
516  capFile = getPage(fileNBE.url)
517  caption = cgi.escape(str(capFile.read()).decode(encoding="utf-8", errors='ignore')).replace("\n", "<br> \n").replace("\t", "&emsp;") # cgi.escape() turns characters into HTML safe ones
518  else:
519  caption = "No caption option enabled. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
520  gallery.write("<p>")
521  gallery.write(caption) # write the caption
522  gallery.write("</p>")
523  gallery.write(' </td>' ) #closes table entry
524  gallery.write("</tr>")
525 
526  # Make a saved figure descriptor with all information necessary later
527  fig = SavedFigure(topic, thumbWebPath, caption, formats, docURL, docTitle, wrapperWebPath, wrapperPath, fileNBE.base)
528  fig.makeWrapper()
529 
530  savedFigsByTopic[topic].append(fig) # Add plot to the saved topic map for sorted grid view
531  if not foundOnceAlready:
532  savedFigs.append(fig) # Add plot to the flat list for grid view
533 
534 
535  gallery.write("</table>" + endl)
536  gallery.write("</section>")
537  gallery.write(link("Return to Table of Categories", "gallery.html#toc"))
538 
539 gallery.write(paragraph("Page generated at " + datetime.datetime.now().strftime("%Y/%m/%d at %H:%M")))
540 gallery.write(bodyClose())
541 gallery.close()
542 
543 grid = codecs.open(webPath + "grid_new.html", 'w', "utf-8")
544 grid.write(bootStrap())
545 grid.write("<title>" + pageTitle + "</title>"+endl)
546 grid.write(jumbo(heading1(pageTitle) + heading2("Unsorted Grid View" +endl)))
547 grid.write(hrule())
548 
549 grid.write(navBar(topics))
550 
551 grid.write(hrule())
552 grid.write(paragraph("To find out how to add a plot to this page, read this " + link("guide", "howto.html") + ".") + endl)
553 
554 # Set the number of columns and column width
555 gridNumCols = 4
556 gridThumbWidth = str(240)
557 
558 grid.write('<table style="border:1px solid black;border-collapse:collapse;" class="fixed" cellpadding="5"> ' + endl)
559 for i in range(0, gridNumCols):
560  grid.write(' <col width="' + thumbSize + 'px" /> ' + endl)
561 
562 grid.write("<tr>"+endl) #opens table row
563 currentCell = 1
564 for fig in savedFigs:
565  grid.write(gridEntry(fig, gridThumbWidth)) #creates table entry with image
566 
567  if currentCell == gridNumCols:
568  grid.write("</tr>"+endl) # closes current table row
569  grid.write("<tr>"+endl) # opens new table row
570  currentCell = 0
571  currentCell += 1
572 
573 grid.write("</tr>"+endl) # close last table row
574 grid.write("</table>"+endl) # close table
575 
576 grid.write(paragraph("Page generated at " + datetime.datetime.now().strftime("%Y/%m/%d at %H:%M")))
577 grid.write(bodyClose())
578 grid.close()
579 
580 sGrid = codecs.open(webPath + "sortgrid_new.html", 'w', "utf-8")
581 sGrid.write(bootStrap())
582 sGrid.write(bodyOpen())
583 sGrid.write("<title>" + pageTitle + "</title>"+endl)
584 sGrid.write(jumbo(heading1(pageTitle) + heading2("Grid View") ) )
585 sGrid.write(hrule())
586 sGrid.write(navBar(topics))
587 sGrid.write(hrule())
588 sGrid.write(paragraph("To find out how to add a plot to this page, read this " + link("guide", "howto.html") + "."))
589 
590 sGrid.write(heading2("Categories"))
591 
592 sGrid.write("<ul>")
593 #Loop over topics to make Table of Contents
594 for topic in topics:
595  sGrid.write(entry(link(topic + hendl, "sortgrid.html#" + topic.strip().replace(" ", "").lower())))
596 sGrid.write("</ul>")
597 
598 for topic in topics:
599  #Make an anchor and a header
600  sGrid.write('<a name="' + topic.strip().replace(" ", "").lower() + '"> </a>')
601  sGrid.write(heading2(hendl))
602  sGrid.write(hrule())
603  sGrid.write(heading2(topic) + hrule() + endl)
604  #Start a table
605  sGrid.write('<table style="border:1px solid black;border-collapse:collapse;" class="fixed" cellpadding="5"> ' + endl)
606  for i in range(0, gridNumCols):
607  sGrid.write(' <col width="' + thumbSize + 'px" /> ' + endl)
608 
609  sGrid.write("<tr>"+endl) #opens table row
610  currentCell = 1
611  for fig in savedFigsByTopic[topic]:
612  sGrid.write(gridEntry(fig, gridThumbWidth)) #creates table entry with image
613 
614  if currentCell == gridNumCols:
615  sGrid.write("</tr>"+endl) # closes current table row
616  sGrid.write("<tr>"+endl) # opens new table row
617  currentCell = 0
618  currentCell += 1
619 
620  sGrid.write("</tr>"+endl) # close last table row
621  sGrid.write("</table>"+endl) # close table
622 
623 
624 
625 sGrid.write(paragraph("Page generated at " + datetime.datetime.now().strftime("%Y/%m/%d at %H:%M")))
626 
627 sGrid.write(bodyClose())
628 sGrid.close()
629 
630 os.system("mv -f " + webPath + "gallery_new.html " + webPath + "gallery.html")
631 os.system("mv -f " + webPath + "grid_new.html " + webPath + "grid.html")
632 os.system("mv -f " + webPath + "sortgrid_new.html " + webPath + "sortgrid.html")
633 
634 
void split(double tt, double *fr)
def navBar(categories=None)
def heading4(str)
def authenticate(urlStr)
Definition: galleryMaker.py:31
def heading1(str)
def link(text, url)
def getFileNBE(fileDom)
Definition: galleryMaker.py:57
def panelRowOpen()
def entry(str)
write
Run ND cosmics.
def __init__(self, name, base, ext, url, tarPath=None)
Definition: galleryMaker.py:49
def jumbo(content)
def paragraph(str, width=None)
def panelRowClose()
def gridEntry(fig, thumbWidth)
void decode(boost::any const &a, InputTag &tag)
procfile open("FD_BRL_v0.txt")
def heading2(str)
def title(str)
def getPage(url)
Definition: galleryMaker.py:41
def __init__(self, topic, thumbWebPath, caption, formats, docURL, docTitle, wrapperWebPath, wrapperPath, fileBaseName)
Definition: galleryMaker.py:79
def loadStatusCache(filePath)
Definition: galleryMaker.py:70
def heading3(str)