view data/plugin/parser/text_x_arnica.py @ 246:f7a67224bf69

text_x_arnica: refactored sort_by*=boolean to sort_by=what
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Sun, 07 Sep 2008 23:39:53 +0200
parents 9636895bf71b
children 809f2840ae46
line wrap: on
line source
# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - arnica parser

    This parser is used to visualize a couple of images as a thumbnail gallery.
    Optionally, a description of an image can be added.

    By default the image name and its creation date is shown.
    If you click on a thumbnail you get navigation tools shown to slide through your images.

    Based on Gallery2 by ReimarBauer 2005-2008, ThomasWaldmann 2005, FlorianFesti 2006

    @copyright: 2008 by MoinMoin:ReimarBauer
    @license: GNU GPL, see COPYING for details.
"""

import os, re, StringIO
from random import randint

from MoinMoin import caching, wikiutil
from MoinMoin.action import AttachFile, cache
from MoinMoin.packages import packLine
from MoinMoin.Page import Page
from MoinMoin.util.dataset import TupleDataset, Column
from MoinMoin.widget.browser import DataBrowserWidget

try:
    import Image
except ImportError:
    Image = None

try:
    import ExifTags
except ImportError:
    ExifTags = None

parser_name = __name__.split('.')[-1]

def arnica_settings(target_page=u'', columns=4, file_regex=u'.',
                    image_for_webnail=False,
                    show_text=True, show_date=True, show_tools=False,
                    sort_by=("name", "date", "alias"),
                    reverse_sort=False,
                    only_items=False, template_itemlist=False,
                    album=False, album_title=unicode, album_image=u'',
                    renew=False,
                    thumbnail_width=wikiutil.UnitArgument('128', float, ['px', ], defaultunit='px'),
                    webnail_width=wikiutil.UnitArgument('640', float, ['px', ], defaultunit='px')):
    """ dummy function to initialize all default parameters for arnica. The parameters are checked for wrong input.
    @param target_page: page to read attachments from. empty pagename is current page.
    @param columns: number of columns for thumbnails
    @param file_regex: regex for selecting images
    @param image_for_webnail if set then the image is shown instead of the webnail
    @param show_text: default shows description
    @param show_date: default shows date from exif header, if available
    @param show_tools: default does not show the icon toolbar
    @param sort_by: default, sorts images by name, optional by date or by alias
    @param reverse_sort: if set, the file list is sorted in reverse order
    @param only_items: if set, only images which are described in listitem are shown, e.g.
                       * [[image1.jpg|alias]]
                       * [[image2.jpg|alias]]
    @param template_itemlist: if set, an item list is shown which could be copied into the wiki page
    @param album: if set, selects album mode, only thumbnail from first image is shown, related is album title and album_image
    @param album_title: default is pagename of the images for the album.
    @param album_image: image to show on album default is the first image
    @param renew: if set then all selected thumbnails_* and webnails_* are removed and will be recreated
    @param thumbnail_width: default width of thumbnail is 128px
    @param webnail_width: default width of webnail is 640px
    """
    if thumbnail_width:
        if thumbnail_width[1] == 'px':
            thumbnail_width = '%dpx' % int(thumbnail_width[0])
        else:
            thumbnail_width = '%g%s' % thumbnail_width
    if webnail_width:
        if webnail_width[1] == 'px':
            webnail_width = '%dpx' % int(webnail_width[0])
        else:
            webnail_width = '%g%s' % webnail_width
    return locals()

def _get_files(request, pagename):
    """ get files dependent on isPicture and ignores tmp. files
    @param pagename: name of the page where to get attachments
    """
    # ToDo remove tmp. files check later
    files = AttachFile._get_files(request, pagename)
    files = [fn for fn in files if wikiutil.isPicture(fn) and not fn.startswith('tmp.')]
    return files

class Parser:
    """ arnica parser """

    extensions = ['.jpg', '.jpeg', '.gif', '.png']

    def __init__(self, raw, request, **kw):
        self.pagename = request.page.page_name
        self.raw = raw
        self.request = request
        self.formatter = request.formatter
        self.form = None
        self._ = request.getText

        args = kw.get('format_args', '')
        self.init_settings = False
        # we use a macro definition to initialize the default init parameters
        # if a user enters a wrong parameter the failure is shown by the exception
        try:
            settings = wikiutil.invoke_extension_function(request, arnica_settings, args)
            for key, value in settings.items():
                setattr(self, key, value)
            # saves the state of valid input
            self.init_settings = True
        except ValueError, err:
            msg = u"arnica: %s" % err.args[0]
            # ToDo use formatter
            request.write(self.formatter.text(msg))

        self.inner_table_style = ' style="border-style:none; margin:10px;"'
        self.td_style = ' align="center" style="padding:0; margin:2px 2px; border-style:none"'
        self.web_key = [] # array for webnail keys (medium_resolution_image)
        self.high_resolution_image = [] # array for images
        self.thumb_key = [] # array for thumbnail keys
        self.exif_date = [] # array of exif date description
        self.imgtype = [] # array of imagetype of high_resolution_image image
        self.description = [] # array of description
        self.webnail_image = [] # array of images for forms

    def get_exif_info(self, file_name, key):
        """ gets exif info from image file
        @param: image file name
        @key: cache key for file_name
        """
        date = "--"
        if not (Image and ExifTags):
            # in that case no hint about a missing module
            return date

        if wikiutil.isPicture(file_name):
            date_key = 'D%s' % key

            if cache.exists(self.request, date_key):
                cache_date = caching.CacheEntry(self.request, cache.cache_arena, date_key+'.data',
                                       cache.cache_scope, do_locking=False)
                return cache_date.content()

            try:
                im = Image.open(file_name)
                rawExif = im._getexif().items()
            except (IOError, AttributeError, KeyError):
                pass
            else:
                for key, value in rawExif:
                    if ExifTags.TAGS.get(key) == 'DateTimeOriginal':
                        date = str(value)
                        date = date.replace(':', '-', 2)
                        date = date[0:19]
                        cache.put(self.request, date_key, date, filename=file_name, content_type='text/plain')
                        break
        return date

    def html_tools_restricted(self, this_target):
        """ shows restricted tools
            @param this_target: image
        """
        if not self.request.user.may.delete(self.pagename):
            return ''
        return """
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="rotate-to-left">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="rotate_left">
        <input type="hidden" name="target" value="%(this_target)s">
        <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_rotate_to_left.png" title="rotate to left">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="rotate-to-right">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="rotate_right">
         <input type="hidden" name="target" value="%(this_target)s">
        <input type="image"  value="submit" src="%(htdocs)s/arnica/img/arnica_rotate_to_right.png" title="rotate to right">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
     <div class="delete-image">
         <input type="hidden" name="action" value="arnica_slides">
         <input type="hidden" name="do" value="delete">
         <input type="hidden" name="target" value="%(this_target)s">
         <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_remove_image.png" title="move to bak">
     </div>
