From aa5e8cdc52ec8d520de73d95549591c7162781be Mon Sep 17 00:00:00 2001
From: Antoine Durand-Gasselin
Date: Wed, 4 Mar 2009 17:00:04 +0100
Subject: [PATCH] [wiki-etch] suppression
darcs-hash:20090304160004-bd074-7e2630d385522d14ea3ad6a52ad0669b2ddc1b2e.gz
---
wiki/PageGraphicalEditor.py | 365 ---
wiki/action/gallery2image.py | 398 ---
wiki/action/opensearch.py | 83 -
wiki/dump-wiki.py | 200 --
wiki/formatter/text_latex.py | 340 ---
wiki/formatter/text_latex_test.py | 348 ---
wiki/formatter/text_plain.py | 254 --
wiki/macro/EtatCommutationSecours.py | 26 -
wiki/macro/EtatSecours.py | 25 -
wiki/macro/Gallery.py | 746 ------
wiki/macro/HostStatus.py | 56 -
wiki/macro/ImageLink.py | 173 --
wiki/macro/InfosBornes.py | 89 -
wiki/macro/MonitStatus.py | 275 ---
wiki/macro/PagesClubs.py | 19 -
wiki/macro/PagesPerso.py | 104 -
wiki/macro/ProgressBar.py | 225 --
wiki/macro/Questionnaire.py | 142 --
wiki/macro/RandomIncludeQuote.py | 244 --
wiki/macro/RandomPageInclude.py | 219 --
wiki/macro/RandomQuoteNum.py | 57 -
wiki/macro/TV.py | 93 -
wiki/macro/TableOfContents.py | 186 --
wiki/macro/TerminalSsh.py | 53 -
wiki/macro/YouTube.py | 27 -
wiki/macro/latex.py | 44 -
wiki/mail.py | 181 --
wiki/parser/Box.py | 119 -
wiki/parser/EXIF.py | 1197 ---------
wiki/parser/Portail.py | 134 -
wiki/parser/__init__.py | 5 -
wiki/parser/latex.py | 214 --
wiki/request.py | 2164 -----------------
.../blackliste/css/blackliste-clean.css | 17 -
.../common/EventCalendar/EventCalendar.css | 452 ----
wiki/static/common/boxes/boxes.css | 103 -
wiki/static/common/pagesPersos.css | 43 -
wiki/static/common/toc/toc.css | 60 -
wiki/static/common/toc/toc.js | 62 -
wiki/static/crans-www/css/common.css | 329 ---
wiki/static/crans-www/css/layout.css | 163 --
wiki/static/crans-www/css/print.css | 52 -
wiki/static/crans-www/css/projection.css | 90 -
wiki/static/crans-www/css/rtl.css | 202 --
wiki/static/crans-www/css/screen.css | 112 -
wiki/static/crans/css/bde.css | 44 -
wiki/static/crans/css/common.css | 347 ---
wiki/static/crans/css/crans.css | 48 -
wiki/static/crans/css/ensanime.css | 136 --
wiki/static/crans/css/federez.css | 56 -
wiki/static/crans/css/layout.css | 329 ---
wiki/static/crans/css/movieclub.css | 116 -
wiki/static/crans/css/print.css | 54 -
wiki/static/crans/css/projection.css | 90 -
wiki/static/crans/css/rtl.css | 202 --
wiki/static/crans/js/scroller.js | 69 -
wiki/theme/__init__.py | 5 -
wiki/theme/bde.py | 35 -
wiki/theme/crans-www.py | 513 ----
wiki/theme/crans.py | 168 --
wiki/theme/ensanime.py | 23 -
wiki/theme/federez-www.py | 507 ----
wiki/theme/federez.py | 39 -
wiki/theme/movieclub.py | 31 -
wiki/user.py | 997 --------
wiki/userform.py | 765 ------
wiki/wikiacl.py | 378 ---
67 files changed, 15442 deletions(-)
delete mode 100644 wiki/PageGraphicalEditor.py
delete mode 100644 wiki/action/gallery2image.py
delete mode 100644 wiki/action/opensearch.py
delete mode 100755 wiki/dump-wiki.py
delete mode 100644 wiki/formatter/text_latex.py
delete mode 100644 wiki/formatter/text_latex_test.py
delete mode 100644 wiki/formatter/text_plain.py
delete mode 100644 wiki/macro/EtatCommutationSecours.py
delete mode 100644 wiki/macro/EtatSecours.py
delete mode 100644 wiki/macro/Gallery.py
delete mode 100644 wiki/macro/HostStatus.py
delete mode 100644 wiki/macro/ImageLink.py
delete mode 100755 wiki/macro/InfosBornes.py
delete mode 100644 wiki/macro/MonitStatus.py
delete mode 100644 wiki/macro/PagesClubs.py
delete mode 100644 wiki/macro/PagesPerso.py
delete mode 100644 wiki/macro/ProgressBar.py
delete mode 100755 wiki/macro/Questionnaire.py
delete mode 100644 wiki/macro/RandomIncludeQuote.py
delete mode 100644 wiki/macro/RandomPageInclude.py
delete mode 100644 wiki/macro/RandomQuoteNum.py
delete mode 100644 wiki/macro/TV.py
delete mode 100644 wiki/macro/TableOfContents.py
delete mode 100644 wiki/macro/TerminalSsh.py
delete mode 100644 wiki/macro/YouTube.py
delete mode 100644 wiki/macro/latex.py
delete mode 100644 wiki/mail.py
delete mode 100644 wiki/parser/Box.py
delete mode 100644 wiki/parser/EXIF.py
delete mode 100644 wiki/parser/Portail.py
delete mode 100644 wiki/parser/__init__.py
delete mode 100644 wiki/parser/latex.py
delete mode 100644 wiki/request.py
delete mode 100644 wiki/static/blackliste/css/blackliste-clean.css
delete mode 100644 wiki/static/common/EventCalendar/EventCalendar.css
delete mode 100644 wiki/static/common/boxes/boxes.css
delete mode 100644 wiki/static/common/pagesPersos.css
delete mode 100644 wiki/static/common/toc/toc.css
delete mode 100644 wiki/static/common/toc/toc.js
delete mode 100644 wiki/static/crans-www/css/common.css
delete mode 100644 wiki/static/crans-www/css/layout.css
delete mode 100644 wiki/static/crans-www/css/print.css
delete mode 100644 wiki/static/crans-www/css/projection.css
delete mode 100644 wiki/static/crans-www/css/rtl.css
delete mode 100644 wiki/static/crans-www/css/screen.css
delete mode 100644 wiki/static/crans/css/bde.css
delete mode 100644 wiki/static/crans/css/common.css
delete mode 100644 wiki/static/crans/css/crans.css
delete mode 100644 wiki/static/crans/css/ensanime.css
delete mode 100644 wiki/static/crans/css/federez.css
delete mode 100644 wiki/static/crans/css/layout.css
delete mode 100644 wiki/static/crans/css/movieclub.css
delete mode 100644 wiki/static/crans/css/print.css
delete mode 100644 wiki/static/crans/css/projection.css
delete mode 100644 wiki/static/crans/css/rtl.css
delete mode 100644 wiki/static/crans/js/scroller.js
delete mode 100644 wiki/theme/__init__.py
delete mode 100644 wiki/theme/bde.py
delete mode 100644 wiki/theme/crans-www.py
delete mode 100644 wiki/theme/crans.py
delete mode 100644 wiki/theme/ensanime.py
delete mode 100644 wiki/theme/federez-www.py
delete mode 100644 wiki/theme/federez.py
delete mode 100644 wiki/theme/movieclub.py
delete mode 100644 wiki/user.py
delete mode 100644 wiki/userform.py
delete mode 100644 wiki/wikiacl.py
diff --git a/wiki/PageGraphicalEditor.py b/wiki/PageGraphicalEditor.py
deleted file mode 100644
index e60205cf..00000000
--- a/wiki/PageGraphicalEditor.py
+++ /dev/null
@@ -1,365 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
- MoinMoin - Call the GUI editor (FCKeditor)
-
- @copyright: (c) Bastian Blank, Florian Festi, Thomas Waldmann
- @license: GNU GPL, see COPYING for details.
-"""
-
-from MoinMoin import PageEditor
-import os, time, codecs, re
-
-#### HACK SAUVAGE 1/2
-from MoinMoin import caching, config, user, util, wikiutil, error, search
-#### FIN DU HACK 1/2
-from MoinMoin.Page import Page
-from MoinMoin.widget import html
-from MoinMoin.widget.dialog import Status
-from MoinMoin.logfile import editlog, eventlog
-from MoinMoin.util import filesys
-import MoinMoin.util.web
-import MoinMoin.util.mail
-from MoinMoin.parser.wiki import Parser
-
-from StringIO import StringIO
-
-def execute(pagename, request):
- if not request.user.may.write(pagename):
- _ = request.getText
- Page(request, pagename).send_page(request,
- msg = _('You are not allowed to edit this page.'))
- return
-
- PageGraphicalEditor(request, pagename).sendEditor()
-
-
-class PageGraphicalEditor(PageEditor.PageEditor):
-
- def word_rule(self):
- regex = re.compile(r"\(\?" + edit_lock_message
- else:
- msg = edit_lock_message
-
- #### HACK SAUVAGE 2/2
- query = search.QueryParser().parse_query(u'CatégorieEditeurGraphiqueInterdit')
- page = search.Page(request, self.page_name)
- if query.search(page):
- msg = _('You cannot use the graphical editor on this page.')
- #### FIN HACK 2/2
-
- # Did one of the prechecks fail?
- if msg:
- self.send_page(self.request, msg=msg)
- return
-
- # Check for preview submit
- if preview is None:
- title = _('Edit "%(pagename)s"')
- else:
- title = _('Preview of "%(pagename)s"')
- self.set_raw_body(preview, modified=1)
-
- # send header stuff
- lock_timeout = self.lock.timeout / 60
- lock_page = wikiutil.escape(self.page_name, quote=1)
- lock_expire = _("Your edit lock on %(lock_page)s has expired!") % {'lock_page': lock_page}
- lock_mins = _("Your edit lock on %(lock_page)s will expire in # minutes.") % {'lock_page': lock_page}
- lock_secs = _("Your edit lock on %(lock_page)s will expire in # seconds.") % {'lock_page': lock_page}
-
- # get request parameters
- try:
- text_rows = int(form['rows'][0])
- except StandardError:
- text_rows = self.cfg.edit_rows
- if self.request.user.valid:
- text_rows = int(self.request.user.edit_rows)
-
- if preview is not None:
- # Propagate original revision
- rev = int(form['rev'][0])
-
- # Check for editing conflicts
- if not self.exists():
- # page does not exist, are we creating it?
- if rev:
- conflict_msg = _('Someone else deleted this page while you were editing!')
- elif rev != self.current_rev():
- conflict_msg = _('Someone else changed this page while you were editing!')
- if self.mergeEditConflict(rev):
- conflict_msg = _("""Someone else saved this page while you were editing!
-Please review the page and save then. Do not save this page as it is!
-Have a look at the diff of %(difflink)s to see what has been changed.""") % {
- 'difflink': self.link_to(self.request,
- querystr='action=diff&rev=%d' % rev)
- }
- rev = self.current_rev()
- if conflict_msg:
- # We don't show preview when in conflict
- preview = None
-
- elif self.exists():
- # revision of existing page
- rev = self.current_rev()
- else:
- # page creation
- rev = 0
-
- # Page editing is done using user language
- self.request.setContentLanguage(self.request.lang)
-
- # Setup status message
- status = [kw.get('msg', ''), conflict_msg, edit_lock_message]
- status = [msg for msg in status if msg]
- status = ' '.join(status)
- status = Status(self.request, content=status)
-
- wikiutil.send_title(self.request,
- title % {'pagename': self.split_title(self.request),},
- page=self,
- pagename=self.page_name, msg=status,
- html_head=self.lock.locktype and (
- PageEditor._countdown_js % {
- 'countdown_script': self.request.theme.externalScript('countdown'),
- 'lock_timeout': lock_timeout,
- 'lock_expire': lock_expire,
- 'lock_mins': lock_mins,
- 'lock_secs': lock_secs,
- }) or '',
- editor_mode=1,
- )
-
- self.request.write(self.request.formatter.startContent("content"))
-
- # Get the text body for the editor field.
- # TODO: what about deleted pages? show the text of the last revision or use the template?
- if preview is not None:
- raw_body = self.get_raw_body()
- elif self.exists():
- # If the page exists, we get the text from the page.
- # TODO: maybe warn if template argument was ignored because the page exists?
- raw_body = self.get_raw_body()
- elif form.has_key('template'):
- # If the page does not exists, we try to get the content from the template parameter.
- template_page = wikiutil.unquoteWikiname(form['template'][0])
- if self.request.user.may.read(template_page):
- raw_body = Page(self.request, template_page).get_raw_body()
- if raw_body:
- self.request.write(_("[Content of new page loaded from %s]") % (template_page,), ' ')
- else:
- self.request.write(_("[Template %s not found]") % (template_page,), ' ')
- else:
- self.request.write(_("[You may not read %s]") % (template_page,), ' ')
-
- # Make backup on previews - but not for new empty pages
- if preview and raw_body:
- self._make_backup(raw_body)
-
- # Generate default content for new pages
- if not raw_body:
- raw_body = _('Describe %s here.') % (self.page_name,)
-
- # send form
- self.request.write('
")
-
- badwords_re = None
- if preview is not None:
- if SpellCheck and (
- form.has_key('button_spellcheck') or
- form.has_key('button_newwords')):
- badwords, badwords_re, msg = SpellCheck.checkSpelling(self, self.request, own_form=0)
- self.request.write("
%s
" % msg)
- self.request.write('')
- self.request.write("")
-
-
- if preview is not None:
- if staytop:
- content_id = 'previewbelow'
- else:
- content_id = 'preview'
- self.send_page(self.request, content_id=content_id, content_only=1,
- hilite_re=badwords_re)
-
- self.request.write(self.request.formatter.endContent()) # end content div
- wikiutil.send_footer(self.request, self.page_name)
-
diff --git a/wiki/action/gallery2image.py b/wiki/action/gallery2image.py
deleted file mode 100644
index 44710dad..00000000
--- a/wiki/action/gallery2image.py
+++ /dev/null
@@ -1,398 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
- MoinMoin - gallery2Image Actionmacro
-
- PURPOSE::
- This action macro is used to rotate, move to bak or to slide through the images from Gallery2
-
- CALLING SEQUENCE::
- called by Gallery2 POST Method
-
- PROCEDURE::
- see Gallery2
-
- Please remove the version number from this file
-
- RESTRICTIONS::
- spaces in file names are not supported. I don't know how to escape them right. Probaly this does work in 1.3.5
-
- MODIFICATION HISTORY::
- Version 1.3.3.-1
- @copyright: 2005 by Reimar Bauer (R.Bauer@fz-juelich.de)
- @license: GNU GPL, see COPYING for details.
- 2005-06-24: 1.3.3.-2 feature reqeust of CraigJohnson added
- os.path.join used to join platform independent pathes
- os.unlink removed to get it more platform independend
- 2005-08-06: 1.3.5-3 RB VS mode added
- by one step back or forward could be toggled through the selected slides
- and the first and last one could be selected too
- 2005-08-07 1.3.5-4 RB bug fixed for cgi-bin call. formatting of tables adjusted
- 2005-08-13 1.3.5-5 RB code change from GET to POST
- forms instead of link
- toggle between webnail and image by click on image
- alias (description) and exif_date added
- this version needs Gallery2-1.3.5-7.py
- 2005-08-31 1.3.5-6 RB html code changed into a function :-)
- some html bugs fixed too
- instead of button text now images used (disabled buttons are grey color coded)
- back link to callers page
- whole page inserted into the common wiki page
- code clean up.
-
-
-
-
-"""
-Dependencies = []
-import os,string,Image,StringIO
-from MoinMoin import config, wikiutil
-from MoinMoin.PageEditor import PageEditor
-from MoinMoin import user, util
-from MoinMoin.Page import Page
-from MoinMoin.action import AttachFile
-from MoinMoin.formatter.text_html import Formatter
-from MoinMoin.parser import wiki
-
-def html_show_image(request,pagename,url_wiki_page,full,alias,exif_date,target,idx):
-
- n = len(target)
- last_disabled = ''
- last_status = ''
- first_disabled = ''
- first_status = ''
- previous = idx - 1
- next = idx + 1
-
- if idx == n-1 :
- last_disabled = 'disabled'
- last_status = '_disabled'
-
- if idx == 0 :
- first_disabled = 'disabled'
- first_status = '_disabled'
-
-
- if previous < 0 :
- previous = 0
-
- if next > n - 1 :
- next = n - 1
-
- if n == 1 :
- next = 0
- # previous = 0
- # first_disabled = 'disabled'
- # first_status = '_disabled'
- # last_disabled = 'disabled'
- # last_status = '_disabled'
-
- html = '''
-
-Ce site est une copie statique et partielle de ce que l'on peut trouver sur le
-wiki de l'association. Si vous êtes ici, alors
-que vous avez demandé un autre site, c'est sans doute que vous êtes connecté
-au réseau wifi de l'association mais que vous n'avez pas encore complété toutes
-les étapes nécessaires pour avoir une connexion pleinement fonctionnelle. Ce site
-contient donc les infos pour configurer correctement votre connexion.
-
-
-
%(pagenamewithlinks)s
-
-%(pagehtml)s
-
-
-Cette page a été extraite du wiki le %(timestamp)s. Vous pouvez l'éditer ou voir la page originale.
-
Upload some images as attachments to '+Globs.pagename+' and I will generate a gallery for you.')
-
-# Thanks to dennyece.arizona.edu
-# This can be replaced with a static translation table to speed things up (later)
-def mktrans():
- # Allow only letters and digits and a few other valid file characters
- alphanumeric=string.letters+string.digits+'.,-_\'!"'
- source_string=""
- destination_string=""
- for i in range(256):
- source_string=source_string+chr(i)
- if chr(i) in alphanumeric:
- destination_string=destination_string+chr(i)
- else:
- destination_string=destination_string+' '
- return string.maketrans(source_string,destination_string)
-
-def qlink(pagename, querystring, query, description=''):
- # Returns a hyperlink constructed as a form query on pagename
- if not description:
- description=query
- return ''+description+''
-
-def navibar(target,querystring):
- # Returns a navigational bar with PREV,THUMBS,NEXT
- positions=Globs.originals.keys()
- positions.sort()
- # Append the action to the end of the URLS. This allows us to keep modes such as action=print
- thumbs='THUMBS'
- index=positions.index(target)
- back,forward='',''
- if not index==0:
- # We are not the first so we can provide a back link
- back=qlink(Globs.pagename, querystring, positions[index-1], 'PREV')
- if not index==len(positions)-1:
- # We are not the last so we can provide a forward link
- forward=qlink(Globs.pagename, querystring, positions[index+1], 'NEXT')
- return '
'+back+'
'+thumbs+'
'+forward+'
'
-
-def toolbar(target,naillevel):
- if Globs.admin:
- rotateleft=''
- rotateright=''
- deleteitem=''
- htarget=''
- compat=''
- return ''
- else:
- return ''
-
-def buildnails(items):
- # For now we use commands.getoutput to do our dirty work
- # Later we can build a batch job and fork it off.
-
- # Make sure our temp directory is writable and generate a message if it isn't
- try:
- if not os.path.isfile(Globs.gallerytempdir+'/tmp.writetest'):
- # There is probably a less ugly was to do this using stat (later)
- open(Globs.gallerytempdir+'/tmp.writetest','w').close()
- except IOError:
- message('I had some trouble writing to the temp directory. Is it owned by me and writable?',0)
-
- # Don't go further if there is a lock in place
- if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
- message("I'm currently busy generating thumbnails and webnails, please try again later.",0)
- return ''
-
- # Find the convert binary in standard locations
- if not globals().has_key('Image'):
- if not os.path.isfile('/usr/bin/convert'):
- if not os.path.isfile('/usr/X11R6/bin/convert'):
- message('Please install ImageMagick or PIL so I can build thumbnails and webnails
',0)
- return
- else:
- Globs.convertbin='/usr/X11R6/bin/convert'
- else:
- Globs.convertbin='/usr/bin/convert'
- else:
- # Use Python Imaging Library
- Globs.convertbin='Image'
-
- # Create a lock file in the attachments dir so we can always remotely remove it if there is a problem
- open(Globs.attachmentdir+'/tmp.lock','w').close()
-
- import time
- tstart=time.time()
- pid,pid2='',''
-
- # For each original file, check for the existance of a nail
- for item in items:
- basename,prefix,width=item
-
- # Check to see if we tarry too long on the road
- if tstart and (time.time()-tstart) > Globs.timeout:
- # This is taking waaay too long let us fork and detach else the browser will time out or worse, the webserver may kill us
- pid = os.fork()
- if pid != 0:
- # We are in the parent so we break out
- message('The thumbnail generation process was taking too long so it has been backgrounded. Please try again later to see the full set of thumbnails',0)
- break
- else:
- # Once we are forked we want to ignore the time
- tstart=''
- # Break away from the controlling terminal, so that the web server cannot kill us by killing our parent
- os.setsid()
- # Fork again so we can get away without a controlling terminal
- pid2 = os.fork()
- if (pid2 != 0):
- os._exit(0)
- else:
- # Close all open file descriptors
- try:
- max_fd = os.sysconf("SC_OPEN_MAX")
- except (AttributeError, ValueError):
- max_fd = 256
- for fd in range(0, max_fd):
- try:
- os.close(fd)
- except OSError:
- pass
- # Redirect the standard file descriptors to /dev/null
- os.open("/dev/null", os.O_RDONLY) # stdin
- os.open("/dev/null", os.O_RDWR) # stdout
- os.open("/dev/null", os.O_RDWR) # stderr
-
- # Now we are finally free to continue the conversions as a daemon
- # If you would like to know more about the above, see:
- # Advanced Programming in the Unix Environment: W. Richard Stevens
- # It is also explained in:
- # Unix Network Programming (Volume 1): W. Richard Stevens
-
- #pathtooriginal='"'+Globs.attachmentdir+'/'+Globs.originals[basename]+'"'
- pathtooriginal='"'+os.path.join(Globs.attachmentdir,Globs.originals[basename])+'"'
- # Warning:
- # Take care if modifying the following line,
- # you may inadvertantly overwrite your original images!
- if not Globs.convertbin == 'Image':
- #print 'building nail for '+pathtooriginal
- #convout=commands.getoutput('%s -geometry %s \"%s\" "\"%s/%s.%s.jpg\""' % (Globs.convertbin,width+'x'+width,pathtooriginal,Globs.gallerytempdir,prefix,basename))
- convout=commands.getoutput('%s -geometry %s %s "%s/%s.%s.jpg"' % (Globs.convertbin,width+'x'+width,pathtooriginal,Globs.gallerytempdir,prefix,basename))
- convout=''
- convout=string.strip(convout)
- if convout:
- message(convout)
- else:
- # Use PIL (strip off the "")
- im = Image.open(pathtooriginal[1:-1])
- # Use the integer version for PIL
- width=string.atoi(width)
- im.thumbnail((width,width), Image.ANTIALIAS)
- im.save(os.path.join(Globs.gallerytempdir,prefix)+'.'+basename+'.jpg','JPEG')
-
- if (not pid) and (not pid2):
- # Release the lock file when finished
- os.unlink(Globs.attachmentdir+'/tmp.lock')
-
- # We have built thumbnails so we can deposit an indicator file to prevent rebuilding next time
- if not os.path.isfile(Globs.attachmentdir+'/delete.me.to.regenerate.thumbnails.and.webnails'):
- open(Globs.attachmentdir+'/delete.me.to.regenerate.thumbnails.and.webnails','w').close()
-
-
-def deletefromdisk(target):
- # Rotate the images
- # Don't go further if there is a lock in place
- if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
- message("I'm currently busy generating thumbnails and webnails. Please try your delete request again later.",0)
- return ''
- # Ok do the actual delete
- if 1:
- # Delete first the temp dir webnail and thumbnail
- try:
- os.unlink(Globs.gallerytempdir+'/tmp.webnail.'+target+'.jpg')
- except:
- pass
- try:
- os.unlink(Globs.gallerytempdir+'/tmp.thumbnail.'+target+'.jpg')
- except:
- pass
- try:
- os.unlink(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg')
- except:
- pass
-
- # Now delete the original (except we actually just move it to /tmp)
- # TODO: insert a random number in the destination filename to cope with deleting files with same name
- origfn=Globs.originals[target]
- try:
- #shutil.copy(Globs.attachmentdir+'/'+origfn,'/tmp/deleted.'+origfn)
- shutil.copy(Globs.attachmentdir+'/'+origfn,'/tmp/'+origfn)
- os.unlink(Globs.attachmentdir+'/'+origfn)
- except:
- pass
- try:
- #shutil.copy(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','/tmp/deleted.tmp.annotation.'+target+'.txt')
- shutil.copy(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','/tmp/tmp.annotation.'+target+'.txt')
- os.unlink(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt')
- except:
- pass
-
-def rotate(target,direction):
- # Rotate the images
- # Don't go further if there is a lock in place
- if os.path.isfile(Globs.attachmentdir+'/tmp.lock'):
- message("I'm currently busy generating thumbnails and webnails. Please try your rotate request again later.",0)
- return ''
-
- # Find the correct binary
- if not globals().has_key('Image'):
- if not os.path.isfile('/usr/bin/mogrify'):
- if not os.path.isfile('/usr/X11R6/bin/mogrify'):
- message('Please install ImageMagick so I can build thumbnails and webnails
',0)
- return
- else:
- Globs.convertbin='/usr/X11R6/bin/mogrify'
- else:
- Globs.convertbin='/usr/bin/mogrify'
- else:
- Globs.convertbin = 'Image'
-
- # Do the actual rotations
- if direction=='rotate right':
- degs='90'
- else:
- degs='270'
- if not Globs.convertbin == 'Image':
- convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.webnail.'+target+'.jpg"')
- convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.thumbnail.'+target+'.jpg"')
- # Don't bother rotating the original. Since we don't want to reduce its quality incase it is used for printing
- #if not os.path.isfile(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg'):
- # # Generate from original
- # pathtooriginal=Globs.attachmentdir+'/'+Globs.originals[target]
- # shutil.copy(pathtooriginal,Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg')
- #convout=commands.getoutput(Globs.convertbin+' -rotate '+degs+' "'+Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg"')
- else:
- # Use PIL (strip off the "")
- if direction=='rotate right':
- degs=270.0
- else:
- degs=90.0
- im = os.path.join(Globs.gallerytempdir,'tmp.webnail.')+target+'.jpg'
- imw = Image.open(im)
- imw.rotate(degs).save(im,'JPEG')
- im = os.path.join(Globs.gallerytempdir,'tmp.thumbnail.')+target+'.jpg'
- imw = Image.open(im)
- imw.rotate(degs).save(im,'JPEG')
- imo = os.path.join(Globs.gallerytempdir,'tmp.rotated.')+target+'.jpg'
- if not os.path.isfile(Globs.gallerytempdir+'/tmp.rotated.'+target+'.jpg'):
- # Generate from original
- im=Globs.attachmentdir+'/'+Globs.originals[target]
- else:
- im = imo
- imw = Image.open(im)
- imw.rotate(degs).save(imo,'JPEG')
-
-def getannotation(target):
- # Annotations are stored as a file for now (later to be stored in images)
- atext=''
- if Globs.annotated.has_key(target):
- try:
- atext=open(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt').readline()
- except:
- atext=''
- message('was annotated')
- else:
- message('was not annotated')
- # replace double quotes with the html escape so quoted annotations appear
- return string.replace(atext,'"','"')
-
-def execute(macro, args):
-
- Globs.version=__version__
-
- # Containers
- formvals={}
- thumbnails={}
- webnails={}
- rotated={}
- try:
- import wikiconfig
- except:
- wikiconfig=''
-
- # Class variables need to be specifically set
- # (except for the case where a value is to be shared with another Gallery macro on the same wiki page)
- Globs.originals={}
- Globs.annotated={}
- Globs.attachmentdir=''
- Globs.admin=''
- Globs.adminmsg=''
- Globs.pagename=''
-
- # process arguments
- if args:
- # Arguments are comma delimited key=value pairs
- sargs=string.split(args,',')
- for item in sargs:
- sitem=string.split(item,'=')
- if len(sitem)==2:
- key,value=sitem[0],sitem[1]
- if key=='thumbnailwidth':
- Globs.thumbnailwidth=value
- elif key=='webnailwidth':
- Globs.webnailwidth=value
- elif key=='numberofcolumns':
- try:
- Globs.numberofcolumns=string.atoi(value)
- except TypeError:
- pass
- # Experimental, uncomment at own risk
- #elif key=='pagename':
- # Globs.pagename=value
-
- transtable=mktrans()
-
- # Useful variables
- dontregen=''
- annotationmessage=''
- Globs.baseurl=macro.request.getBaseURL()+'/'
- if not Globs.pagename:
- #Globs.pagename = string.replace(macro.formatter.page.page_name,'/','_2f')
- Globs.pagename = macro.formatter.page.page_name
- # This fixes the subpages bug. subname is now used instead of pagename when creating certain urls
- Globs.subname = string.split(Globs.pagename,'/')[-1]
- # Hmmm. A bug in moinmoin? underscores are getting escaped. These doubly escaped pagenames are even appearing in data/pages
- try:
- # Try the old MoinMoin-1.2.x way first
- textdir=config.text_dir
- pagepath = string.replace(wikiutil.getPagePath(Globs.pagename),'_5f','_')
- except:
- pagepath = macro.formatter.page.getPagePath()
- Globs.attachmentdir = pagepath+'/attachments'
- Globs.galleryrotchar='?'
- if hasattr(macro,'cfg') and hasattr(macro.cfg,'gallerytempdir') and hasattr(macro.cfg,'gallerytempurl'):
- Globs.gallerytempdirroot=macro.cfg.gallerytempdir
- Globs.gallerytempdir=macro.cfg.gallerytempdir+'/'+Globs.pagename+'/'
- Globs.gallerytempurl=macro.cfg.gallerytempurl+'/'+Globs.pagename+'/'
- elif hasattr(wikiconfig,'gallerytempdir') and hasattr(wikiconfig,'gallerytempurl'):
- message('gallerytempdir and gallerytempurl found')
- Globs.gallerytempdirroot=wikiconfig.gallerytempdir
- Globs.gallerytempdir=wikiconfig.gallerytempdir+'/'+Globs.pagename+'/'
- Globs.gallerytempurl=wikiconfig.gallerytempurl+'/'+Globs.pagename+'/'
- elif hasattr(wikiconfig,'attachments'):
- Globs.gallerytempdirroot=wikiconfig.attachments['dir']
- Globs.gallerytempdir=wikiconfig.attachments['dir']+'/'+Globs.pagename+'/attachments/'
- Globs.gallerytempurl=wikiconfig.attachments['url']+'/'+Globs.pagename+'/attachments/'
- Globs.attachmentdir = Globs.gallerytempdir
- else:
- Globs.gallerytempdir=Globs.attachmentdir
- Globs.gallerytempurl=Globs.subname+'?action=AttachFile&do=get&target='
- # MoinMoin no longer allows us to use a ? to trigger a refetch, so we pass it a &
- Globs.galleryrotchar='&'
- if args:
- args=macro.request.getText(args)
-
- # HTML Constants
- tleft='
'
- tmidd='
'
- trigh='
\n'
-
- # Process any form items into a dictionary (values become unique)
- for item in macro.form.items():
- if not formvals.has_key(item[0]):
- # Here is where we clean the untrusted web input
- # (sometimes we get foreign keys from moinmoin when the page is edited)
- try:
- formvals[item[0]]=string.translate(item[1][0],transtable)
- except AttributeError:
- pass
-
- # Add this to the end of each URL to keep some versions of moinmoin happy
- if formvals.has_key('action'):
- if formvals['action']=='content':
- # translate content action to print action for now
- Globs.bcomp='&action=print'
- else:
- Globs.bcomp='&action='+formvals['action']
- else:
- Globs.bcomp='&action=show'
-
- # Figure out if we have delete privs
- try:
- # If a user can delete the page containing the Gallery, then they are considered a Gallery administrator
- # This probably should be configurable via a wikiconfig variable eg: galleryadminreq =
- if macro.request.user.may.delete(macro.formatter.page.page_name):
- Globs.admin='true'
- except AttributeError:
- pass
-
- out=cStringIO.StringIO()
-
- # Grab a list of the files in the attachment directory
- if os.path.isdir(Globs.attachmentdir):
- if Globs.gallerytempdir==Globs.attachmentdir:
- afiles=os.listdir(Globs.attachmentdir)
- else:
- if not os.path.isdir(Globs.gallerytempdirroot):
- message('You need to create the temp dir first:'+Globs.gallerytempdirroot,0)
- return macro.formatter.rawHTML(
- Globs.adminmsg+'
')
- if not os.path.isdir(Globs.gallerytempdir):
- # Try to create it if it is absent
- spagename=string.split(Globs.pagename,'/')
- compbit=''
- for component in spagename:
- compbit=compbit+'/'+component
- try:
- # The following line stops an exception being raised when trying
- # to create a directory that already exists (thanks Magnus Wahrenberg)
- if not os.access(Globs.gallerytempdirroot+compbit, os.F_OK):
- os.mkdir(Globs.gallerytempdirroot+compbit)
-
- except:
- message('Please check permissions on temp dir:'+Globs.gallerytempdirroot,0)
- return macro.formatter.rawHTML(
- Globs.adminmsg+'
')
-
- if os.path.isdir(Globs.gallerytempdir):
- afiles=os.listdir(Globs.attachmentdir)+os.listdir(Globs.gallerytempdir)
- else:
- message('You need to create the temp dir first:'+Globs.gallerytempdir,0)
- return macro.formatter.rawHTML(
- Globs.adminmsg+'
')
-
- # Split out the thumbnails and webnails
- for item in afiles:
- if item.startswith('tmp.thumbnail.'):
- origname=item[14:-4]
- thumbnails[origname]=''
- elif item.startswith('tmp.webnail.'):
- origname=item[12:-4]
- webnails[origname]=''
- elif item.startswith('tmp.rotated.'):
- origname=item[12:-4]
- rotated[origname]=''
- elif item.startswith('tmp.annotation.'):
- origname=item[15:-4]
- Globs.annotated[origname]=''
- elif item == 'delete.me.to.regenerate.thumbnails.and.webnails':
- dontregen='true'
- elif item == 'tmp.writetest' or item == 'tmp.lock':
- pass
- else:
- # This must be one of the original images
- lastdot=string.rfind(item,'.')
- origname=item[:lastdot]
- ext = item[lastdot+1:]
- if string.lower(ext) not in Globs.allowedextensions:
- continue
- Globs.originals[origname]=item
- else:
- message(version(),0)
- return macro.formatter.rawHTML( Globs.adminmsg )
-
- if not Globs.gallerytempdir==Globs.attachmentdir and os.path.isfile(Globs.attachmentdir+'/tmp.writetest'):
- # If we are using the new gallerytempdir and we were using the old system then make sure there are no
- # remnant files from the old system in the attachment dir to confuse us
- message('You have changed to using a gallerytempdir so I am cleaning old tmp files from your attachment dir.',0)
- for item in webnails.keys():
- try:
- os.unlink(Globs.attachmentdir+'/tmp.webnail.'+item+'.jpg')
- except:
- pass
- # Try deleting any old thumbnails which may be in the attachment directory
- for item in thumbnails.keys():
- try:
- os.unlink(Globs.attachmentdir+'/tmp.thumbnail.'+item+'.jpg')
- except:
- pass
- # Try deleting any old rotated originals which may be in the attachment directory
- for item in rotated.keys():
- try:
- os.unlink(Globs.attachmentdir+'/tmp.rotated.'+item+'.jpg')
- except:
- pass
- os.unlink(Globs.attachmentdir+'/tmp.writetest')
-
- newnails=[]
- # Any thumbnails need to be built?
- for key in Globs.originals.keys():
- if (not thumbnails.has_key(key)) or (not dontregen):
- # Create a thumbnail for this original
- newnails.append((key,'tmp.thumbnail',Globs.thumbnailwidth))
- # Any webnails need to be built?
- for key in Globs.originals.keys():
- if (not webnails.has_key(key)) or (not dontregen):
- # Create a webnail for this original
- newnails.append((key,'tmp.webnail',Globs.webnailwidth))
- # Ok, lets build them all at once
- if not len(newnails)==0:
- buildnails(newnails)
-
- # If a regen of thumbnails and webnails has occurred, then we should also delete any tmp.rotated files.
- if not dontregen:
- for key in rotated.keys():
- # Wrapped in a try except since child processes may try to unlink a second time
- try:
- os.unlink(Globs.gallerytempdir+'/tmp.rotated.'+key+'.jpg')
- except:
- pass
-
- if formvals.has_key('annotate'):
- if Globs.admin and formvals.has_key('target'):
- target=formvals['target']
- # Write an annotation file
- atext=string.replace(formvals['annotate'],'"','"')
- ouf=open(Globs.attachmentdir+'/tmp.annotation.'+target+'.txt','w')
- ouf.write(atext)
- ouf.close()
- message('Annotation updated to '+atext+'',0)
- # Now update the annotated dictionary
- if not Globs.annotated.has_key(target):
- Globs.annotated[target]=''
-
- if formvals.has_key('webnail'):
- # Does the webnail exist?
- message('webnail requested')
- target=formvals['webnail']
- rotend=''
- if Globs.originals.has_key(target):
- out.write(navibar(target,'webnail'))
- out.write(toolbar(target,'webnail'))
- if formvals.has_key('rotate'):
- direction=formvals['rotate']
- message(direction)
- rotate(target,direction)
- rotend=Globs.galleryrotchar+'rot='+repr(randint(1,10000))
- if formvals.has_key('delete'):
- message('Deleted '+target+'',0)
- deletefromdisk(target)
- # Put things in a table
- out.write(tleft)
- # Lets build up an image tag
- out.write('\n')
- out.write(trigh)
- out.write(tleft)
-
- atext=getannotation(target)
-
- # Are we an administrator?
- if Globs.admin:
- # We always provide an annotation text field
- out.write('
')
- else:
- out.write(atext)
- out.write(trigh)
- #out.write(toolbar(target,'webnail'))
-
- else:
- message('I do not have file: '+target,0)
- elif formvals.has_key('original'):
- # Now we just construct a single item rather than a table
- # Does the webnail exist?
- message('original requested')
- target=formvals['original']
- rotend=''
- if not Globs.originals.has_key(target):
- message('I do not have file: '+target,0)
- else:
- if formvals.has_key('rotate'):
- direction=formvals['rotate']
- message(direction)
- rotate(target,direction)
- rotend=Globs.galleryrotchar+'rot='+repr(randint(1,10000))
- rotated[target]=''
- # Lets build up an image tag
- out.write(navibar(target,'original'))
- out.write(tleft)
- originalfilename=Globs.originals[target]
- # If there is a rotated version, show that instead
- if rotated.has_key(target):
- out.write('\n')
- else:
- out.write('\n')
- out.write(trigh)
- out.write(tleft)
-
- atext=getannotation(target)
-
- # Are we an administrator?
- if Globs.admin:
- # We always provide an annotation text field
- out.write('')
- else:
- out.write(atext)
- out.write(trigh)
- out.write(toolbar(target,'original'))
-
- elif formvals.has_key('rotate'):
- # We rotate all sizes of this image to the left or right
- message('rotate requested')
- target=formvals['target']
- direction=formvals['rotate']
- if not Globs.originals.has_key(target):
- message('I do not have file: '+target,0)
- else:
- # Do the rotation
- rotate(target,direction)
- # Display the new image in webnail mode
- # We may need a way of forcing the browser to reload the newly rotated image here (later)
- out.write(tleft)
- out.write('\n')
- out.write(trigh)
-
- else:
- # Finally lets build a table of thumbnails
- thumbs=Globs.originals.keys()
- thumbs.sort()
- thumbs.reverse()
- # If version number is requested (append a ?version=tellme&action=show to the page request)
- # or if there are no original images, just give help message and return
- if formvals.has_key('version') or len(thumbs)==0:
- message(version(),0)
- return macro.formatter.rawHTML( Globs.adminmsg )
- out.write('\n
')
- cease=''
- rollover=''
- while 1:
- out.write('
')
- for i in range(Globs.numberofcolumns):
- try:
- item=thumbs.pop()
- except IndexError:
- cease='true'
- break
-
- # Alt text
- atext=getannotation(item)
- rollover='alt="'+atext+'" title="'+atext+'"'
-
- # Table entry for thumbnail image
- out.write('
')
- out.write('
\n')
- if cease:
- out.write('
')
- break
-
- out.seek(0)
- # Finally output any administrative messages at the top followed by any generated content
- return macro.formatter.rawHTML(
- Globs.adminmsg+'
" % ( letter, letter )
-
-def makeIndex(letter_list):
- index = u''
- for aLetter in letter_list:
- index = u"%s%s" % ( index, aLetter, aLetter)
- return u"
%s
" % index
-
-def execute(macro, args):
- dirs = comptes()
- dirs.sort()
-
- html = u""
-
- premiere_lettre = ''
- letter_list = []
- for d in dirs:
- if premiere_lettre != d[0]:
- premiere_lettre = d[0]
- letter_list.append(premiere_lettre)
- html = u"%s\n%s" % ( html, makeAnchor(premiere_lettre) )
- html = u"%s\n%s" % (html, account(d).__str__())
-
-
- index = makeIndex(letter_list)
- html = index + html
- html += u' '
- return html
diff --git a/wiki/macro/ProgressBar.py b/wiki/macro/ProgressBar.py
deleted file mode 100644
index 880698de..00000000
--- a/wiki/macro/ProgressBar.py
+++ /dev/null
@@ -1,225 +0,0 @@
-"""
-MoinMoin - ProgressBar Macro
-Generates a progress bar (in the form of a table)
-
-@copyright: Pascal Bauermeister
-@license: GPL
-
-Updates:
-
- * [v0.1.1] Sun Dec 18 21:31:17 CET 2005
- Changed table cell percentage markup.
-
- * [v0.1.0] Fri Dec 16 22:30:10 CET 2005
- Original version
-
-----
-
-The ProgressBar macro generates a table showing a progress indicator.
-
-Usage:
- [[ ProgressBar ]]
- [[ ProgressBar (TABLEWIDTH TABLEFORMAT PROGRESS%) ]]
- [[ ProgressBar (TABLEWIDTH TABLEFORMAT CURRENT/STEPS) ]]
- [[ ProgressBar (TABLEWIDTH TABLEFORMAT STARTDATE,ENDDATE) ]]
-
-If no arguments are given, the usage is inserted in the HTML result.
-
-Options:
-
- TABLEWIDTH (optional prefix)
- A wiki tablewidth attribute value between []'s
- Examples:
- [100%]
- [80px]
-
- TABLEFORMAT (optional prefix)
- A pair of wiki table attribute, to format the inactive and active cells.
- Examples:
- # black on white bar
- # same, 90% table
-
- A third format may be given for STARTDATE,ENDDATE usage
-
- By default: <><#8080ff>
-
- PROGRESS
- Will display a table with two cells:
- - left: completion, taking PROGRESS % of the table width
- - right: remaining
-
- CURRENT/STEPS
- Will display a table with STEPS cells, CURRENT of which are active.
-
- STARTDATE,ENDDATE
- Will display a table with the number of days, with the cell
- representing today in active format and background in inactive format.
-
- If today is before STARTDATE, the left-most cell will be in the
- 3rd format. If today is after ENDDATE the rightmost cell will be
- in the 3rd format.
-
- Dates are in this format: YYYY-MM-DD
-
-Debugging
- Please prepend a '?' to the arguments.
-
-Examples:
- [[ProgressBar(60%)]]
- [[ProgressBar(6/10)]]
- [[ProgressBar(2005-11-01,2006-01-06)]]
-
- [[ProgressBar([50%] 60%)]]
- [[ProgressBar([50px] 60%)]]
- [[ProgressBar([90%]<#8080ff><#808080> 6/10)]]
-----
-"""
-
-
-# Imports
-import time, re, StringIO
-from MoinMoin import version
-from MoinMoin.parser import wiki
-
-Dependencies = ["time"] # macro cannot be cached
-
-
-class _Error (Exception):
- pass
-
-
-def escape (str):
- return str.replace ('&','&').replace ('<', '<').replace ('>', '>')
-
-def usage (full = False):
-
- """Returns the interesting part of the module's doc"""
-
- if full:
- return __doc__
- else:
- rx = re.compile ("--$(.*)^--", re.DOTALL + re.MULTILINE)
- return rx.findall (__doc__) [0].strip ()
-
-
-def s2t (s):
- return time.mktime (time.strptime (s, "%Y-%m-%d"))
-
-
-def execute (macro, text, args_re=None):
-
- try: res = _execute (macro, text)
- except Exception, msg:
- return """
-
- Error: macro ProgressBar: %s
- """ % escape ("%s" % msg)
- return res
-
-
-def _execute (macro, text):
-
- fmt = ['#808080','','#8080ff']
- width ="100px"
- res = ""
- text = text.strip ()
-
- # help if empty text
- help = len (text) == 0
-
- # debug if starts with '?'
- if text.startswith ('?'):
- debug = True
- text = text [1:]
- else:
- debug = False
- orig_text = text
-
- # Formats
- try:
- # Table width
- if text.startswith ('['):
- pos = text.rfind (']')
- width = text [1:pos]
- text = text [pos+1:].strip ()
-
- # Cells format
- if text.startswith ('<'):
- pos = text.rfind ('>')
- f = text [1:pos].split ('><')
- text = text [pos+1:].strip ()
- fmt [:len (f)] = f
- except:
- help = True
-
- # Show help
- if help:
- return """
-
'),
- ]
- self.request.write(u'\n'.join(html))
-
-#####################################################################
-# Parser : classe principale, c'est elle qui parse
-#######
-class Parser:
-
- def __init__(self, raw, request, **kw):
- self.request = request
- self.raw = raw
- self.settings={'title': u'|'.join([u" ".join(kw.keys()), u" ".join(kw.values())])}
- self.settings = self.parseArgs(kw["format_args"])
-
- def getRandomColor(self):
- nb_of_colors = color_list.__len__()
- from random import randint
- colorNum = randint(0, nb_of_colors - 1)
- return color_list[colorNum]
-
- def parseArgs(self, argsString):
- argList = argsString.split(u',')
- settings = {}
- for anArg in argList:
- anArg = anArg.strip(u' ')
- if anArg.find(u'color=')!=-1:
- theColor = anArg.split(u'=')[1].lower()
- if theColor == 'random':
- theColor = self.getRandomColor()
- settings['color'] = theColor
- else:
- settings['title'] = anArg
- return settings
-
- def format(self, formatter):
- quotes = self.raw
-
- # on utilise la classe qui va fabriquer le code html
- boite = BoxFormatter(self.request, formatter)
- title = self.settings['title']
- content = to_wikiname(self.request, formatter, quotes)
- try:
- color = self.settings['color']
- except:
- color=None
- boite.make(title, content, color)
- return
-
diff --git a/wiki/parser/EXIF.py b/wiki/parser/EXIF.py
deleted file mode 100644
index 154499f0..00000000
--- a/wiki/parser/EXIF.py
+++ /dev/null
@@ -1,1197 +0,0 @@
-# Library to extract EXIF information in digital camera image files
-#
-# To use this library call with:
-# f=open(path_name, 'rb')
-# tags=EXIF.process_file(f)
-# tags will now be a dictionary mapping names of EXIF tags to their
-# values in the file named by path_name. You can process the tags
-# as you wish. In particular, you can iterate through all the tags with:
-# for tag in tags.keys():
-# if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
-# 'EXIF MakerNote'):
-# print "Key: %s, value %s" % (tag, tags[tag])
-# (This code uses the if statement to avoid printing out a few of the
-# tags that tend to be long or boring.)
-#
-# The tags dictionary will include keys for all of the usual EXIF
-# tags, and will also include keys for Makernotes used by some
-# cameras, for which we have a good specification.
-#
-# Contains code from "exifdump.py" originally written by Thierry Bousch
-# and released into the public domain.
-#
-# Updated and turned into general-purpose library by Gene Cash
-#
-# This copyright license is intended to be similar to the FreeBSD license.
-#
-# Copyright 2002 Gene Cash All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the
-# distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
-# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# This means you may do anything you want with this code, except claim you
-# wrote it. Also, if it breaks you get to keep both pieces.
-#
-# Patch Contributors:
-# * Simon J. Gerraty
-# s2n fix & orientation decode
-# * John T. Riedl
-# Added support for newer Nikon type 3 Makernote format for D70 and some
-# other Nikon cameras.
-# * Joerg Schaefer
-# Fixed subtle bug when faking an EXIF header, which affected maker notes
-# using relative offsets, and a fix for Nikon D100.
-#
-# 21-AUG-99 TB Last update by Thierry Bousch to his code.
-# 17-JAN-02 CEC Discovered code on web.
-# Commented everything.
-# Made small code improvements.
-# Reformatted for readability.
-# 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs.
-# Added ability to extract JPEG formatted thumbnail.
-# Added ability to read GPS IFD (not tested).
-# Converted IFD data structure to dictionaries indexed by
-# tag name.
-# Factored into library returning dictionary of IFDs plus
-# thumbnail, if any.
-# 20-JAN-02 CEC Added MakerNote processing logic.
-# Added Olympus MakerNote.
-# Converted data structure to single-level dictionary, avoiding
-# tag name collisions by prefixing with IFD name. This makes
-# it much easier to use.
-# 23-JAN-02 CEC Trimmed nulls from end of string values.
-# 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
-# 26-JAN-02 CEC Added ability to extract TIFF thumbnails.
-# Added Nikon, Fujifilm, Casio MakerNotes.
-# 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
-# IFD_Tag() object.
-# 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
-#
-
-# field type descriptions as (length, abbreviation, full name) tuples
-FIELD_TYPES=(
- (0, 'X', 'Proprietary'), # no such type
- (1, 'B', 'Byte'),
- (1, 'A', 'ASCII'),
- (2, 'S', 'Short'),
- (4, 'L', 'Long'),
- (8, 'R', 'Ratio'),
- (1, 'SB', 'Signed Byte'),
- (1, 'U', 'Undefined'),
- (2, 'SS', 'Signed Short'),
- (4, 'SL', 'Signed Long'),
- (8, 'SR', 'Signed Ratio')
- )
-
-# dictionary of main EXIF tag names
-# first element of tuple is tag name, optional second element is
-# another dictionary giving names to values
-EXIF_TAGS={
- 0x0100: ('ImageWidth', ),
- 0x0101: ('ImageLength', ),
- 0x0102: ('BitsPerSample', ),
- 0x0103: ('Compression',
- {1: 'Uncompressed TIFF',
- 6: 'JPEG Compressed'}),
- 0x0106: ('PhotometricInterpretation', ),
- 0x010A: ('FillOrder', ),
- 0x010D: ('DocumentName', ),
- 0x010E: ('ImageDescription', ),
- 0x010F: ('Make', ),
- 0x0110: ('Model', ),
- 0x0111: ('StripOffsets', ),
- 0x0112: ('Orientation',
- {1: 'Horizontal (normal)',
- 2: 'Mirrored horizontal',
- 3: 'Rotated 180',
- 4: 'Mirrored vertical',
- 5: 'Mirrored horizontal then rotated 90 CCW',
- 6: 'Rotated 90 CW',
- 7: 'Mirrored horizontal then rotated 90 CW',
- 8: 'Rotated 90 CCW'}),
- 0x0115: ('SamplesPerPixel', ),
- 0x0116: ('RowsPerStrip', ),
- 0x0117: ('StripByteCounts', ),
- 0x011A: ('XResolution', ),
- 0x011B: ('YResolution', ),
- 0x011C: ('PlanarConfiguration', ),
- 0x0128: ('ResolutionUnit',
- {1: 'Not Absolute',
- 2: 'Pixels/Inch',
- 3: 'Pixels/Centimeter'}),
- 0x012D: ('TransferFunction', ),
- 0x0131: ('Software', ),
- 0x0132: ('DateTime', ),
- 0x013B: ('Artist', ),
- 0x013E: ('WhitePoint', ),
- 0x013F: ('PrimaryChromaticities', ),
- 0x0156: ('TransferRange', ),
- 0x0200: ('JPEGProc', ),
- 0x0201: ('JPEGInterchangeFormat', ),
- 0x0202: ('JPEGInterchangeFormatLength', ),
- 0x0211: ('YCbCrCoefficients', ),
- 0x0212: ('YCbCrSubSampling', ),
- 0x0213: ('YCbCrPositioning', ),
- 0x0214: ('ReferenceBlackWhite', ),
- 0x828D: ('CFARepeatPatternDim', ),
- 0x828E: ('CFAPattern', ),
- 0x828F: ('BatteryLevel', ),
- 0x8298: ('Copyright', ),
- 0x829A: ('ExposureTime', ),
- 0x829D: ('FNumber', ),
- 0x83BB: ('IPTC/NAA', ),
- 0x8769: ('ExifOffset', ),
- 0x8773: ('InterColorProfile', ),
- 0x8822: ('ExposureProgram',
- {0: 'Unidentified',
- 1: 'Manual',
- 2: 'Program Normal',
- 3: 'Aperture Priority',
- 4: 'Shutter Priority',
- 5: 'Program Creative',
- 6: 'Program Action',
- 7: 'Portrait Mode',
- 8: 'Landscape Mode'}),
- 0x8824: ('SpectralSensitivity', ),
- 0x8825: ('GPSInfo', ),
- 0x8827: ('ISOSpeedRatings', ),
- 0x8828: ('OECF', ),
- # print as string
- #0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))),
- 0x9003: ('DateTimeOriginal', ),
- 0x9004: ('DateTimeDigitized', ),
- 0x9101: ('ComponentsConfiguration',
- {0: '',
- 1: 'Y',
- 2: 'Cb',
- 3: 'Cr',
- 4: 'Red',
- 5: 'Green',
- 6: 'Blue'}),
- 0x9102: ('CompressedBitsPerPixel', ),
- 0x9201: ('ShutterSpeedValue', ),
- 0x9202: ('ApertureValue', ),
- 0x9203: ('BrightnessValue', ),
- 0x9204: ('ExposureBiasValue', ),
- 0x9205: ('MaxApertureValue', ),
- 0x9206: ('SubjectDistance', ),
- 0x9207: ('MeteringMode',
- {0: 'Unidentified',
- 1: 'Average',
- 2: 'CenterWeightedAverage',
- 3: 'Spot',
- 4: 'MultiSpot'}),
- 0x9208: ('LightSource',
- {0: 'Unknown',
- 1: 'Daylight',
- 2: 'Fluorescent',
- 3: 'Tungsten',
- 10: 'Flash',
- 17: 'Standard Light A',
- 18: 'Standard Light B',
- 19: 'Standard Light C',
- 20: 'D55',
- 21: 'D65',
- 22: 'D75',
- 255: 'Other'}),
- 0x9209: ('Flash', {0: 'No',
- 1: 'Fired',
- 5: 'Fired (?)', # no return sensed
- 7: 'Fired (!)', # return sensed
- 9: 'Fill Fired',
- 13: 'Fill Fired (?)',
- 15: 'Fill Fired (!)',
- 16: 'Off',
- 24: 'Auto Off',
- 25: 'Auto Fired',
- 29: 'Auto Fired (?)',
- 31: 'Auto Fired (!)',
- 32: 'Not Available'}),
- 0x920A: ('FocalLength', ),
- 0x927C: ('MakerNote', ),
- # print as string
- #0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
- 0x9290: ('SubSecTime', ),
- 0x9291: ('SubSecTimeOriginal', ),
- 0x9292: ('SubSecTimeDigitized', ),
- # print as string
- #0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
- 0xA001: ('ColorSpace', ),
- 0xA002: ('ExifImageWidth', ),
- 0xA003: ('ExifImageLength', ),
- 0xA005: ('InteroperabilityOffset', ),
- 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP
- 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - -
- 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - -
- 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - -
- 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - -
- 0xA214: ('SubjectLocation', ), # 0x9214 - -
- 0xA215: ('ExposureIndex', ), # 0x9215 - -
- 0xA217: ('SensingMethod', ), # 0x9217 - -
- 0xA300: ('FileSource',
- {3: 'Digital Camera'}),
- 0xA301: ('SceneType',
- {1: 'Directly Photographed'}),
- 0xA302: ('CVAPattern',),
- }
-
-# interoperability tags
-INTR_TAGS={
- 0x0001: ('InteroperabilityIndex', ),
- 0x0002: ('InteroperabilityVersion', ),
- 0x1000: ('RelatedImageFileFormat', ),
- 0x1001: ('RelatedImageWidth', ),
- 0x1002: ('RelatedImageLength', ),
- }
-
-# GPS tags (not used yet, haven't seen camera with GPS)
-GPS_TAGS={
- 0x0000: ('GPSVersionID', ),
- 0x0001: ('GPSLatitudeRef', ),
- 0x0002: ('GPSLatitude', ),
- 0x0003: ('GPSLongitudeRef', ),
- 0x0004: ('GPSLongitude', ),
- 0x0005: ('GPSAltitudeRef', ),
- 0x0006: ('GPSAltitude', ),
- 0x0007: ('GPSTimeStamp', ),
- 0x0008: ('GPSSatellites', ),
- 0x0009: ('GPSStatus', ),
- 0x000A: ('GPSMeasureMode', ),
- 0x000B: ('GPSDOP', ),
- 0x000C: ('GPSSpeedRef', ),
- 0x000D: ('GPSSpeed', ),
- 0x000E: ('GPSTrackRef', ),
- 0x000F: ('GPSTrack', ),
- 0x0010: ('GPSImgDirectionRef', ),
- 0x0011: ('GPSImgDirection', ),
- 0x0012: ('GPSMapDatum', ),
- 0x0013: ('GPSDestLatitudeRef', ),
- 0x0014: ('GPSDestLatitude', ),
- 0x0015: ('GPSDestLongitudeRef', ),
- 0x0016: ('GPSDestLongitude', ),
- 0x0017: ('GPSDestBearingRef', ),
- 0x0018: ('GPSDestBearing', ),
- 0x0019: ('GPSDestDistanceRef', ),
- 0x001A: ('GPSDestDistance', )
- }
-
-# Nikon E99x MakerNote Tags
-# http://members.tripod.com/~tawba/990exif.htm
-MAKERNOTE_NIKON_NEWER_TAGS={
- 0x0002: ('ISOSetting', ),
- 0x0003: ('ColorMode', ),
- 0x0004: ('Quality', ),
- 0x0005: ('Whitebalance', ),
- 0x0006: ('ImageSharpening', ),
- 0x0007: ('FocusMode', ),
- 0x0008: ('FlashSetting', ),
- 0x0009: ('AutoFlashMode', ),
- 0x000B: ('WhiteBalanceBias', ),
- 0x000C: ('WhiteBalanceRBCoeff', ),
- 0x000F: ('ISOSelection', ),
- 0x0012: ('FlashCompensation', ),
- 0x0013: ('ISOSpeedRequested', ),
- 0x0016: ('PhotoCornerCoordinates', ),
- 0x0018: ('FlashBracketCompensationApplied', ),
- 0x0019: ('AEBracketCompensationApplied', ),
- 0x0080: ('ImageAdjustment', ),
- 0x0081: ('ToneCompensation', ),
- 0x0082: ('AuxiliaryLens', ),
- 0x0083: ('LensType', ),
- 0x0084: ('LensMinMaxFocalMaxAperture', ),
- 0x0085: ('ManualFocusDistance', ),
- 0x0086: ('DigitalZoomFactor', ),
- 0x0088: ('AFFocusPosition',
- {0x0000: 'Center',
- 0x0100: 'Top',
- 0x0200: 'Bottom',
- 0x0300: 'Left',
- 0x0400: 'Right'}),
- 0x0089: ('BracketingMode',
- {0x00: 'Single frame, no bracketing',
- 0x01: 'Continuous, no bracketing',
- 0x02: 'Timer, no bracketing',
- 0x10: 'Single frame, exposure bracketing',
- 0x11: 'Continuous, exposure bracketing',
- 0x12: 'Timer, exposure bracketing',
- 0x40: 'Single frame, white balance bracketing',
- 0x41: 'Continuous, white balance bracketing',
- 0x42: 'Timer, white balance bracketing'}),
- 0x008D: ('ColorMode', ),
- 0x008F: ('SceneMode?', ),
- 0x0090: ('LightingType', ),
- 0x0092: ('HueAdjustment', ),
- 0x0094: ('Saturation',
- {-3: 'B&W',
- -2: '-2',
- -1: '-1',
- 0: '0',
- 1: '1',
- 2: '2'}),
- 0x0095: ('NoiseReduction', ),
- 0x00A7: ('TotalShutterReleases', ),
- 0x00A9: ('ImageOptimization', ),
- 0x00AA: ('Saturation', ),
- 0x00AB: ('DigitalVariProgram', ),
- 0x0010: ('DataDump', )
- }
-
-MAKERNOTE_NIKON_OLDER_TAGS={
- 0x0003: ('Quality',
- {1: 'VGA Basic',
- 2: 'VGA Normal',
- 3: 'VGA Fine',
- 4: 'SXGA Basic',
- 5: 'SXGA Normal',
- 6: 'SXGA Fine'}),
- 0x0004: ('ColorMode',
- {1: 'Color',
- 2: 'Monochrome'}),
- 0x0005: ('ImageAdjustment',
- {0: 'Normal',
- 1: 'Bright+',
- 2: 'Bright-',
- 3: 'Contrast+',
- 4: 'Contrast-'}),
- 0x0006: ('CCDSpeed',
- {0: 'ISO 80',
- 2: 'ISO 160',
- 4: 'ISO 320',
- 5: 'ISO 100'}),
- 0x0007: ('WhiteBalance',
- {0: 'Auto',
- 1: 'Preset',
- 2: 'Daylight',
- 3: 'Incandescent',
- 4: 'Fluorescent',
- 5: 'Cloudy',
- 6: 'Speed Light'})
- }
-
-# decode Olympus SpecialMode tag in MakerNote
-def olympus_special_mode(v):
- a={
- 0: 'Normal',
- 1: 'Unknown',
- 2: 'Fast',
- 3: 'Panorama'}
- b={
- 0: 'Non-panoramic',
- 1: 'Left to right',
- 2: 'Right to left',
- 3: 'Bottom to top',
- 4: 'Top to bottom'}
- return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
-
-MAKERNOTE_OLYMPUS_TAGS={
- # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
- # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
- 0x0100: ('JPEGThumbnail', ),
- 0x0200: ('SpecialMode', olympus_special_mode),
- 0x0201: ('JPEGQual',
- {1: 'SQ',
- 2: 'HQ',
- 3: 'SHQ'}),
- 0x0202: ('Macro',
- {0: 'Normal',
- 1: 'Macro'}),
- 0x0204: ('DigitalZoom', ),
- 0x0207: ('SoftwareRelease', ),
- 0x0208: ('PictureInfo', ),
- # print as string
- 0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
- 0x0F00: ('DataDump', )
- }
-
-MAKERNOTE_CASIO_TAGS={
- 0x0001: ('RecordingMode',
- {1: 'Single Shutter',
- 2: 'Panorama',
- 3: 'Night Scene',
- 4: 'Portrait',
- 5: 'Landscape'}),
- 0x0002: ('Quality',
- {1: 'Economy',
- 2: 'Normal',
- 3: 'Fine'}),
- 0x0003: ('FocusingMode',
- {2: 'Macro',
- 3: 'Auto Focus',
- 4: 'Manual Focus',
- 5: 'Infinity'}),
- 0x0004: ('FlashMode',
- {1: 'Auto',
- 2: 'On',
- 3: 'Off',
- 4: 'Red Eye Reduction'}),
- 0x0005: ('FlashIntensity',
- {11: 'Weak',
- 13: 'Normal',
- 15: 'Strong'}),
- 0x0006: ('Object Distance', ),
- 0x0007: ('WhiteBalance',
- {1: 'Auto',
- 2: 'Tungsten',
- 3: 'Daylight',
- 4: 'Fluorescent',
- 5: 'Shade',
- 129: 'Manual'}),
- 0x000B: ('Sharpness',
- {0: 'Normal',
- 1: 'Soft',
- 2: 'Hard'}),
- 0x000C: ('Contrast',
- {0: 'Normal',
- 1: 'Low',
- 2: 'High'}),
- 0x000D: ('Saturation',
- {0: 'Normal',
- 1: 'Low',
- 2: 'High'}),
- 0x0014: ('CCDSpeed',
- {64: 'Normal',
- 80: 'Normal',
- 100: 'High',
- 125: '+1.0',
- 244: '+3.0',
- 250: '+2.0',})
- }
-
-MAKERNOTE_FUJIFILM_TAGS={
- 0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))),
- 0x1000: ('Quality', ),
- 0x1001: ('Sharpness',
- {1: 'Soft',
- 2: 'Soft',
- 3: 'Normal',
- 4: 'Hard',
- 5: 'Hard'}),
- 0x1002: ('WhiteBalance',
- {0: 'Auto',
- 256: 'Daylight',
- 512: 'Cloudy',
- 768: 'DaylightColor-Fluorescent',
- 769: 'DaywhiteColor-Fluorescent',
- 770: 'White-Fluorescent',
- 1024: 'Incandescent',
- 3840: 'Custom'}),
- 0x1003: ('Color',
- {0: 'Normal',
- 256: 'High',
- 512: 'Low'}),
- 0x1004: ('Tone',
- {0: 'Normal',
- 256: 'High',
- 512: 'Low'}),
- 0x1010: ('FlashMode',
- {0: 'Auto',
- 1: 'On',
- 2: 'Off',
- 3: 'Red Eye Reduction'}),
- 0x1011: ('FlashStrength', ),
- 0x1020: ('Macro',
- {0: 'Off',
- 1: 'On'}),
- 0x1021: ('FocusMode',
- {0: 'Auto',
- 1: 'Manual'}),
- 0x1030: ('SlowSync',
- {0: 'Off',
- 1: 'On'}),
- 0x1031: ('PictureMode',
- {0: 'Auto',
- 1: 'Portrait',
- 2: 'Landscape',
- 4: 'Sports',
- 5: 'Night',
- 6: 'Program AE',
- 256: 'Aperture Priority AE',
- 512: 'Shutter Priority AE',
- 768: 'Manual Exposure'}),
- 0x1100: ('MotorOrBracket',
- {0: 'Off',
- 1: 'On'}),
- 0x1300: ('BlurWarning',
- {0: 'Off',
- 1: 'On'}),
- 0x1301: ('FocusWarning',
- {0: 'Off',
- 1: 'On'}),
- 0x1302: ('AEWarning',
- {0: 'Off',
- 1: 'On'})
- }
-
-MAKERNOTE_CANON_TAGS={
- 0x0006: ('ImageType', ),
- 0x0007: ('FirmwareVersion', ),
- 0x0008: ('ImageNumber', ),
- 0x0009: ('OwnerName', )
- }
-
-# see http://www.burren.cx/david/canon.html by David Burren
-# this is in element offset, name, optional value dictionary format
-MAKERNOTE_CANON_TAG_0x001={
- 1: ('Macromode',
- {1: 'Macro',
- 2: 'Normal'}),
- 2: ('SelfTimer', ),
- 3: ('Quality',
- {2: 'Normal',
- 3: 'Fine',
- 5: 'Superfine'}),
- 4: ('FlashMode',
- {0: 'Flash Not Fired',
- 1: 'Auto',
- 2: 'On',
- 3: 'Red-Eye Reduction',
- 4: 'Slow Synchro',
- 5: 'Auto + Red-Eye Reduction',
- 6: 'On + Red-Eye Reduction',
- 16: 'external flash'}),
- 5: ('ContinuousDriveMode',
- {0: 'Single Or Timer',
- 1: 'Continuous'}),
- 7: ('FocusMode',
- {0: 'One-Shot',
- 1: 'AI Servo',
- 2: 'AI Focus',
- 3: 'MF',
- 4: 'Single',
- 5: 'Continuous',
- 6: 'MF'}),
- 10: ('ImageSize',
- {0: 'Large',
- 1: 'Medium',
- 2: 'Small'}),
- 11: ('EasyShootingMode',
- {0: 'Full Auto',
- 1: 'Manual',
- 2: 'Landscape',
- 3: 'Fast Shutter',
- 4: 'Slow Shutter',
- 5: 'Night',
- 6: 'B&W',
- 7: 'Sepia',
- 8: 'Portrait',
- 9: 'Sports',
- 10: 'Macro/Close-Up',
- 11: 'Pan Focus'}),
- 12: ('DigitalZoom',
- {0: 'None',
- 1: '2x',
- 2: '4x'}),
- 13: ('Contrast',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 14: ('Saturation',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 15: ('Sharpness',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 16: ('ISO',
- {0: 'See ISOSpeedRatings Tag',
- 15: 'Auto',
- 16: '50',
- 17: '100',
- 18: '200',
- 19: '400'}),
- 17: ('MeteringMode',
- {3: 'Evaluative',
- 4: 'Partial',
- 5: 'Center-weighted'}),
- 18: ('FocusType',
- {0: 'Manual',
- 1: 'Auto',
- 3: 'Close-Up (Macro)',
- 8: 'Locked (Pan Mode)'}),
- 19: ('AFPointSelected',
- {0x3000: 'None (MF)',
- 0x3001: 'Auto-Selected',
- 0x3002: 'Right',
- 0x3003: 'Center',
- 0x3004: 'Left'}),
- 20: ('ExposureMode',
- {0: 'Easy Shooting',
- 1: 'Program',
- 2: 'Tv-priority',
- 3: 'Av-priority',
- 4: 'Manual',
- 5: 'A-DEP'}),
- 23: ('LongFocalLengthOfLensInFocalUnits', ),
- 24: ('ShortFocalLengthOfLensInFocalUnits', ),
- 25: ('FocalUnitsPerMM', ),
- 28: ('FlashActivity',
- {0: 'Did Not Fire',
- 1: 'Fired'}),
- 29: ('FlashDetails',
- {14: 'External E-TTL',
- 13: 'Internal Flash',
- 11: 'FP Sync Used',
- 7: '2nd("Rear")-Curtain Sync Used',
- 4: 'FP Sync Enabled'}),
- 32: ('FocusMode',
- {0: 'Single',
- 1: 'Continuous'})
- }
-
-MAKERNOTE_CANON_TAG_0x004={
- 7: ('WhiteBalance',
- {0: 'Auto',
- 1: 'Sunny',
- 2: 'Cloudy',
- 3: 'Tungsten',
- 4: 'Fluorescent',
- 5: 'Flash',
- 6: 'Custom'}),
- 9: ('SequenceNumber', ),
- 14: ('AFPointUsed', ),
- 15: ('FlashBias',
- {0XFFC0: '-2 EV',
- 0XFFCC: '-1.67 EV',
- 0XFFD0: '-1.50 EV',
- 0XFFD4: '-1.33 EV',
- 0XFFE0: '-1 EV',
- 0XFFEC: '-0.67 EV',
- 0XFFF0: '-0.50 EV',
- 0XFFF4: '-0.33 EV',
- 0X0000: '0 EV',
- 0X000C: '0.33 EV',
- 0X0010: '0.50 EV',
- 0X0014: '0.67 EV',
- 0X0020: '1 EV',
- 0X002C: '1.33 EV',
- 0X0030: '1.50 EV',
- 0X0034: '1.67 EV',
- 0X0040: '2 EV'}),
- 19: ('SubjectDistance', )
- }
-
-# extract multibyte integer in Motorola format (little endian)
-def s2n_motorola(str):
- x=0
- for c in str:
- x=(x << 8) | ord(c)
- return x
-
-# extract multibyte integer in Intel format (big endian)
-def s2n_intel(str):
- x=0
- y=0L
- for c in str:
- x=x | (ord(c) << y)
- y=y+8
- return x
-
-# ratio object that eventually will be able to reduce itself to lowest
-# common denominator for printing
-def gcd(a, b):
- if b == 0:
- return a
- else:
- return gcd(b, a % b)
-
-class Ratio:
- def __init__(self, num, den):
- self.num=num
- self.den=den
-
- def __repr__(self):
- self.reduce()
- if self.den == 1:
- return str(self.num)
- return '%d/%d' % (self.num, self.den)
-
- def reduce(self):
- div=gcd(self.num, self.den)
- if div > 1:
- self.num=self.num/div
- self.den=self.den/div
-
-# for ease of dealing with tags
-class IFD_Tag:
- def __init__(self, printable, tag, field_type, values, field_offset,
- field_length):
- # printable version of data
- self.printable=printable
- # tag ID number
- self.tag=tag
- # field type as index into FIELD_TYPES
- self.field_type=field_type
- # offset of start of field in bytes from beginning of IFD
- self.field_offset=field_offset
- # length of data field in bytes
- self.field_length=field_length
- # either a string or array of data items
- self.values=values
-
- def __str__(self):
- return self.printable
-
- def __repr__(self):
- return '(0x%04X) %s=%s @ %d' % (self.tag,
- FIELD_TYPES[self.field_type][2],
- self.printable,
- self.field_offset)
-
-# class that handles an EXIF header
-class EXIF_header:
- def __init__(self, file, endian, offset, fake_exif, debug=0):
- self.file=file
- self.endian=endian
- self.offset=offset
- self.fake_exif=fake_exif
- self.debug=debug
- self.tags={}
-
- # convert slice to integer, based on sign and endian flags
- # usually this offset is assumed to be relative to the beginning of the
- # start of the EXIF information. For some cameras that use relative tags,
- # this offset may be relative to some other starting point.
- def s2n(self, offset, length, signed=0):
- self.file.seek(self.offset+offset)
- slice=self.file.read(length)
- if self.endian == 'I':
- val=s2n_intel(slice)
- else:
- val=s2n_motorola(slice)
- # Sign extension ?
- if signed:
- msb=1L << (8*length-1)
- if val & msb:
- val=val-(msb << 1)
- return val
-
- # convert offset to string
- def n2s(self, offset, length):
- s=''
- for i in range(length):
- if self.endian == 'I':
- s=s+chr(offset & 0xFF)
- else:
- s=chr(offset & 0xFF)+s
- offset=offset >> 8
- return s
-
- # return first IFD
- def first_IFD(self):
- return self.s2n(4, 4)
-
- # return pointer to next IFD
- def next_IFD(self, ifd):
- entries=self.s2n(ifd, 2)
- return self.s2n(ifd+2+12*entries, 4)
-
- # return list of IFDs in header
- def list_IFDs(self):
- i=self.first_IFD()
- a=[]
- while i:
- a.append(i)
- i=self.next_IFD(i)
- return a
-
- # return list of entries in this IFD
- def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0):
- entries=self.s2n(ifd, 2)
- for i in range(entries):
- # entry is index of start of this IFD in the file
- entry=ifd+2+12*i
- tag=self.s2n(entry, 2)
- # get tag name. We do it early to make debugging easier
- tag_entry=dict.get(tag)
- if tag_entry:
- tag_name=tag_entry[0]
- else:
- tag_name='Tag 0x%04X' % tag
- field_type=self.s2n(entry+2, 2)
- if not 0 < field_type < len(FIELD_TYPES):
- # unknown field type
- raise ValueError, \
- 'unknown type %d in tag 0x%04X' % (field_type, tag)
- typelen=FIELD_TYPES[field_type][0]
- count=self.s2n(entry+4, 4)
- offset=entry+8
- if count*typelen > 4:
- # offset is not the value; it's a pointer to the value
- # if relative we set things up so s2n will seek to the right
- # place when it adds self.offset. Note that this 'relative'
- # is for the Nikon type 3 makernote. Other cameras may use
- # other relative offsets, which would have to be computed here
- # slightly differently.
- if relative:
- tmp_offset=self.s2n(offset, 4)
- offset=tmp_offset+ifd-self.offset+4
- if self.fake_exif:
- offset=offset+18
- else:
- offset=self.s2n(offset, 4)
- field_offset=offset
- if field_type == 2:
- # special case: null-terminated ASCII string
- if count != 0:
- self.file.seek(self.offset+offset)
- values=self.file.read(count)
- values=values.strip().replace('\x00','')
- else:
- values=''
- else:
- values=[]
- signed=(field_type in [6, 8, 9, 10])
- for j in range(count):
- if field_type in (5, 10):
- # a ratio
- value_j=Ratio(self.s2n(offset, 4, signed),
- self.s2n(offset+4, 4, signed))
- else:
- value_j=self.s2n(offset, typelen, signed)
- values.append(value_j)
- offset=offset+typelen
- # now "values" is either a string or an array
- if count == 1 and field_type != 2:
- printable=str(values[0])
- else:
- printable=str(values)
- # compute printable version of values
- if tag_entry:
- if len(tag_entry) != 1:
- # optional 2nd tag element is present
- if callable(tag_entry[1]):
- # call mapping function
- printable=tag_entry[1](values)
- else:
- printable=''
- for i in values:
- # use lookup table for this tag
- printable+=tag_entry[1].get(i, repr(i))
- self.tags[ifd_name+' '+tag_name]=IFD_Tag(printable, tag,
- field_type,
- values, field_offset,
- count*typelen)
- if self.debug:
- print ' debug: %s: %s' % (tag_name,
- repr(self.tags[ifd_name+' '+tag_name]))
-
- # extract uncompressed TIFF thumbnail (like pulling teeth)
- # we take advantage of the pre-existing layout in the thumbnail IFD as
- # much as possible
- def extract_TIFF_thumbnail(self, thumb_ifd):
- entries=self.s2n(thumb_ifd, 2)
- # this is header plus offset to IFD ...
- if self.endian == 'M':
- tiff='MM\x00*\x00\x00\x00\x08'
- else:
- tiff='II*\x00\x08\x00\x00\x00'
- # ... plus thumbnail IFD data plus a null "next IFD" pointer
- self.file.seek(self.offset+thumb_ifd)
- tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
-
- # fix up large value offset pointers into data area
- for i in range(entries):
- entry=thumb_ifd+2+12*i
- tag=self.s2n(entry, 2)
- field_type=self.s2n(entry+2, 2)
- typelen=FIELD_TYPES[field_type][0]
- count=self.s2n(entry+4, 4)
- oldoff=self.s2n(entry+8, 4)
- # start of the 4-byte pointer area in entry
- ptr=i*12+18
- # remember strip offsets location
- if tag == 0x0111:
- strip_off=ptr
- strip_len=count*typelen
- # is it in the data area?
- if count*typelen > 4:
- # update offset pointer (nasty "strings are immutable" crap)
- # should be able to say "tiff[ptr:ptr+4]=newoff"
- newoff=len(tiff)
- tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:]
- # remember strip offsets location
- if tag == 0x0111:
- strip_off=newoff
- strip_len=4
- # get original data and store it
- self.file.seek(self.offset+oldoff)
- tiff+=self.file.read(count*typelen)
-
- # add pixel strips and update strip offset info
- old_offsets=self.tags['Thumbnail StripOffsets'].values
- old_counts=self.tags['Thumbnail StripByteCounts'].values
- for i in range(len(old_offsets)):
- # update offset pointer (more nasty "strings are immutable" crap)
- offset=self.n2s(len(tiff), strip_len)
- tiff=tiff[:strip_off]+offset+tiff[strip_off+strip_len:]
- strip_off+=strip_len
- # add pixel strip to end
- self.file.seek(self.offset+old_offsets[i])
- tiff+=self.file.read(old_counts[i])
-
- self.tags['TIFFThumbnail']=tiff
-
- # decode all the camera-specific MakerNote formats
-
- # Note is the data that comprises this MakerNote. The MakerNote will
- # likely have pointers in it that point to other parts of the file. We'll
- # use self.offset as the starting point for most of those pointers, since
- # they are relative to the beginning of the file.
- #
- # If the MakerNote is in a newer format, it may use relative addressing
- # within the MakerNote. In that case we'll use relative addresses for the
- # pointers.
- #
- # As an aside: it's not just to be annoying that the manufacturers use
- # relative offsets. It's so that if the makernote has to be moved by the
- # picture software all of the offsets don't have to be adjusted. Overall,
- # this is probably the right strategy for makernotes, though the spec is
- # ambiguous. (The spec does not appear to imagine that makernotes would
- # follow EXIF format internally. Once they did, it's ambiguous whether
- # the offsets should be from the header at the start of all the EXIF info,
- # or from the header at the start of the makernote.)
- def decode_maker_note(self):
- note=self.tags['EXIF MakerNote']
- make=self.tags['Image Make'].printable
- model=self.tags['Image Model'].printable
-
- # Nikon
- # The maker note usually starts with the word Nikon, followed by the
- # type of the makernote (1 or 2, as a short). If the word Nikon is
- # not at the start of the makernote, it's probably type 2, since some
- # cameras work that way.
- if make in ('NIKON', 'NIKON CORPORATION'):
- if note.values[0:7] == [78, 105, 107, 111, 110, 00, 01]:
- if self.debug:
- print "Looks like a type 1 Nikon MakerNote."
- self.dump_IFD(note.field_offset+8, 'MakerNote',
- dict=MAKERNOTE_NIKON_OLDER_TAGS)
- elif note.values[0:7] == [78, 105, 107, 111, 110, 00, 02]:
- if self.debug:
- print "Looks like a labeled type 2 Nikon MakerNote"
- if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
- raise ValueError, "Missing marker tag '42' in MakerNote."
- # skip the Makernote label and the TIFF header
- self.dump_IFD(note.field_offset+10+8, 'MakerNote',
- dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
- else:
- # E99x or D1
- if self.debug:
- print "Looks like an unlabeled type 2 Nikon MakerNote"
- self.dump_IFD(note.field_offset, 'MakerNote',
- dict=MAKERNOTE_NIKON_NEWER_TAGS)
- return
-
- # Olympus
- if make[:7] == 'OLYMPUS':
- self.dump_IFD(note.field_offset+8, 'MakerNote',
- dict=MAKERNOTE_OLYMPUS_TAGS)
- return
-
- # Casio
- if make == 'Casio':
- self.dump_IFD(note.field_offset, 'MakerNote',
- dict=MAKERNOTE_CASIO_TAGS)
- return
-
- # Fujifilm
- if make == 'FUJIFILM':
- # bug: everything else is "Motorola" endian, but the MakerNote
- # is "Intel" endian
- endian=self.endian
- self.endian='I'
- # bug: IFD offsets are from beginning of MakerNote, not
- # beginning of file header
- offset=self.offset
- self.offset+=note.field_offset
- # process note with bogus values (note is actually at offset 12)
- self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
- # reset to correct values
- self.endian=endian
- self.offset=offset
- return
-
- # Canon
- if make == 'Canon':
- self.dump_IFD(note.field_offset, 'MakerNote',
- dict=MAKERNOTE_CANON_TAGS)
- for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
- ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
- self.canon_decode_tag(self.tags[i[0]].values, i[1])
- return
-
- # decode Canon MakerNote tag based on offset within tag
- # see http://www.burren.cx/david/canon.html by David Burren
- def canon_decode_tag(self, value, dict):
- for i in range(1, len(value)):
- x=dict.get(i, ('Unknown', ))
- if self.debug:
- print i, x
- name=x[0]
- if len(x) > 1:
- val=x[1].get(value[i], 'Unknown')
- else:
- val=value[i]
- # it's not a real IFD Tag but we fake one to make everybody
- # happy. this will have a "proprietary" type
- self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
- None, None)
-
-# process an image file (expects an open file object)
-# this is the function that has to deal with all the arbitrary nasty bits
-# of the EXIF standard
-def process_file(file, debug=0):
- # determine whether it's a JPEG or TIFF
- data=file.read(12)
- if data[0:4] in ['II*\x00', 'MM\x00*']:
- # it's a TIFF file
- file.seek(0)
- endian=file.read(1)
- file.read(1)
- offset=0
- elif data[0:2] == '\xFF\xD8':
- # it's a JPEG file
- # skip JFIF style header(s)
- fake_exif=0
- while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM'):
- length=ord(data[4])*256+ord(data[5])
- file.read(length-8)
- # fake an EXIF beginning of file
- data='\xFF\x00'+file.read(10)
- fake_exif=1
- if data[2] == '\xFF' and data[6:10] == 'Exif':
- # detected EXIF header
- offset=file.tell()
- endian=file.read(1)
- else:
- # no EXIF information
- return {}
- else:
- # file format not recognized
- return {}
-
- # deal with the EXIF info we found
- if debug:
- print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
- hdr=EXIF_header(file, endian, offset, fake_exif, debug)
- ifd_list=hdr.list_IFDs()
- ctr=0
- for i in ifd_list:
- if ctr == 0:
- IFD_name='Image'
- elif ctr == 1:
- IFD_name='Thumbnail'
- thumb_ifd=i
- else:
- IFD_name='IFD %d' % ctr
- if debug:
- print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
- hdr.dump_IFD(i, IFD_name)
- # EXIF IFD
- exif_off=hdr.tags.get(IFD_name+' ExifOffset')
- if exif_off:
- if debug:
- print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
- hdr.dump_IFD(exif_off.values[0], 'EXIF')
- # Interoperability IFD contained in EXIF IFD
- intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
- if intr_off:
- if debug:
- print ' EXIF Interoperability SubSubIFD at offset %d:' \
- % intr_off.values[0]
- hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
- dict=INTR_TAGS)
- # GPS IFD
- gps_off=hdr.tags.get(IFD_name+' GPSInfo')
- if gps_off:
- if debug:
- print ' GPS SubIFD at offset %d:' % gps_off.values[0]
- hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS)
- ctr+=1
-
- # extract uncompressed TIFF thumbnail
- thumb=hdr.tags.get('Thumbnail Compression')
- if thumb and thumb.printable == 'Uncompressed TIFF':
- hdr.extract_TIFF_thumbnail(thumb_ifd)
-
- # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
- thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat')
- if thumb_off:
- file.seek(offset+thumb_off.values[0])
- size=hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
- hdr.tags['JPEGThumbnail']=file.read(size)
-
- # deal with MakerNote contained in EXIF IFD
- if hdr.tags.has_key('EXIF MakerNote'):
- hdr.decode_maker_note()
-
- # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
- # since it's not allowed in a uncompressed TIFF IFD
- if not hdr.tags.has_key('JPEGThumbnail'):
- thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
- if thumb_off:
- file.seek(offset+thumb_off.values[0])
- hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
-
- return hdr.tags
-
-# library test/debug function (dump given files)
-if __name__ == '__main__':
- import sys
-
- if len(sys.argv) < 2:
- print 'Usage: %s files...\n' % sys.argv[0]
- sys.exit(0)
-
- for filename in sys.argv[1:]:
- try:
- file=open(filename, 'rb')
- except:
- print filename, 'unreadable'
- print
- continue
- print filename+':'
- # data=process_file(file, 1) # with debug info
- data=process_file(file)
- if not data:
- print 'No EXIF information found'
- continue
-
- x=data.keys()
- x.sort()
- for i in x:
- if i in ('JPEGThumbnail', 'TIFFThumbnail'):
- continue
- try:
- print ' %s (%s): %s' % \
- (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
- except:
- print 'error', i, '"', data[i], '"'
- if data.has_key('JPEGThumbnail'):
- print 'File has JPEG thumbnail'
- print
-
-
-class Parser:
- pass
diff --git a/wiki/parser/Portail.py b/wiki/parser/Portail.py
deleted file mode 100644
index 31d69677..00000000
--- a/wiki/parser/Portail.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-# -*- coding: utf-8 -*-
- ..
- .... ............ ........
- . ....... . .... ..
- . ... .. .. .. .. ..... . ..
- .. .. ....@@@. .. . ........ .
- .. . .. ..@.@@..@@. .@@@@@@@ @@@@@@. ....
- .@@@@. .@@@@. .@@@@..@@.@@..@@@..@@@..@@@@.... ....
- @@@@... .@@@.. @@ @@ .@..@@..@@...@@@. .@@@@@. ..
- .@@@.. . @@@. @@.@@..@@.@@..@@@ @@ .@@@@@@.. .....
- ...@@@.... @@@ .@@.......... ........ ..... ..
- . ..@@@@.. . .@@@@. .. ....... . .............
- . .. .... .. .. . ... ....
- . . .... ............. .. ...
- .. .. ... ........ ... ...
- ................................
-
-MoinMoin - Portail parser
-
- PURPOSE:
- Pour afficher un portail a la wikipedia.
-
- CALLING SEQUENCE:
- {{{
- #!Portail
- image1.png @@ title1 @@ description 1
- image2.png @@ title2 @@ description 2
- image3.png @@ title3 @@ description 3
- }}}
-
- CREDIT
- Le code est derive de celui de Gallery2.
-
-"""
-from MoinMoin.parser import wiki
-import os,string,re,StringIO
-from MoinMoin.action import AttachFile
-
-#####################################################################
-# Fonctions #
-#####################################################################
-# to_wikiname : transfome la chaine text avec le parser wiki
-# classique et le formateur formatter
-# (je sais pas a quoi sert request, mais faut pas
-# l'enlever !)
-#######
-
-def to_wikiname(request,formatter,text):
- ##taken from MiniPage
- out=StringIO.StringIO()
- request.redirect(out)
- wikiizer = wiki.Parser(text.strip(),request)
- wikiizer.format(formatter)
- result=out.getvalue()
- request.redirect()
- del out
-
- return result.strip()
-
-#####################################################################
-# PortailFormatter : creer le code html du portail a l'aide de
-# de formatter et l'envoie a request
-#######
-class PortailFormatter:
-
- def __init__(self, request, formatter):
- self.formatter = formatter
- self.request = request
- self.counter = 0
-
- def open(self):
- self.request.write(self.formatter.table(1,{'style':'width:100%;padding:10px;'}))
-
- def cell(self, title, description, image):
- if self.counter==0:
- self.request.write(self.formatter.table_row(1))
- self.request.write(self.formatter.table_cell(1,{'style':'width:50%;border:none;padding:20px 20px 20px 50px;background:transparent url(\'' + image.replace("\'","\\\'") + '\') center left no-repeat; vertical-align:middle;'}))
- self.request.write(title)
- self.request.write(description)
- self.request.write(self.formatter.table_cell(0))
- if self.counter==1:
- self.request.write(self.formatter.table_row(0))
- self.counter = (self.counter + 1)%2
-
- def close(self):
- if self.counter==1:
- self.request.write(self.formatter.table_cell(1,{'style':'width:50%;border:none;padding:20px 20px 20px 50px;'}))
- self.request.write(self.formatter.table_cell(0))
- self.request.write(self.formatter.table_row(0))
- self.request.write(self.formatter.table(0))
-
-#####################################################################
-# Parser : classe principale, c'est elle qui parse
-#######
-class Parser:
-
- def __init__(self, raw, request, **kw):
- self.request = request
- self.raw = raw
- return
-
-
- def format(self, formatter):
-
- # on divise le textes en lignes (1 ligne = une entree dans le portail)
- quotes = self.raw.split('\n')
-
- # on récupere le chemin des fichiers attaches pour les images
- current_pagename=formatter.page.page_name
- attachment_path = AttachFile.getAttachDir(self.request, current_pagename, create=1)
-
- # on initialise la classe qui va fabriquer le code html
- portail = PortailFormatter(self.request, formatter)
- portail.open()
-
- # on traite les ligne une à une
- for line in quotes:
- line=line.strip()
- items=line.split('@@',2)
- if items.__len__()<3:
- self.request.write('')
- else:
- description=items.pop().strip()
- link=items.pop().strip()
- image = AttachFile.getAttachUrl(current_pagename, items.pop().strip(), self.request).replace('& ','&')
-
- link = to_wikiname(self.request, formatter, link)
- description = to_wikiname(self.request, formatter, description)
- portail.cell(link, description, image)
- portail.close()
-
- return
diff --git a/wiki/parser/__init__.py b/wiki/parser/__init__.py
deleted file mode 100644
index e4ed3b60..00000000
--- a/wiki/parser/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-
-from MoinMoin.util import pysupport
-
-modules = pysupport.getPackageModules(__file__)
diff --git a/wiki/parser/latex.py b/wiki/parser/latex.py
deleted file mode 100644
index d2f43a75..00000000
--- a/wiki/parser/latex.py
+++ /dev/null
@@ -1,214 +0,0 @@
-#FORMAT python
-#!/usr/bin/python
-# -*- coding: iso-8859-15 -*-
-
-"""
-New latex formatter using dvipng and tempfile
-
-Author: JohannesBerg
-
-This parser (and the corresponding macro) was tested with Python 2.3.4 and
- * Debian Linux with out-of-the-box tetex-bin and dvipng packages installed
- * Windows XP (not by me)
-
-In the parser, you can add stuff to the prologue by writing
-%%end-prologue%%
-somewhere in the document, before that write stuff like \\usepackage and after it put
-the actual latex display code.
-"""
-
-Dependencies = []
-
-import sha, os, tempfile, shutil, re
-from MoinMoin.action import AttachFile
-from MoinMoin.Page import Page
-
-latex_template = r'''
-\documentclass[12pt]{article}
-\pagestyle{empty}
-%(prologue)s
-\begin{document}
-%(raw)s
-\end{document}
-'''
-
-max_pages = 10
-MAX_RUN_TIME = 5 # seconds
-
-latex = "latex" # edit full path here, e.g. reslimit = "C:\\path\\to\\latex.exe"
-dvipng = "dvipng" # edit full path here (or reslimit = r'C:\path\to\latex.exe')
-
-# last arg must have %s in it!
-latex_args = ("--interaction=nonstopmode", "%s.tex")
-
-# last arg must have %s in it!
-dvipng_args = ("-bgTransparent", "-Ttight", "--noghostscript", "-l%s" % max_pages, "%s.dvi")
-
-# this is formatted with hexdigest(texcode),
-# page number and extension are appended by
-# the tools
-latex_name_template = "latex_%s_p"
-
-# keep this up-to-date, also with max_pages!!
-latex_attachment = re.compile((latex_name_template+'%s%s') % (r'[0-9a-fA-F]{40}', r'[0-9]{1,2}', r'\.png'))
-
-anchor = re.compile(r'^%%anchor:[ ]*([a-zA-Z0-9_-]+)$', re.MULTILINE | re.IGNORECASE)
-# the anchor re must start with a % sign to be ignored by latex as a comment!
-end_prologue = '%%end-prologue%%'
-
-def call_command_in_dir_NT(app, args, targetdir):
- reslimit = "runlimit.exe" # edit full path here
- os.environ['openin_any'] = 'p'
- os.environ['openout_any'] = 'p'
- os.environ['shell_escape'] = 'f'
- stdouterr = os.popen('%s %d "%s" %s %s < NUL' % (reslimit, MAX_RUN_TIME, targetdir, app, ' '.join(args)), 'r')
- output = ''.join(stdouterr.readlines())
- err = stdouterr.close()
- if not err is None:
- return ' error! exitcode was %d, transscript follows:\n\n%s' % (err,output)
- return None
-
-def call_command_in_dir_unix(app, args, targetdir):
- # this is the unix implementation
- (r,w) = os.pipe()
- pid = os.fork()
- if pid == -1:
- return 'could not fork'
- if pid == 0:
- # child
- os.close(r)
- os.dup2(os.open("/dev/null", os.O_WRONLY), 0)
- os.dup2(w, 1)
- os.dup2(w, 2)
- os.chdir(targetdir)
- os.environ['openin_any'] = 'p'
- os.environ['openout_any'] = 'p'
- os.environ['shell_escape'] = 'f'
- import resource
- resource.setrlimit(resource.RLIMIT_CPU,
- (MAX_RUN_TIME * 1000, MAX_RUN_TIME * 1000)) # docs say this is seconds, but it is msecs on my system.
- os.execvp(app, [app] + list(args))
- print "failed to exec()",app
- os._exit(2)
- else:
- # parent
- os.close(w)
- r = os.fdopen(r,"r")
- output = ''.join(r.readlines())
- (npid, exi) = os.waitpid(pid, 0)
- r.close()
- sig = exi & 0xFF
- stat = exi >> 8
- if stat != 0 or sig != 0:
- return ' error! exitcode was %d (signal %d), transscript follows:\n\n%s' % (stat,sig,output)
- return None
- # notreached
-
-if os.name == 'nt':
- call_command_in_dir = call_command_in_dir_NT
-else:
- call_command_in_dir = call_command_in_dir_unix
-
-
-class Parser:
- extensions = ['.tex']
- def __init__ (self, raw, request, **kw):
- self.raw = raw
- if len(self.raw)>0 and self.raw[0] == '#':
- self.raw[0] = '%'
- self.request = request
- self.exclude = []
- if not hasattr(request, "latex_cleanup_done"):
- request.latex_cleanup_done = {}
-
- def cleanup(self, pagename):
- attachdir = AttachFile.getAttachDir(self.request, pagename, create=1)
- for f in os.listdir(attachdir):
- if not latex_attachment.match(f) is None:
- os.remove("%s/%s" % (attachdir, f))
-
- def _internal_format(self, formatter, text):
- tmp = text.split(end_prologue, 1)
- if len(tmp) == 2:
- prologue,tex=tmp
- else:
- prologue = ''
- tex = tmp[0]
- if callable(getattr(formatter, 'johill_sidecall_emit_latex', None)):
- return formatter.johill_sidecall_emit_latex(tex)
- return self.get(formatter, tex, prologue, True)
-
- def format(self, formatter):
- self.request.write(self._internal_format(formatter, self.raw))
-
- def get(self, formatter, inputtex, prologue, para=False):
- if not self.request.latex_cleanup_done.has_key(formatter.page.page_name):
- self.request.latex_cleanup_done[formatter.page.page_name] = True
- self.cleanup(formatter.page.page_name)
-
- if len(inputtex) == 0: return ''
-
- if callable(getattr(formatter, 'johill_sidecall_emit_latex', None)):
- return formatter.johill_sidecall_emit_latex(inputtex)
-
- extra_preamble = ''
- preamble_page = self.request.pragma.get('latex_preamble', None)
- if preamble_page is not None:
- extra_preamble = Page(self.request, preamble_page).get_raw_body()
- extra_preamble = re.sub(re.compile('^#'), '%', extra_preamble)
-
- tex = latex_template % { 'raw': inputtex, 'prologue': extra_preamble + prologue }
- enctex = tex.encode('utf-8')
- fn = latex_name_template % sha.new(enctex).hexdigest()
-
- attachdir = AttachFile.getAttachDir(self.request, formatter.page.page_name, create=1)
- dst = "%s/%s%%d.png" % (attachdir, fn)
- if not os.access(dst % 1, os.R_OK):
- tmpdir = tempfile.mkdtemp()
- try:
- data = open("%s/%s.tex" % (tmpdir, fn), "w")
- data.write(enctex)
- data.close()
- args = list(latex_args)
- args[-1] = args[-1] % fn
- res = call_command_in_dir(latex, args, tmpdir)
- if not res is None:
- return formatter.preformatted(1)+formatter.text('latex'+res)+formatter.preformatted(0)
- args = list(dvipng_args)
- args[-1] = args[-1] % fn
- res = call_command_in_dir(dvipng, args, tmpdir)
- if not res is None:
- return formatter.preformatted(1)+formatter.text('dvipng'+res)+formatter.preformatted(0)
-
- page = 1
- while os.access("%s/%s%d.png" % (tmpdir, fn, page), os.R_OK):
- shutil.copyfile ("%s/%s%d.png" % (tmpdir, fn, page), dst % page)
- page += 1
-
- finally:
- for root,dirs,files in os.walk(tmpdir, topdown=False):
- for name in files:
- os.remove(os.path.join(root,name))
- for name in dirs:
- os.rmdir(os.path.join(root,name))
- os.rmdir(tmpdir)
-
- result = ""
- page = 1
- loop = False
- for match in anchor.finditer(inputtex):
- result += formatter.anchordef(match.group(1))
- for match in anchor.finditer(prologue):
- result += formatter.anchordef(match.group(1))
- while os.access(dst % page, os.R_OK):
- url = AttachFile.getAttachUrl(formatter.page.page_name, fn+"%d.png" % page, self.request)
- if loop:
- result += formatter.linebreak(0)+formatter.linebreak(0)
- if para:
- result += formatter.paragraph(1)
- result += formatter.image(src="%s" % url, alt=inputtex, title=inputtex, align="absmiddle")
- if para:
- result += formatter.paragraph(0)
- page += 1
- loop = True
- return result
diff --git a/wiki/request.py b/wiki/request.py
deleted file mode 100644
index 0c44050e..00000000
--- a/wiki/request.py
+++ /dev/null
@@ -1,2164 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
- MoinMoin - Data associated with a single Request
-
- @copyright: 2001-2003 by Jürgen Hermann
- @copyright: 2003-2004 by Thomas Waldmann
- @license: GNU GPL, see COPYING for details.
-"""
-
-import os, re, time, sys, cgi, StringIO
-import copy
-from MoinMoin import config, wikiutil, user, caching
-from MoinMoin.util import MoinMoinNoFooter, IsWin9x
-
-# Timing ---------------------------------------------------------------
-
-class Clock:
- """ Helper class for code profiling
- we do not use time.clock() as this does not work across threads
- """
-
- def __init__(self):
- self.timings = {'total': time.time()}
-
- def start(self, timer):
- self.timings[timer] = time.time() - self.timings.get(timer, 0)
-
- def stop(self, timer):
- self.timings[timer] = time.time() - self.timings[timer]
-
- def value(self, timer):
- return "%.3f" % (self.timings[timer],)
-
- def dump(self):
- outlist = []
- for timing in self.timings.items():
- outlist.append("%s = %.3fs" % timing)
- outlist.sort()
- return outlist
-
-
-# Utilities
-
-def cgiMetaVariable(header, scheme='http'):
- """ Return CGI meta variable for header name
-
- e.g 'User-Agent' -> 'HTTP_USER_AGENT'
- See http://www.faqs.org/rfcs/rfc3875.html section 4.1.18
- """
- var = '%s_%s' % (scheme, header)
- return var.upper().replace('-', '_')
-
-
-# Request Base ----------------------------------------------------------
-
-class RequestBase(object):
- """ A collection for all data associated with ONE request. """
-
- # Header set to force misbehaved proxies and browsers to keep their
- # hands off a page
- # Details: http://support.microsoft.com/support/kb/articles/Q234/0/67.ASP
- nocache = [
- "Pragma: no-cache",
- "Cache-Control: no-cache",
- "Expires: -1",
- ]
-
- # Defaults (used by sub classes)
- http_accept_language = 'en'
- server_name = 'localhost'
- server_port = '80'
-
- # Extra headers we support. Both standalone and twisted store
- # headers as lowercase.
- moin_location = 'x-moin-location'
- proxy_host = 'x-forwarded-host'
-
- def __init__(self, properties={}):
- # Decode values collected by sub classes
- self.path_info = self.decodePagename(self.path_info)
-
- self.failed = 0
- self._available_actions = None
- self._known_actions = None
-
- # Pages meta data that we collect in one request
- self.pages = {}
-
- self.sent_headers = 0
- self.user_headers = []
- self.cacheable = 0 # may this output get cached by http proxies/caches?
- self.page = None
- self._dicts = None
-
- # Fix dircaching problems on Windows 9x
- if IsWin9x():
- import dircache
- dircache.reset()
-
- # Check for dumb proxy requests
- # TODO relying on request_uri will not work on all servers, especially
- # not on external non-Apache servers
- self.forbidden = False
- if self.request_uri.startswith('http://'):
- self.makeForbidden403()
-
- # Init
- else:
- self.writestack = []
- self.clock = Clock()
- # order is important here!
- self.__dict__.update(properties)
- self._load_multi_cfg()
-
- self.isSpiderAgent = self.check_spider()
-
- # Set decode charsets. Input from the user is always in
- # config.charset, which is the page charsets. Except
- # path_info, which may use utf-8, and handled by decodePagename.
- self.decode_charsets = [config.charset]
-
- # hierarchical wiki - set rootpage
- from MoinMoin.Page import Page
- #path = self.getPathinfo()
- #if path.startswith('/'):
- # pages = path[1:].split('/')
- # if 0: # len(path) > 1:
- # ## breaks MainPage/SubPage on flat storage
- # rootname = u'/'.join(pages[:-1])
- # else:
- # # this is the usual case, as it ever was...
- # rootname = u""
- #else:
- # # no extra path after script name
- # rootname = u""
-
- self.args = {}
- self.form = {}
-
- # MOVED: this was in run() method, but moved here for auth module being able to use it
- if not self.query_string.startswith('action=xmlrpc'):
- self.args = self.form = self.setup_args()
-
- rootname = u''
- self.rootpage = Page(self, rootname, is_rootpage=1)
-
- self.user = self.get_user_from_form()
-
- if not self.query_string.startswith('action=xmlrpc'):
- if not self.forbidden and self.isForbidden():
- self.makeForbidden403()
- if not self.forbidden and self.surge_protect():
- self.makeUnavailable503()
-
- from MoinMoin import i18n
-
- self.logger = None
- self.pragma = {}
- self.mode_getpagelinks = 0
- self.no_closing_html_code = 0
-
- self.i18n = i18n
- self.lang = i18n.requestLanguage(self)
- # Language for content. Page content should use the wiki
- # default lang, but generated content like search results
- # should use the user language.
- self.content_lang = self.cfg.language_default
- self.getText = lambda text, i18n=self.i18n, request=self, lang=self.lang, **kv: i18n.getText(text, request, lang, kv.get('formatted', True))
-
- self.opened_logs = 0
- self.reset()
-
- def surge_protect(self):
- """ check if someone requesting too much from us """
- validuser = self.user.valid
- current_id = validuser and self.user.name or self.remote_addr
-
- #### DEBUT HACK : Excemption pour nos proxies
- if not validuser and (current_id.startswith('127.') or current_id in self.cfg.ip_url_replace.keys()): # localnet
- #### FIN DU HACK
- return False
- current_action = self.form.get('action', ['show'])[0]
-
- limits = self.cfg.surge_action_limits
- default_limit = self.cfg.surge_action_limits.get('default', (30, 60))
-
- now = int(time.time())
- surgedict = {}
- surge_detected = False
-
- try:
- cache = caching.CacheEntry(self, 'surgeprotect', 'surge-log')
- if cache.exists():
- data = cache.content()
- data = data.split("\n")
- for line in data:
- try:
- id, t, action, surge_indicator = line.split("\t")
- t = int(t)
- maxnum, dt = limits.get(action, default_limit)
- if t >= now - dt:
- events = surgedict.setdefault(id, copy.copy({}))
- timestamps = events.setdefault(action, copy.copy([]))
- timestamps.append((t, surge_indicator))
- except StandardError, err:
- pass
-
- maxnum, dt = limits.get(current_action, default_limit)
- events = surgedict.setdefault(current_id, copy.copy({}))
- timestamps = events.setdefault(current_action, copy.copy([]))
- surge_detected = len(timestamps) > maxnum
-
- surge_indicator = surge_detected and "!" or ""
- timestamps.append((now, surge_indicator))
- if surge_detected:
- if len(timestamps) < maxnum*2:
- timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-
- if current_action != 'AttachFile': # don't add AttachFile accesses to all or picture galleries will trigger SP
- current_action = 'all' # put a total limit on user's requests
- maxnum, dt = limits.get(current_action, default_limit)
- events = surgedict.setdefault(current_id, copy.copy({}))
- timestamps = events.setdefault(current_action, copy.copy([]))
- surge_detected = surge_detected or len(timestamps) > maxnum
-
- surge_indicator = surge_detected and "!" or ""
- timestamps.append((now, surge_indicator))
- if surge_detected:
- if len(timestamps) < maxnum*2:
- timestamps.append((now + self.cfg.surge_lockout_time, surge_indicator)) # continue like that and get locked out
-
- data = []
- for id, events in surgedict.items():
- for action, timestamps in events.items():
- for t, surge_indicator in timestamps:
- data.append("%s\t%d\t%s\t%s" % (id, t, action, surge_indicator))
- data = "\n".join(data)
- cache.update(data)
- except StandardError, err:
- pass
-
- return surge_detected
-
- def getDicts(self):
- """ Lazy initialize the dicts on the first access """
- if self._dicts is None:
- from MoinMoin import wikidicts
- dicts = wikidicts.GroupDict(self)
- dicts.scandicts()
- self._dicts = dicts
- return self._dicts
-
- def delDicts(self):
- """ Delete the dicts, used by some tests """
- del self._dicts
- self._dicts = None
-
- dicts = property(getDicts, None, delDicts)
-
- def _load_multi_cfg(self):
- # protect against calling multiple times
- if not hasattr(self, 'cfg'):
- from MoinMoin import multiconfig
- self.cfg = multiconfig.getConfig(self.url)
-
- def setAcceptedCharsets(self, accept_charset):
- """ Set accepted_charsets by parsing accept-charset header
-
- Set self.accepted_charsets to an ordered list based on
- http_accept_charset.
-
- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616.txt
-
- TODO: currently no code use this value.
-
- @param accept_charset: accept-charset header
- """
- charsets = []
- if accept_charset:
- accept_charset = accept_charset.lower()
- # Add iso-8859-1 if needed
- if (not '*' in accept_charset and
- accept_charset.find('iso-8859-1') < 0):
- accept_charset += ',iso-8859-1'
-
- # Make a list, sorted by quality value, using Schwartzian Transform
- # Create list of tuples (value, name) , sort, extract names
- for item in accept_charset.split(','):
- if ';' in item:
- name, qval = item.split(';')
- qval = 1.0 - float(qval.split('=')[1])
- else:
- name, qval = item, 0
- charsets.append((qval, name))
- charsets.sort()
- # Remove *, its not clear what we should do with it later
- charsets = [name for qval, name in charsets if name != '*']
-
- self.accepted_charsets = charsets
-
- def _setup_vars_from_std_env(self, env):
- """ Set common request variables from CGI environment
-
- Parse a standard CGI environment as created by common web
- servers. Reference: http://www.faqs.org/rfcs/rfc3875.html
-
- @param env: dict like object containing cgi meta variables
- """
- # Values we can just copy
- self.env = env
- self.http_accept_language = env.get('HTTP_ACCEPT_LANGUAGE',
- self.http_accept_language)
- self.server_name = env.get('SERVER_NAME', self.server_name)
- self.server_port = env.get('SERVER_PORT', self.server_port)
- self.saved_cookie = env.get('HTTP_COOKIE', '')
- self.script_name = env.get('SCRIPT_NAME', '')
- self.path_info = env.get('PATH_INFO', '')
- self.query_string = env.get('QUERY_STRING', '')
- self.request_method = env.get('REQUEST_METHOD', None)
- self.remote_addr = env.get('REMOTE_ADDR', '')
- self.http_user_agent = env.get('HTTP_USER_AGENT', '')
-
- # REQUEST_URI is not part of CGI spec, but an addition of Apache.
- self.request_uri = env.get('REQUEST_URI', '')
-
- # Values that need more work
- self.setHttpReferer(env.get('HTTP_REFERER'))
- self.setIsSSL(env)
- self.setHost(env.get('HTTP_HOST'))
- self.fixURI(env)
- self.setURL(env)
-
- ##self.debugEnvironment(env)
-
- def setHttpReferer(self, referer):
- """ Set http_referer, making sure its ascii
-
- IE might send non-ascii value.
- """
- value = ''
- if referer:
- value = unicode(referer, 'ascii', 'replace')
- value = value.encode('ascii', 'replace')
- self.http_referer = value
-
- def setIsSSL(self, env):
- """ Set is_ssl
-
- @param env: dict like object containing cgi meta variables
- """
- self.is_ssl = bool(env.get('SSL_PROTOCOL') or
- env.get('SSL_PROTOCOL_VERSION') or
- env.get('HTTPS') == 'on')
-
- def setHost(self, host=None):
- """ Set http_host
-
- Create from server name and port if missing. Previous code
- default to localhost.
- """
- if not host:
- port = ''
- standardPort = ('80', '443')[self.is_ssl]
- if self.server_port != standardPort:
- port = ':' + self.server_port
- host = self.server_name + port
- self.http_host = host
-
- def fixURI(self, env):
- """ Fix problems with script_name and path_info
-
- Handle the strange charset semantics on Windows and other non
- posix systems. path_info is transformed into the system code
- page by the web server. Additionally, paths containing dots let
- most webservers choke.
-
- Broken environment variables in different environments:
- path_info script_name
- Apache1 X X PI does not contain dots
- Apache2 X X PI is not encoded correctly
- IIS X X path_info include script_name
- Other ? - ? := Possible and even RFC-compatible.
- - := Hopefully not.
-
- @param env: dict like object containing cgi meta variables
- """
- # Fix the script_name when using Apache on Windows.
- server_software = env.get('SERVER_SOFTWARE', '')
- if os.name == 'nt' and server_software.find('Apache/') != -1:
- # Removes elements ending in '.' from the path.
- self.script_name = '/'.join([x for x in self.script_name.split('/')
- if not x.endswith('.')])
-
- # Fix path_info
- if os.name != 'posix' and self.request_uri != '':
- # Try to recreate path_info from request_uri.
- import urlparse
- scriptAndPath = urlparse.urlparse(self.request_uri)[2]
- path = scriptAndPath.replace(self.script_name, '', 1)
- self.path_info = wikiutil.url_unquote(path, want_unicode=False)
- elif os.name == 'nt':
- # Recode path_info to utf-8
- path = wikiutil.decodeWindowsPath(self.path_info)
- self.path_info = path.encode("utf-8")
-
- # Fix bug in IIS/4.0 when path_info contain script_name
- if self.path_info.startswith(self.script_name):
- self.path_info = self.path_info[len(self.script_name):]
-
- def setURL(self, env):
- """ Set url, used to locate wiki config
-
- This is the place to manipulate url parts as needed.
-
- @param env: dict like object containing cgi meta variables or
- http headers.
- """
- # If we serve on localhost:8000 and use a proxy on
- # example.com/wiki, our urls will be example.com/wiki/pagename
- # Same for the wiki config - they must use the proxy url.
- self.rewriteHost(env)
- self.rewriteURI(env)
-
- if not self.request_uri:
- self.request_uri = self.makeURI()
- self.url = self.http_host + self.request_uri
-
- def rewriteHost(self, env):
- """ Rewrite http_host transparently
-
- Get the proxy host using 'X-Forwarded-Host' header, added by
- Apache 2 and other proxy software.
-
- TODO: Will not work for Apache 1 or others that don't add this
- header.
-
- TODO: If we want to add an option to disable this feature it
- should be in the server script, because the config is not
- loaded at this point, and must be loaded after url is set.
-
- @param env: dict like object containing cgi meta variables or
- http headers.
- """
- proxy_host = (env.get(self.proxy_host) or
- env.get(cgiMetaVariable(self.proxy_host)))
- if proxy_host:
- self.http_host = proxy_host
-
- def rewriteURI(self, env):
- """ Rewrite request_uri, script_name and path_info transparently
-
- Useful when running mod python or when running behind a proxy,
- e.g run on localhost:8000/ and serve as example.com/wiki/.
-
- Uses private 'X-Moin-Location' header to set the script name.
- This allow setting the script name when using Apache 2
- directive::
-
-
- RequestHeader set X-Moin-Location /my/wiki/
-
-
- TODO: does not work for Apache 1 and others that do not allow
- setting custom headers per request.
-
- @param env: dict like object containing cgi meta variables or
- http headers.
- """
- location = (env.get(self.moin_location) or
- env.get(cgiMetaVariable(self.moin_location)))
- if location is None:
- return
-
- scriptAndPath = self.script_name + self.path_info
- location = location.rstrip('/')
- self.script_name = location
-
- # This may happen when using mod_python
- if scriptAndPath.startswith(location):
- self.path_info = scriptAndPath[len(location):]
-
- # Recreate the URI from the modified parts
- if self.request_uri:
- self.request_uri = self.makeURI()
-
- def makeURI(self):
- """ Return uri created from uri parts """
- uri = self.script_name + wikiutil.url_quote(self.path_info)
- if self.query_string:
- uri += '?' + self.query_string
- return uri
-
- def splitURI(self, uri):
- """ Return path and query splited from uri
-
- Just like CGI environment, the path is unquoted, the query is
- not.
- """
- if '?' in uri:
- path, query = uri.split('?', 1)
- else:
- path, query = uri, ''
- return wikiutil.url_unquote(path, want_unicode=False), query
-
- def get_user_from_form(self):
- """ read the maybe present UserPreferences form and call get_user with the values """
- name = self.form.get('name', [None])[0]
- password = self.form.get('password', [None])[0]
- login = self.form.has_key('login')
- logout = self.form.has_key('logout')
- u = self.get_user_default_unknown(name=name, password=password,
- login=login, logout=logout,
- user_obj=None)
- return u
-
- def get_user_default_unknown(self, **kw):
- """ call do_auth and if it doesnt return a user object, make some "Unknown User" """
- user_obj = self.get_user_default_None(**kw)
- if user_obj is None:
- user_obj = user.User(self, auth_method="request:427")
- return user_obj
-
- def get_user_default_None(self, **kw):
- """ loop over auth handlers, return a user obj or None """
- name = kw.get('name')
- password = kw.get('password')
- login = kw.get('login')
- logout = kw.get('logout')
- user_obj = kw.get('user_obj')
- for auth in self.cfg.auth:
- user_obj, continue_flag = auth(self,
- name=name, password=password,
- login=login, logout=logout,
- user_obj=user_obj)
- if not continue_flag:
- break
- return user_obj
-
- def reset(self):
- """ Reset request state.
-
- Called after saving a page, before serving the updated
- page. Solves some practical problems with request state
- modified during saving.
-
- """
- # This is the content language and has nothing to do with
- # The user interface language. The content language can change
- # during the rendering of a page by lang macros
- self.current_lang = self.cfg.language_default
-
- self._all_pages = None
- # caches unique ids
- self._page_ids = {}
- # keeps track of pagename/heading combinations
- # parsers should use this dict and not a local one, so that
- # macros like TableOfContents in combination with Include
- # can work
- self._page_headings = {}
-
- if hasattr(self, "_fmt_hd_counters"):
- del self._fmt_hd_counters
-
- def loadTheme(self, theme_name):
- """ Load the Theme to use for this request.
-
- @param theme_name: the name of the theme
- @type theme_name: str
- @rtype: int
- @return: success code
- 0 on success
- 1 if user theme could not be loaded,
- 2 if a hard fallback to modern theme was required.
- """
- fallback = 0
- if theme_name == "":
- theme_name = self.cfg.theme_default
-
- try:
- Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name,
- 'Theme')
- except wikiutil.PluginMissingError:
- fallback = 1
- try:
- Theme = wikiutil.importPlugin(self.cfg, 'theme',
- self.cfg.theme_default, 'Theme')
- except wikiutil.PluginMissingError:
- fallback = 2
- from MoinMoin.theme.modern import Theme
-
- self.theme = Theme(self)
- return fallback
-
- def setContentLanguage(self, lang):
- """ Set the content language, used for the content div
-
- Actions that generate content in the user language, like search,
- should set the content direction to the user language before they
- call send_title!
- """
- self.content_lang = lang
- self.current_lang = lang
-
- def getPragma(self, key, defval=None):
- """ Query a pragma value (#pragma processing instruction)
-
- Keys are not case-sensitive.
- """
- return self.pragma.get(key.lower(), defval)
-
- def setPragma(self, key, value):
- """ Set a pragma value (#pragma processing instruction)
-
- Keys are not case-sensitive.
- """
- self.pragma[key.lower()] = value
-
- def getPathinfo(self):
- """ Return the remaining part of the URL. """
- return self.path_info
-
- def getScriptname(self):
- """ Return the scriptname part of the URL ('/path/to/my.cgi'). """
- if self.script_name == '/':
- return ''
- return self.script_name
-
- def getPageNameFromQueryString(self):
- """ Try to get pagename from the query string
-
- Support urls like http://netloc/script/?page_name. Allow
- solving path_info encoding problems by calling with the page
- name as a query.
- """
- pagename = wikiutil.url_unquote(self.query_string, want_unicode=False)
- pagename = self.decodePagename(pagename)
- pagename = self.normalizePagename(pagename)
- return pagename
-
- def getKnownActions(self):
- """ Create a dict of avaiable actions
-
- Return cached version if avaiable.
-
- @rtype: dict
- @return: dict of all known actions
- """
- try:
- self.cfg._known_actions # check
- except AttributeError:
- from MoinMoin import wikiaction
- # Add built in actions from wikiaction
- actions = [name[3:] for name in wikiaction.__dict__
- if name.startswith('do_')]
-
- # Add plugins
- dummy, plugins = wikiaction.getPlugins(self)
- actions.extend(plugins)
-
- # Add extensions
- from MoinMoin.action import extension_actions
- actions.extend(extension_actions)
-
- # TODO: Use set when we require Python 2.3
- actions = dict(zip(actions, [''] * len(actions)))
- self.cfg._known_actions = actions
-
- # Return a copy, so clients will not change the dict.
- return self.cfg._known_actions.copy()
-
- def getAvailableActions(self, page):
- """ Get list of avaiable actions for this request
-
- The dict does not contain actions that starts with lower
- case. Themes use this dict to display the actions to the user.
-
- @param page: current page, Page object
- @rtype: dict
- @return: dict of avaiable actions
- """
- if self._available_actions is None:
- # Add actions for existing pages only, including deleted pages.
- # Fix *OnNonExistingPage bugs.
- if not (page.exists(includeDeleted=1) and
- self.user.may.read(page.page_name)):
- return []
-
- # Filter non ui actions (starts with lower case letter)
- actions = self.getKnownActions()
- for key in actions.keys():
- if key[0].islower():
- del actions[key]
-
- # Filter wiki excluded actions
- for key in self.cfg.actions_excluded:
- if key in actions:
- del actions[key]
-
- # Filter actions by page type, acl and user state
- excluded = []
- if ((page.isUnderlayPage() and not page.isStandardPage()) or
- not self.user.may.write(page.page_name) or
- not self.user.may.delete(page.page_name)):
- # Prevent modification of underlay only pages, or pages
- # the user can't write and can't delete
- excluded = [u'RenamePage', u'DeletePage',] # AttachFile must NOT be here!
- for key in excluded:
- if key in actions:
- del actions[key]
-
- self._available_actions = actions
-
- # Return a copy, so clients will not change the dict.
- return self._available_actions.copy()
-
- def redirectedOutput(self, function, *args, **kw):
- """ Redirect output during function, return redirected output """
- buffer = StringIO.StringIO()
- self.redirect(buffer)
- try:
- function(*args, **kw)
- finally:
- self.redirect()
- text = buffer.getvalue()
- buffer.close()
- return text
-
- def redirect(self, file=None):
- """ Redirect output to file, or restore saved output """
- if file:
- self.writestack.append(self.write)
- self.write = file.write
- else:
- self.write = self.writestack.pop()
-
- def reset_output(self):
- """ restore default output method
- destroy output stack
- (useful for error messages)
- """
- if self.writestack:
- self.write = self.writestack[0]
- self.writestack = []
-
- def log(self, msg):
- """ Log to stderr, which may be error.log """
- msg = msg.strip()
- # Encode unicode msg
- if isinstance(msg, unicode):
- msg = msg.encode(config.charset)
- # Add time stamp
- msg = '[%s] %s\n' % (time.asctime(), msg)
- sys.stderr.write(msg)
-
- def write(self, *data):
- """ Write to output stream.
- """
- raise NotImplementedError
-
- def encode(self, data):
- """ encode data (can be both unicode strings and strings),
- preparing for a single write()
- """
- wd = []
- for d in data:
- try:
- if isinstance(d, unicode):
- # if we are REALLY sure, we can use "strict"
- d = d.encode(config.charset, 'replace')
- wd.append(d)
- except UnicodeError:
- print >>sys.stderr, "Unicode error on: %s" % repr(d)
- return ''.join(wd)
-
- def decodePagename(self, name):
- """ Decode path, possibly using non ascii characters
-
- Does not change the name, only decode to Unicode.
-
- First split the path to pages, then decode each one. This enables
- us to decode one page using config.charset and another using
- utf-8. This situation happens when you try to add to a name of
- an existing page.
-
- See http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
-
- @param name: page name, string
- @rtype: unicode
- @return decoded page name
- """
- # Split to pages and decode each one
- pages = name.split('/')
- decoded = []
- for page in pages:
- # Recode from utf-8 into config charset. If the path
- # contains user typed parts, they are encoded using 'utf-8'.
- if config.charset != 'utf-8':
- try:
- page = unicode(page, 'utf-8', 'strict')
- # Fit data into config.charset, replacing what won't
- # fit. Better have few "?" in the name than crash.
- page = page.encode(config.charset, 'replace')
- except UnicodeError:
- pass
-
- # Decode from config.charset, replacing what can't be decoded.
- page = unicode(page, config.charset, 'replace')
- decoded.append(page)
-
- # Assemble decoded parts
- name = u'/'.join(decoded)
- return name
-
- def normalizePagename(self, name):
- """ Normalize page name
-
- Convert '_' to spaces - allows using nice URLs with spaces, with no
- need to quote.
-
- Prevent creating page names with invisible characters or funny
- whitespace that might confuse the users or abuse the wiki, or
- just does not make sense.
-
- Restrict even more group pages, so they can be used inside acl
- lines.
-
- @param name: page name, unicode
- @rtype: unicode
- @return: decoded and sanitized page name
- """
- # Replace underscores with spaces
- name = name.replace(u'_', u' ')
-
- # Strip invalid characters
- name = config.page_invalid_chars_regex.sub(u'', name)
-
- # Split to pages and normalize each one
- pages = name.split(u'/')
- normalized = []
- for page in pages:
- # Ignore empty or whitespace only pages
- if not page or page.isspace():
- continue
-
- # Cleanup group pages.
- # Strip non alpha numeric characters, keep white space
- if wikiutil.isGroupPage(self, page):
- page = u''.join([c for c in page
- if c.isalnum() or c.isspace()])
-
- # Normalize white space. Each name can contain multiple
- # words separated with only one space. Split handle all
- # 30 unicode spaces (isspace() == True)
- page = u' '.join(page.split())
-
- normalized.append(page)
-
- # Assemble components into full pagename
- name = u'/'.join(normalized)
- return name
-
- def read(self, n):
- """ Read n bytes from input stream.
- """
- raise NotImplementedError
-
- def flush(self):
- """ Flush output stream.
- """
- raise NotImplementedError
-
- def check_spider(self):
- """ check if the user agent for current request is a spider/bot """
- isSpider = False
- spiders = self.cfg.ua_spiders
- if spiders:
- ua = self.getUserAgent()
- if ua:
- isSpider = re.search(spiders, ua, re.I) is not None
- return isSpider
-
- def isForbidden(self):
- """ check for web spiders and refuse anything except viewing """
- forbidden = 0
- # we do not have a parsed query string here, so we can just do simple matching
- qs = self.query_string
- if ((qs != '' or self.request_method != 'GET') and
- not 'action=rss_rc' in qs and
- # allow spiders to get attachments and do 'show'
- not ('action=AttachFile' in qs and 'do=get' in qs) and
- not 'action=show' in qs
- ):
- forbidden = self.isSpiderAgent
-
- if not forbidden and self.cfg.hosts_deny:
- ip = self.remote_addr
- for host in self.cfg.hosts_deny:
- if host[-1] == '.' and ip.startswith(host):
- forbidden = 1
- #self.log("hosts_deny (net): %s" % str(forbidden))
- break
- if ip == host:
- forbidden = 1
- #self.log("hosts_deny (ip): %s" % str(forbidden))
- break
- return forbidden
-
- def setup_args(self, form=None):
- """ Return args dict
-
- In POST request, invoke _setup_args_from_cgi_form to handle
- possible file uploads. For other request simply parse the query
- string.
-
- Warning: calling with a form might fail, depending on the type
- of the request! Only the request know which kind of form it can
- handle.
-
- TODO: The form argument should be removed in 1.5.
- """
- if form is not None or self.request_method == 'POST':
- return self._setup_args_from_cgi_form(form)
- args = cgi.parse_qs(self.query_string, keep_blank_values=1)
- return self.decodeArgs(args)
-
- def _setup_args_from_cgi_form(self, form=None):
- """ Return args dict from a FieldStorage
-
- Create the args from a standard cgi.FieldStorage or from given
- form. Each key contain a list of values.
-
- @param form: a cgi.FieldStorage
- @rtype: dict
- @return: dict with form keys, each contains a list of values
- """
- if form is None:
- form = cgi.FieldStorage()
-
- args = {}
- for key in form:
- values = form[key]
- if not isinstance(values, list):
- values = [values]
- fixedResult = []
- for item in values:
- fixedResult.append(item.value)
- if isinstance(item, cgi.FieldStorage) and item.filename:
- # Save upload file name in a separate key
- args[key + '__filename__'] = item.filename
- args[key] = fixedResult
-
- return self.decodeArgs(args)
-
- def decodeArgs(self, args):
- """ Decode args dict
-
- Decoding is done in a separate path because it is reused by
- other methods and sub classes.
- """
- decode = wikiutil.decodeUserInput
- result = {}
- for key in args:
- if key + '__filename__' in args:
- # Copy file data as is
- result[key] = args[key]
- elif key.endswith('__filename__'):
- result[key] = decode(args[key], self.decode_charsets)
- else:
- result[key] = [decode(value, self.decode_charsets)
- for value in args[key]]
- return result
-
- def getBaseURL(self):
- """ Return a fully qualified URL to this script. """
- return self.getQualifiedURL(self.getScriptname())
-
- def getQualifiedURL(self, uri=''):
- """ Return an absolute URL starting with schema and host.
-
- Already qualified urls are returned unchanged.
-
- @param uri: server rooted uri e.g /scriptname/pagename. It
- must start with a slash. Must be ascii and url encoded.
- """
- import urlparse
- scheme = urlparse.urlparse(uri)[0]
- if scheme:
- return uri
-
- scheme = ('http', 'https')[self.is_ssl]
- result = "%s://%s%s" % (scheme, self.http_host, uri)
-
- # This might break qualified urls in redirects!
- # e.g. mapping 'http://netloc' -> '/'
- return wikiutil.mapURL(self, result)
-
- def getUserAgent(self):
- """ Get the user agent. """
- return self.http_user_agent
-
- def makeForbidden(self, resultcode, msg):
- statusmsg = {
- 403: 'FORBIDDEN',
- 503: 'Service unavailable',
- }
- self.http_headers([
- 'Status: %d %s' % (resultcode, statusmsg[resultcode]),
- 'Content-Type: text/plain'
- ])
- self.write(msg)
- self.setResponseCode(resultcode)
- self.forbidden = True
-
- def makeForbidden403(self):
- self.makeForbidden(403, 'You are not allowed to access this!\r\n')
-
- def makeUnavailable503(self):
- self.makeForbidden(503, "Warning:\r\n"
- "You triggered the wiki's surge protection by doing too many requests in a short time.\r\n"
- "Please make a short break reading the stuff you already got.\r\n"
- "When you restart doing requests AFTER that, slow down or you might get locked out for a longer time!\r\n")
-
- def initTheme(self):
- """ Set theme - forced theme, user theme or wiki default """
- if self.cfg.theme_force:
- theme_name = self.cfg.theme_default
- #### DEBUT HACK : Utilisation d'un thème différent pour www.crans.org
- if self.remote_addr in self.cfg.ip_theme.keys():
- theme_name = self.cfg.ip_theme[self.remote_addr]
- #### FIN DU HACK
- else:
- theme_name = self.user.theme_name
- self.loadTheme(theme_name)
-
- def run(self):
- # Exit now if __init__ failed or request is forbidden
- if self.failed or self.forbidden:
- #Don't sleep()! Seems to bind too much resources, so twisted will
- #run out of threads, files, whatever (with low CPU load) and stop
- #serving requests.
- #if self.forbidden:
- # time.sleep(10) # let the sucker wait!
- return self.finish()
-
- self.open_logs()
- _ = self.getText
- self.clock.start('run')
-
- # Imports
- from MoinMoin.Page import Page
-
- if self.query_string == 'action=xmlrpc':
- from MoinMoin.wikirpc import xmlrpc
- xmlrpc(self)
- return self.finish()
-
- if self.query_string == 'action=xmlrpc2':
- from MoinMoin.wikirpc import xmlrpc2
- xmlrpc2(self)
- return self.finish()
-
- # parse request data
- try:
- self.initTheme()
-
- # MOVED: moved to __init__() for auth module being able to use it
- #self.args = self.setup_args()
- #self.form = self.args
-
- action = self.form.get('action',[None])[0]
-
- # Get pagename
- # The last component in path_info is the page name, if any
- path = self.getPathinfo()
- if path.startswith('/'):
- pagename = self.normalizePagename(path)
- else:
- pagename = None
-
- # Handle request. We have these options:
-
- # 1. If user has a bad user name, delete its bad cookie and
- # send him to UserPreferences to make a new account.
- if not user.isValidName(self, self.user.name):
- msg = _("""Invalid user name {{{'%s'}}}.
-Name may contain any Unicode alpha numeric character, with optional one
-space between words. Group page name is not allowed.""") % self.user.name
- self.user = self.get_user_default_unknown(name=self.user.name, logout=True)
- page = wikiutil.getSysPage(self, 'UserPreferences')
- page.send_page(self, msg=msg)
-
- # 2. Or jump to page where user left off
- elif not pagename and not action and self.user.remember_last_visit:
- pagetrail = self.user.getTrail()
- if pagetrail:
- # Redirect to last page visited
- if ":" in pagetrail[-1]:
- wikitag, wikiurl, wikitail, error = wikiutil.resolve_wiki(self, pagetrail[-1])
- url = wikiurl + wikiutil.quoteWikinameURL(wikitail)
- else:
- url = Page(self, pagetrail[-1]).url(self)
- else:
- # Or to localized FrontPage
- url = wikiutil.getFrontPage(self).url(self)
- self.http_redirect(url)
- return self.finish()
-
- # 3. Or save drawing
- elif (self.form.has_key('filepath') and
- self.form.has_key('noredirect')):
- # looks like user wants to save a drawing
- from MoinMoin.action.AttachFile import execute
- # TODO: what if pagename is None?
- execute(pagename, self)
- raise MoinMoinNoFooter
-
- # 4. Or handle action
- elif action:
- # Use localized FrontPage if pagename is empty
- if not pagename:
- self.page = wikiutil.getFrontPage(self)
- else:
- self.page = Page(self, pagename)
-
- # Complain about unknown actions
- if not action in self.getKnownActions():
- self.http_headers()
- self.write(u'
Unknown action %s
' % wikiutil.escape(action))
-
- # Disallow non available actions
- elif (action[0].isupper() and
- not action in self.getAvailableActions(self.page)):
- # Send page with error
- msg = _("You are not allowed to do %s on this page.") % wikiutil.escape(action)
- if not self.user.valid:
- # Suggest non valid user to login
- msg += _(" %s and try again.", formatted=0) % _('Login') # XXX merge into 1 string after 1.5.3 release
- self.page.send_page(self, msg=msg)
-
- # Try action
- else:
- from MoinMoin.wikiaction import getHandler
- handler = getHandler(self, action)
- handler(self.page.page_name, self)
-
- # 5. Or redirect to another page
- elif self.form.has_key('goto'):
- self.http_redirect(Page(self, self.form['goto'][0]).url(self))
- return self.finish()
-
- # 6. Or (at last) visit pagename
- else:
- if not pagename and self.query_string:
- pagename = self.getPageNameFromQueryString()
- # pagename could be empty after normalization e.g. '///' -> ''
- if not pagename:
- pagename = wikiutil.getFrontPage(self).page_name
-
- # Visit pagename
- self.page = Page(self, pagename)
- self.page.send_page(self, count_hit=1)
-
- # generate page footer (actions that do not want this footer
- # use raise util.MoinMoinNoFooter to break out of the
- # default execution path, see the "except MoinMoinNoFooter"
- # below)
-
- self.clock.stop('run')
- self.clock.stop('total')
-
- # Close html code
- if not self.no_closing_html_code:
- if (self.cfg.show_timings and
- self.form.get('action', [None])[0] != 'print'):
- self.write('