</form>
""" % {
            'url': Page(self.request, self.pagename).url(self.request),
            'style': self.td_style,
            'htdocs': self.request.cfg.url_prefix_static,
            "pagename": wikiutil.quoteWikinameURL(self.pagename),
            "this_target": this_target,
        }

    def html_tools(self, idx):
        """ html code of thumbnails view with contol
        @param idx: index position of corresponding data
        """

        this_image = self.high_resolution_image[idx]
        html = """
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="get-image">
        <input type="hidden" name="action" value="AttachFile">
        <input type="hidden" name="do" value="get">
        <input type="hidden" name="target" value="%(this_target)s">
        <input type="image" value="submit" src="%(htdocs)s/arnica/img/arnica_full_image.png" title="load image">
    </div>
</form>
<form action="%(url)s" method="POST" enctype="multipart/form-data">
    <div class="slide-show">
        <input type="hidden" name="action" value="arnica_slides">
        <input type="hidden" name="do" value="slide_show">
        <input type="hidden" name="alias" value="%(description)s">
        <input type="hidden" name="target" value="%(target)s">
        <input type="hidden" name="pagename" value="%(pagename)s">
        <input type="hidden" name="images" value="%(images)s">
        <input type="hidden" name="original_images" value="%(original_images)s">
        <input type="hidden" name="exif_date" value="%(exif_date)s">
        <input type="image" value="submit" title="slide show" src="%(htdocs)s/arnica/img/arnica_load_slide_show.png">
    </div>
</form>
%(html_tools_restricted)s
""" % {
            "url": Page(self.request, self.pagename).url(self.request),
            "pagename": self.pagename,
            "htdocs": self.request.cfg.url_prefix_static,
            "tablestyle": self.inner_table_style,
            "style": self.td_style,
            "thumbnail_width": self.thumbnail_width,
            "description": packLine([self.description[idx]] + self.description),
            "exif_date": packLine([self.exif_date[idx]] + self.exif_date),
            "target": self.webnail_image[idx],
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "images": packLine([self.webnail_image[idx]] + self.webnail_image),
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "this_target": self.high_resolution_image[idx],
            "html_tools_restricted": self.html_tools_restricted(this_image),
        }
        return html

    def html_show_tools(self, idx):
        """ shows toolbox
        @param idx: index position of corresponding data
        """

        html = ''
        if self.show_tools:
            html = '<div class="html-show-tools">%(tools)s</div>' % {
                "style": self.td_style,
                "tools": self.html_tools(idx)}
        return html

    def html_show_date(self, idx):
        """ shows date
        @param idx: index position of corresponding data
        """

        html = ''
        if self.show_date:
            html = '<div class="html-show-date">%(this_exif_date)s</div>' % {
                "this_exif_date": self.formatter.text(self.exif_date[idx])}
        return html

    def html_show_alias(self, idx):
        """ alias text below image
        @param idx: index position of corresponding data
        """

        html = ''
        if self.show_text:
            html = '<div class="html-show-alias"> %(this_alias)s</div>' % {
                    "this_alias": self.to_wikitext(self.description[idx])}
        return html

    def html_arrange_thumbnails(self, idx):
        """ defines arrangement of thumbnail, text, date and tools
        @param idx: index positionn of corresponding data
        """

        title = ""
        if self.album:
            title = '<div class="title">%(n)d images (%(album_title)s)</div>' % {"n": len(self.high_resolution_image),
                                                        "album_title": self.album_title or self.pagename}
        html = """
<div class="arrange-thumbnails">
    <form action="%(url)s" method="POST" enctype="multipart/form-data">
        <div class="image"> %(title)s
             <input type="hidden" name="action" value="arnica_slides">
             <input type="hidden" name="do" value="slide_show">
             <input type="hidden" name="alias" value="%(description)s">
             <input type="hidden" name="target" value="%(target)s">
             <input type="hidden" name="pagename" value="%(pagename)s">
             <input type="hidden" name="images" value="%(images)s">
             <input type="hidden" name="original_images" value="%(original_images)s">
             <input type="hidden" name="exif_date" value="%(exif_date)s">
             <input type="image" value="submit" title="slide show" src="%(thumbnail)s">
        </div>
    </form>
    %(alias_html)s
    %(date_html)s
    %(html_tools)s
</div>
""" % {
            "title": title,
            "tdstyle": self.td_style,
            "style": self.inner_table_style,
            "url": Page(self.request, self.pagename).url(self.request),
            "pagename": self.pagename,
            "description": packLine([self.description[idx]] + self.description),
            "exif_date": packLine([self.exif_date[idx]] + self.exif_date),
            "target": self.webnail_image[idx],
            "original_images": packLine([self.high_resolution_image[idx]] + self.high_resolution_image),
            "images": packLine([self.webnail_image[idx]] + self.webnail_image),
            "thumbnail": cache.url(self.request, self.thumb_key[idx]),
            "thumbnail_width": self.thumbnail_width,
            "html_tools": self.html_show_tools(idx),
            "date_html": self.html_show_date(idx),
            "alias_html": self.html_show_alias(idx),
        }
        return html

    def define_thumb_webnails(self, path, files, image_alias):
        """ creates lists for thumbnails and webnails
        @param path: path to attachment
        @param files: file names of images
        @param image_alias: text alias for image file
        """

        for attfile in files:
            if image_alias.get(attfile):
                # use alias
                self.description.append(image_alias.get(attfile)[0])
            else:
                # use filename
                self.description.append(attfile)
            self.high_resolution_image.append(attfile)
            fname, ext = os.path.splitext(attfile)
            key = cache.key(self.request, itemname=self.pagename, attachname=attfile)
            webnail = 'W' + key
            thumbfile = 'T' + key
            if ext in ('.gif', '.png'):
                self.imgtype.append('PNG')
            else:
                self.imgtype.append("JPEG")

            att_file = os.path.join(path, attfile)
            if os.path.exists(att_file):
                self.web_key.append(webnail)
                self.thumb_key.append(thumbfile)
                date = self.get_exif_info(att_file, key)
                self.exif_date.append(self.formatter.text(date))

    def to_wikitext(self, text):
        """ converts text to wiki name if it is written as WikiName or [[wikiname]]
        @param text: text to parse and render
        """
        text = ''.join(text)
        from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
        return wikiutil.renderText(self.request, WikiParser, text)

    def get_image_alias(self):
        """  gets the quotes from the item list and returns a dictionary of image and alias """
        # ToDo keep the order of the given quotes
        quotes = self.raw.split('\n')
        quotes = [quote.strip() for quote in quotes]
        quotes = [quote[2:] for quote in quotes if quote.startswith('* ')]
        image_alias = {}
        counter = 0
        for line in quotes:
            if line.startswith('[[') and line.endswith(']]'):
                img, alias = line[2:-2].split('|', 1)
                image_alias[img.strip()] = (self.formatter.text(alias.strip()), counter)
                counter += 1
        return image_alias

    def select_files(self, formatter):
        """ select files """
        # we need to take the page_name from the formatter.page otherwise
        # include does not work
        self.pagename = formatter.page.page_name
        if self.target_page and Page(self.request, self.target_page).exists() and self.request.user.may.read(self.target_page):
            self.pagename = self.target_page
        path = AttachFile.getAttachDir(self.request, self.pagename, create=1)
        image_dict = {}
        image_alias = self.get_image_alias()
        if self.only_items:
            # get the same order of files and aliastext as on the page written
            files = image_alias.keys()
            alias_text = image_alias.values()
            index = [alias_text[idx][1] for idx in range(len(files))]
            alias_text = [alias_text[idx][0] for idx in index]
            files = [files[idx] for idx in index]

            all_files = [fn for fn in files if wikiutil.isPicture(fn) and
                         AttachFile.exists(self.request, self.pagename, fn)]
            if self.sort_by == "alias":
                # ToDo use a longer var
                i = 0
                for attfile in all_files:
                    image_dict[alias_text[i]] = attfile
                    # ToDo use a longer var
                    i += 1
                keys = image_dict.keys()
                keys.sort()
                all_files = [image_dict[txt] for txt in keys]
        else:
            all_files = _get_files(self.request, self.pagename)
        if self.file_regex != u'.':
            all_files = [attfile for attfile in all_files if re.match(self.file_regex, attfile)]

        if self.sort_by == "name" and not self.only_items:
            all_files.sort()
        if self.sort_by == "date":
            for attfile in all_files:
                infile = os.path.join(path, attfile)
                ft_file = "%s%x" % (os.path.getmtime(infile), randint(0, 256))
                image_dict[ft_file] = attfile
            keys = image_dict.keys()
            keys.sort()
            all_files = [image_dict[txt] for txt in keys]
        image_dict.clear()
        if self.reverse_sort:
            all_files.reverse()
        if all_files:
            self.define_thumb_webnails(path, all_files, image_alias)
        return all_files

    def create_thumbnail_and_webnail_image(self, image, webnail, thumbnail, image_type):
        """ creates thumbnails and webnails cache files for given image type
        @param image: filename of image
        @param webnail: name of webnail file
        @param thumbnail: name of thumbnail file
        @param image_type: filetype of image
        """
        _ = self.request.getText
        if not Image:
            msg = _('The parser %(parser)s needs python imaging library (PIL) installed') % {'parser': parser_name}
            self.request.write(self.formatter.text(msg))
            return

        path = AttachFile.getAttachDir(self.request, self.pagename, create=1)
        imagef = os.path.join(path, image)

        if os.path.getsize(imagef) == 0:
            return

        if self.renew:
            cache.remove(self.request, webnail)
            cache.remove(self.request, thumbnail)

        if not cache.exists(self.request, webnail) or not cache.exists(self.request, thumbnail):
            try:
                im_obj = Image.open(imagef)
            except IOError:
                msg = _("Can not access attachment: %(filename)s. Often the case for this error is an emtpy image attachment.") % {"filename": os.path.basename(imagef)}
                self.request.write(self.formatter.text(msg))
                return
            else:
                # ToDo may be it is better to just copy the image to the new location
                # currently PIL does not support exif information
                if self.image_for_webnail:
                    buf = StringIO.StringIO()
                    im_obj.save(buf, image_type)
                    buf.flush()
                    buf.seek(0)
                    cache.put(self.request, webnail, buf, filename=image)
            # XXX later, a unit conversion method for using other units than 'px' is needed
            if not cache.exists(self.request, webnail):
                try:
                    exif = im_obj._getexif()
                except (AttributeError, KeyError):
                    exif = None
                try:
                    im_obj.thumbnail((int(self.webnail_width.strip('px')), int(self.webnail_width.strip('px'))), Image.ANTIALIAS)
                except IOError:
                    #  one reason can be that the image file is truncated
                    return
                if exif:
                    try:
                        process = {
                            1: lambda x: x,
                            2: lambda x: x.transpose(Image.FLIP_LEFT_RIGHT),
                            3: lambda x: x.transpose(Image.ROTATE_180),
                            4: lambda x: x.transpose(Image.FLIP_TOP_BOTTOM),
                            5: lambda x: x.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM),
                            6: lambda x: x.transpose(Image.ROTATE_270),
                            7: lambda x: x.transpose(Image.ROTATE_90).transpose(Image.FLIP_LEFT_RIGHT),
                            8: lambda x: x.transpose(Image.ROTATE_90),
                        }
                        im_obj = process[exif[274]](im_obj)
                    except KeyError:
                        pass

                buf = StringIO.StringIO()
                im_obj.save(buf, image_type)
                buf.flush()
                buf.seek(0)
                cache.put(self.request, webnail, buf, filename=image)
                buf.close()

            if not cache.exists(self.request, thumbnail):
                try:
                    im_obj.thumbnail((int(self.thumbnail_width.strip('px')), int(self.thumbnail_width.strip('px'))), Image.ANTIALIAS)
                except IOError:
                    # one reason can be that the image file is truncated
                    return
                buf = StringIO.StringIO()
                im_obj.save(buf, image_type)
                buf.flush()
                buf.seek(0)
                cache.put(self.request, thumbnail, buf, filename=image)
                buf.close()

    def render(self, formatter):
        """ renders thumbnails """

        _ = self._

        # checks if initializing of all attributes in __init__ was done
        if not self.init_settings:
            return

        if self.target_page and (not Page(self.request, self.target_page).exists() or not self.request.user.may.read(self.target_page)):
            text = _("""Page '%(new_pagename)s' does not exist or you don't have enough rights.""") % {"new_pagename": self.target_page}
            self.request.write(self.formatter.text(text))
            return

        if not self.select_files(formatter):
            text = _("No matching image file found!")
            self.request.write(self.formatter.text(text))
            return

        if self.template_itemlist:
            self.request.write(self.formatter.div(1, css_class="text"))
            text = _("""\
Copy the following listitems into the script.
Replace alias with the label you want.
Afterwards disable template_itemlist by setting it to False:""")
            self.request.write(self.formatter.text(text))
            self.request.write(self.formatter.div(1))
            self.request.write(self.formatter.preformatted(1))
            for image in self.high_resolution_image:
                text = ' * [[%(image)s|alias]]\n' % {
                           'image': image,
                        }
                self.request.write(self.formatter.text(text))
            self.request.write(self.formatter.preformatted(0))
            self.request.write(self.formatter.div(0))
            self.request.write(self.formatter.div(0))

        cols = min([self.columns, len(self.high_resolution_image)])
        if self.album:
            cols = 1
        data = TupleDataset()
        data.columns = []
        for dummy in range(cols):
            data.columns.extend([Column('', label=(''))])
        col_count = 1
        result = []
        for image in self.high_resolution_image:
            # ToDo use a longer var
            i = self.high_resolution_image.index(image)
            self.create_thumbnail_and_webnail_image(image, self.web_key[i],
                                                    self.thumb_key[i], self.imgtype[i])
            if not self.album:
                self.webnail_image = self.web_key
                text = self.html_arrange_thumbnails(i)
                if col_count <= cols:
                    result.append(''.join(text))
                if col_count == cols:
                    col_count = 0
                    data.addRow(tuple(result))
                    # resets the result list after the row is added
                    result = []
                col_count += 1
        if result and not self.album:
            # ToDo use a longer var
            for i in range(cols - col_count + 1):
                result.append('')
        if self.album:
            album_image = self.album_image or self.high_resolution_image[0]
            try:
                # ToDo use a longer var
                i = self.high_resolution_image.index(album_image)
                self.create_thumbnail_and_webnail_image(image, self.web_key[i], self.thumb_key[i], self.imgtype[i])
                self.webnail_image = self.web_key
                text = self.html_arrange_thumbnails(i)
            except ValueError:
                text = self.formatter.text(_("""You can't use as album image:
"%(album_image)s" because it does not exist or it is not listed
in your item list!""") % {"album_image": album_image, })

            result.append(''.join(text))

        # adds the last row if it is not filled up
        if result:
            data.addRow(tuple(result))
        browser = DataBrowserWidget(self.request, show_header=False)
        browser.setData(data)
        return browser.render(method="POST")

    def format(self, formatter):
        """ parser output """
        # checks if initializing of all attributes in __init__ was done
        if self.init_settings:
            self.request.write(self.formatter.div(1, css_class="arnica"))
            self.request.write(self.render(formatter))
            self.request.write(self.formatter.div(0))