source: documentViewer/documentViewer.py @ 599:3b79ae4a23ca

Last change on this file since 599:3b79ae4a23ca was 599:3b79ae4a23ca, checked in by casties, 11 years ago

normalize uri of annotated image.

File size: 42.6 KB
Line 
1from OFS.Folder import Folder
2from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
3from Products.PageTemplates.PageTemplateFile import PageTemplateFile
4from App.ImageFile import ImageFile
5from AccessControl import ClassSecurityInfo
6from AccessControl import getSecurityManager
7from Globals import package_home
8
9import xml.etree.ElementTree as ET
10
11import os
12import sys
13import urllib
14import logging
15import math
16import urlparse 
17import re
18import string
19import json
20
21from Products.MetaDataProvider import MetaDataFolder
22
23from SrvTxtUtils import getInt, utf8ify, getText, getHttpData, refreshingImageFileIndexHtml
24   
25def serializeNode(node, encoding="utf-8"):
26    """returns a string containing node as XML"""
27    s = ET.tostring(node)
28   
29    # 4Suite:
30    #    stream = cStringIO.StringIO()
31    #    Ft.Xml.Domlette.Print(node, stream=stream, encoding=encoding)
32    #    s = stream.getvalue()
33    #    stream.close()
34    return s
35
36def getMDText(node):
37    """returns the @text content from the MetaDataProvider metadata node"""
38    if isinstance(node, dict):
39        return node.get('@text', None)
40   
41    return node
42
43def getParentPath(path, cnt=1):
44    """returns pathname shortened by cnt"""
45    # make sure path doesn't end with /
46    path = path.rstrip('/')
47    # split by /, shorten, and reassemble
48    return '/'.join(path.split('/')[0:-cnt])
49
50def getPnForPf(docinfo, pf, default=0):
51    """returns image number for image file name or default"""
52    if 'imgFileNames' in docinfo:
53        pn = docinfo['imgFileNames'].get(pf, None)
54        if pn is None:
55            # try to cut extension
56            xi = pf.rfind('.')
57            if xi > 0:
58                pf = pf[:xi]
59                # try again, else return 0
60                pn = docinfo['imgFileNames'].get(pf, default)
61            else:
62                # no extension
63                pn = default
64               
65        return pn
66   
67    return default
68
69def getPfForPn(docinfo, pn, default=None):
70    """returns image file name for image number or default"""
71    if 'imgFileIndexes' in docinfo:
72        pn = docinfo['imgFileIndexes'].get(pn, default)
73        return pn
74   
75    return default
76
77
78##
79## documentViewer class
80##
81class documentViewer(Folder):
82    """document viewer"""
83    meta_type="Document viewer"
84   
85    security=ClassSecurityInfo()
86    manage_options=Folder.manage_options+(
87        {'label':'Configuration','action':'changeDocumentViewerForm'},
88        )
89   
90    metadataService = None
91    """MetaDataFolder instance"""
92   
93
94    #
95    # templates and forms
96    #
97    # viewMode templates
98    viewer_text = PageTemplateFile('zpt/viewer/viewer_text', globals())
99    viewer_xml = PageTemplateFile('zpt/viewer/viewer_xml', globals())
100    viewer_image = PageTemplateFile('zpt/viewer/viewer_image', globals())
101    viewer_index = PageTemplateFile('zpt/viewer/viewer_index', globals())
102    viewer_thumbs = PageTemplateFile('zpt/viewer/viewer_thumbs', globals())
103    viewer_indexonly = PageTemplateFile('zpt/viewer/viewer_indexonly', globals())
104    # available layer types (annotator not default)
105    builtinLayers = {'text': ['dict','search','gis'],
106                     'xml': None, 'image': None, 'index': ['extended']}
107    availableLayers = builtinLayers;
108    # layer templates
109    layer_text_dict = PageTemplateFile('zpt/viewer/layer_text_dict', globals())
110    layer_text_search = PageTemplateFile('zpt/viewer/layer_text_search', globals())
111    layer_text_annotator = PageTemplateFile('zpt/viewer/layer_text_annotator', globals())
112    layer_text_gis = PageTemplateFile('zpt/viewer/layer_text_gis', globals())
113    layer_text_pundit = PageTemplateFile('zpt/viewer/layer_text_pundit', globals())
114    layer_image_annotator = PageTemplateFile('zpt/viewer/layer_image_annotator', globals())
115    layer_image_search = PageTemplateFile('zpt/viewer/layer_image_search', globals())
116    layer_index_extended = PageTemplateFile('zpt/viewer/layer_index_extended', globals())
117    # toc templates
118    toc_thumbs = PageTemplateFile('zpt/viewer/toc_thumbs', globals())
119    toc_text = PageTemplateFile('zpt/viewer/toc_text', globals())
120    toc_figures = PageTemplateFile('zpt/viewer/toc_figures', globals())
121    toc_concordance = PageTemplateFile('zpt/viewer/toc_concordance', globals())
122    toc_handwritten = PageTemplateFile('zpt/viewer/toc_handwritten', globals())
123    toc_none = PageTemplateFile('zpt/viewer/toc_none', globals())
124    # other templates
125    common_template = PageTemplateFile('zpt/viewer/common_template', globals())
126    info_xml = PageTemplateFile('zpt/viewer/info_xml', globals())
127    docuviewer_css = ImageFile('css/docuviewer.css',globals())
128    # make docuviewer_css refreshable for development
129    docuviewer_css.index_html = refreshingImageFileIndexHtml
130    docuviewer_ie_css = ImageFile('css/docuviewer_ie.css',globals())
131    # make docuviewer_ie_css refreshable for development
132    #docuviewer_ie_css.index_html = refreshingImageFileIndexHtml
133    jquery_js = ImageFile('js/jquery.js',globals())
134   
135   
136    def __init__(self,id,imageScalerUrl=None,textServerName=None,title="",digilibBaseUrl=None,thumbcols=2,thumbrows=5,authgroups="mpiwg"):
137        """init document viewer"""
138        self.id=id
139        self.title=title
140        self.thumbcols = thumbcols
141        self.thumbrows = thumbrows
142        # authgroups is list of authorized groups (delimited by ,)
143        self.authgroups = [s.strip().lower() for s in authgroups.split(',')]
144        # create template folder so we can always use template.something
145       
146        templateFolder = Folder('template')
147        self['template'] = templateFolder # Zope-2.12 style
148        #self._setObject('template',templateFolder) # old style
149        try:
150            import MpdlXmlTextServer
151            textServer = MpdlXmlTextServer.MpdlXmlTextServer(id='fulltextclient',serverName=textServerName)
152            templateFolder['fulltextclient'] = textServer
153            #templateFolder._setObject('fulltextclient',textServer)
154        except Exception, e:
155            logging.error("Unable to create MpdlXmlTextServer for fulltextclient: "+str(e))
156           
157        try:
158            from Products.zogiLib.zogiLib import zogiLib
159            zogilib = zogiLib(id="zogilib", title="zogilib for docuviewer", dlServerURL=imageScalerUrl, layout="book")
160            templateFolder['zogilib'] = zogilib
161            #templateFolder._setObject('zogilib',zogilib)
162        except Exception, e:
163            logging.error("Unable to create zogiLib for zogilib: "+str(e))
164           
165        try:
166            # assume MetaDataFolder instance is called metadata
167            self.metadataService = getattr(self, 'metadata')
168        except Exception, e:
169            logging.error("Unable to find MetaDataFolder 'metadata': "+str(e))
170           
171        if digilibBaseUrl is not None:
172            self.digilibBaseUrl = digilibBaseUrl
173            self.digilibScalerUrl = digilibBaseUrl + '/servlet/Scaler'
174            self.digilibViewerUrl = digilibBaseUrl + '/jquery/digilib.html'
175           
176       
177    # proxy text server methods to fulltextclient
178    def getTextPage(self, **args):
179        """returns full text content of page"""
180        return self.template.fulltextclient.getTextPage(**args)
181
182    def getSearchResults(self, **args):
183        """loads list of search results and stores XML in docinfo"""
184        return self.template.fulltextclient.getSearchResults(**args)
185
186    def getResultsPage(self, **args):
187        """returns one page of the search results"""
188        return self.template.fulltextclient.getResultsPage(**args)
189
190    def getTextInfo(self, **args):
191        """returns document info from the text server"""
192        return self.template.fulltextclient.getTextInfo(**args)
193
194    def getToc(self, **args):
195        """loads table of contents and stores XML in docinfo"""
196        return self.template.fulltextclient.getToc(**args)
197
198    def getTocPage(self, **args):
199        """returns one page of the table of contents"""
200        return self.template.fulltextclient.getTocPage(**args)
201
202    def getRepositoryType(self, **args):
203        """get repository type"""
204        return self.template.fulltextclient.getRepositoryType(**args)
205
206    def getTextDownloadUrl(self, **args):
207        """get list of gis places on one page"""
208        return self.template.fulltextclient.getTextDownloadUrl(**args)
209 
210    def getPlacesOnPage(self, **args):
211        """get list of gis places on one page"""
212        return self.template.fulltextclient.getPlacesOnPage(**args)
213 
214    # Thumb list for CoolIris Plugin
215    thumbs_main_rss = PageTemplateFile('zpt/thumbs_main_rss', globals())
216    security.declareProtected('View','thumbs_rss')
217    def thumbs_rss(self,mode,url,viewMode="auto",start=None,pn=1):
218        '''
219        view it
220        @param mode: defines how to access the document behind url
221        @param url: url which contains display information
222        @param viewMode: image: display images, text: display text, default is auto (try text, else image)
223       
224        '''
225       
226        if not hasattr(self, 'template'):
227            # this won't work
228            logging.error("template folder missing!")
229            return "ERROR: template folder missing!"
230                       
231        if not self.digilibBaseUrl:
232            self.digilibBaseUrl = self.findDigilibUrl() or "http://nausikaa.mpiwg-berlin.mpg.de/digitallibrary"
233           
234        docinfo = self.getDocinfo(mode=mode,url=url)
235        #pageinfo = self.getPageinfo(start=start,current=pn,docinfo=docinfo)
236        pageinfo = self.getPageinfo(start=start,pn=pn, docinfo=docinfo)
237        ''' ZDES '''
238        pt = getattr(self.template, 'thumbs_main_rss')
239       
240        if viewMode=="auto": # automodus gewaehlt
241            if docinfo.has_key("textURL") or docinfo.get('textURLPath',None): #texturl gesetzt und textViewer konfiguriert
242                viewMode="text"
243            else:
244                viewMode="image"
245               
246        return pt(docinfo=docinfo,pageinfo=pageinfo,viewMode=viewMode)
247
248 
249    security.declareProtected('View','index_html')
250    def index_html(self, url, mode="texttool", viewMode="auto", viewLayer=None, tocMode=None, start=None, pn=None, pf=None):
251        """
252        show page
253        @param url: url which contains display information
254        @param mode: defines how to access the document behind url
255        @param viewMode: 'image': display images, 'text': display text, 'xml': display xml, default is 'auto'
256        @param viewLayer: sub-type of viewMode, e.g. layer 'dict' for viewMode='text'
257        @param tocMode: type of 'table of contents' for navigation (thumbs, text, figures, none)
258        """
259       
260        logging.debug("documentViewer(index_html) mode=%s url=%s viewMode=%s viewLayer=%s start=%s pn=%s pf=%s"%(mode,url,viewMode,viewLayer,start,pn,pf))
261       
262        if not hasattr(self, 'template'):
263            # this won't work
264            logging.error("template folder missing!")
265            return "ERROR: template folder missing!"
266           
267        if not getattr(self, 'digilibBaseUrl', None):
268            self.digilibBaseUrl = self.findDigilibUrl() or "http://digilib.mpiwg-berlin.mpg.de/digitallibrary"
269           
270        # mode=filepath should not have toc-thumbs
271        if tocMode is None:
272            if mode == "filepath":
273                tocMode = "none"
274            else:
275                tocMode = "thumbs"
276           
277        # docinfo: information about document (cached)
278        docinfo = self.getDocinfo(mode=mode,url=url,tocMode=tocMode)
279       
280        # userinfo: user settings (cached)
281        userinfo = self.getUserinfo()
282       
283        # auto viewMode: text if there is a text else images
284        if viewMode=="auto": 
285            if docinfo.get('textURLPath', None):
286                # docinfo.get('textURL', None) not implemented yet
287                viewMode = "text"
288                if viewLayer is None and 'viewLayer' not in userinfo:
289                    # use layer dict as default
290                    viewLayer = "dict"
291            else:
292                viewMode = "image"
293               
294        elif viewMode == "text_dict":
295            # legacy fix
296            viewMode = "text"
297            viewLayer = "dict"
298           
299        elif viewMode == 'images':
300            # legacy fix
301            viewMode = 'image'
302            self.REQUEST['viewMode'] = 'image'
303
304        # safe viewLayer in userinfo
305        userinfo['viewLayer'] = viewLayer
306               
307        # pageinfo: information about page (not cached)
308        pageinfo = self.getPageinfo(start=start, pn=pn, pf=pf, docinfo=docinfo, userinfo=userinfo, viewMode=viewMode, viewLayer=viewLayer, tocMode=tocMode)
309                   
310        # get template /template/viewer_$viewMode
311        pt = getattr(self.template, 'viewer_%s'%viewMode, None)
312        if pt is None:
313            logging.error("No template for viewMode=%s!"%viewMode)
314            # TODO: error page?
315            return "No template for viewMode=%s!"%viewMode
316       
317        # and execute with parameters
318        return pt(docinfo=docinfo, pageinfo=pageinfo)
319 
320    def getAvailableLayers(self):
321        """returns dict with list of available layers per viewMode"""
322        return self.availableLayers
323   
324    def findDigilibUrl(self):
325        """try to get the digilib URL from zogilib"""
326        url = self.template.zogilib.getDLBaseUrl()
327        return url
328   
329    def getScalerUrl(self, fn=None, pn=None, dw=100, dh=100, docinfo=None):
330        """returns URL to digilib Scaler with params"""
331        url = None
332        if docinfo is not None:
333            url = docinfo.get('imageURL', None)
334           
335        if url is None:
336            url = self.digilibScalerUrl
337            if fn is None and docinfo is not None:
338                fn = docinfo.get('imagePath','')
339           
340            url += "fn=%s"%fn
341           
342        if pn:
343            url += "&pn=%s"%pn
344           
345        url += "&dw=%s&dh=%s"%(dw,dh)
346        return url
347
348    def getDocumentViewerURL(self):
349        """returns the URL of this instance"""
350        return self.absolute_url()
351   
352    def getStyle(self, idx, selected, style=""):
353        """returns a string with the given style and append 'sel' if idx == selected."""
354        #logger("documentViewer (getstyle)", logging.INFO, "idx: %s selected: %s style: %s"%(idx,selected,style))
355        if idx == selected:
356            return style + 'sel'
357        else:
358            return style
359   
360    def getParams(self, param=None, val=None, params=None, duplicates=None):
361        """returns dict with URL parameters.
362       
363        Takes URL parameters and additionally param=val or dict params.
364        Deletes key if value is None."""
365        # copy existing request params
366        newParams=self.REQUEST.form.copy()
367        # change single param
368        if param is not None:
369            if val is None:
370                if newParams.has_key(param):
371                    del newParams[param]
372            else:
373                newParams[param] = str(val)
374               
375        # change more params
376        if params is not None:
377            for (k, v) in params.items():
378                if v is None:
379                    # val=None removes param
380                    if newParams.has_key(k):
381                        del newParams[k]
382                       
383                else:
384                    newParams[k] = v
385
386        if duplicates:
387            # eliminate lists (coming from duplicate keys)
388            for (k,v) in newParams.items():
389                if isinstance(v, list):
390                    if duplicates == 'comma':
391                        # make comma-separated list of non-empty entries
392                        newParams[k] = ','.join([t for t in v if t])
393                    elif duplicates == 'first':
394                        # take first non-empty entry
395                        newParams[k] = [t for t in v if t][0]
396       
397        return newParams
398   
399    def getLink(self, param=None, val=None, params=None, baseUrl=None, paramSep='&', duplicates='comma'):
400        """returns URL to documentviewer with parameter param set to val or from dict params"""
401        urlParams = self.getParams(param=param, val=val, params=params, duplicates=duplicates)
402        # quote values and assemble into query string (not escaping '/')
403        ps = paramSep.join(["%s=%s"%(k, urllib.quote_plus(utf8ify(v), '/')) for (k, v) in urlParams.items()])
404        if baseUrl is None:
405            baseUrl = self.getDocumentViewerURL()
406           
407        url = "%s?%s"%(baseUrl, ps)
408        return url
409
410    def getLinkAmp(self, param=None, val=None, params=None, baseUrl=None, duplicates='comma'):
411        """link to documentviewer with parameter param set to val"""
412        return self.getLink(param=param, val=val, params=params, baseUrl=baseUrl, paramSep='&', duplicates=duplicates)
413   
414
415    def setAvailableLayers(self, newLayerString=None):
416        """sets availableLayers to newLayerString or tries to autodetect available layers.
417        assumes layer templates have the form layer_{m}_{l} for layer l in mode m.
418        newLayerString is parsed as JSON."""
419        if newLayerString is not None:
420            try:
421                layers = json.loads(newLayerString)
422                if 'text' in layers and 'image' in layers:
423                    self.availableLayers = layers
424                    return
425            except:
426                pass
427
428            logging.error("invalid layers=%s! autodetecting..."%repr(newLayerString))
429           
430        # start with builtin layers
431        self.availableLayers = self.builtinLayers.copy()
432        # add layers from templates
433        for t in self.template:
434            if t.startswith('layer_'):
435                try:
436                    (x, m, l) = t.split('_', 3)
437                    if m not in self.availableLayers:
438                        # mode m doesn't exist -> new list
439                        self.availableLayers[m] = [l]
440                       
441                    else:
442                        # m exists -> append
443                        if l not in self.availableLayers[m]:
444                            self.availableLayers[m].append()
445                           
446                except:
447                    pass
448
449    def getAvailableLayersJson(self):
450        """returns available layers as JSON string."""
451        return json.dumps(self.availableLayers)
452   
453   
454    def getInfo_xml(self,url,mode):
455        """returns info about the document as XML"""
456        if not self.digilibBaseUrl:
457            self.digilibBaseUrl = self.findDigilibUrl() or "http://digilib.mpiwg-berlin.mpg.de/digitallibrary"
458       
459        docinfo = self.getDocinfo(mode=mode,url=url)
460        pt = getattr(self.template, 'info_xml')
461        return pt(docinfo=docinfo)
462
463    def getAuthenticatedUser(self, anon=None):
464        """returns the authenticated user object or None. (ignores Zopes anonymous user)"""
465        user = getSecurityManager().getUser()
466        if user is not None and user.getUserName() != "Anonymous User":
467            return user
468        else:
469            return anon
470
471    def isAccessible(self, docinfo):
472        """returns if access to the resource is granted"""
473        access = docinfo.get('accessType', None)
474        logging.debug("documentViewer (accessOK) access type %s"%access)
475        if access == 'free':
476            logging.debug("documentViewer (accessOK) access is free")
477            return True
478       
479        elif access is None or access in self.authgroups:
480            # only local access -- only logged in users
481            user = self.getAuthenticatedUser()
482            logging.debug("documentViewer (accessOK) user=%s ip=%s"%(user,self.REQUEST.getClientAddr()))
483            return (user is not None)
484       
485        logging.error("documentViewer (accessOK) unknown access type %s"%access)
486        return False
487
488    def getUserinfo(self):
489        """returns userinfo object"""
490        logging.debug("getUserinfo")
491        userinfo = {}
492        # look for cached userinfo in session
493        if self.REQUEST.SESSION.has_key('userinfo'):
494            userinfo = self.REQUEST.SESSION['userinfo']
495            # check if its still current?
496        else:
497            # store in session
498            self.REQUEST.SESSION['userinfo'] = userinfo
499           
500        return userinfo
501
502    def getDocinfo(self, mode, url, tocMode=None):
503        """returns docinfo depending on mode"""
504        logging.debug("getDocinfo: mode=%s, url=%s"%(mode,url))
505        # look for cached docinfo in session
506        if self.REQUEST.SESSION.has_key('docinfo'):
507            docinfo = self.REQUEST.SESSION['docinfo']
508            # check if its still current
509            if docinfo is not None and docinfo.get('mode', None) == mode and docinfo.get('url', None) == url:
510                logging.debug("getDocinfo: docinfo in session. keys=%s"%docinfo.keys())
511                return docinfo
512           
513        # new docinfo
514        docinfo = {'mode': mode, 'url': url}
515        # add self url
516        docinfo['viewerUrl'] = self.getDocumentViewerURL()
517        docinfo['digilibBaseUrl'] = self.digilibBaseUrl
518        docinfo['digilibScalerUrl'] = self.digilibScalerUrl
519        docinfo['digilibViewerUrl'] = self.digilibViewerUrl
520        # get index.meta DOM
521        docUrl = None
522        metaDom = None
523        if mode=="texttool": 
524            # url points to document dir or index.meta
525            metaDom = self.metadataService.getDomFromPathOrUrl(url)
526            if metaDom is None:
527                raise IOError("Unable to find index.meta for mode=texttool!")
528           
529            docUrl = url.replace('/index.meta', '')
530            if url.startswith('/mpiwg/online/'):
531                docUrl = url.replace('/mpiwg/online/', '', 1)
532
533        elif mode=="imagepath":
534            # url points to folder with images, index.meta optional
535            # asssume index.meta in parent dir
536            docUrl = getParentPath(url)
537            metaDom = self.metadataService.getDomFromPathOrUrl(docUrl)
538            docinfo['imagePath'] = url.replace('/mpiwg/online', '', 1)
539
540        elif mode=="filepath":
541            # url points to image file, index.meta optional
542            docinfo['imageURL'] = "%s?fn=%s"%(self.digilibScalerUrl, url)
543            docinfo['numPages'] = 1
544            # asssume index.meta is two path segments up
545            docUrl = getParentPath(url, 2)
546            metaDom = self.metadataService.getDomFromPathOrUrl(docUrl)
547
548        else:
549            logging.error("documentViewer (getdocinfo) unknown mode: %s!"%mode)
550            raise ValueError("Unknown mode %s! Has to be one of 'texttool','imagepath','filepath'."%(mode))
551       
552        docinfo['documentUrl'] = docUrl
553        # process index.meta contents
554        if metaDom is not None and metaDom.tag == 'resource':
555            # document directory name and path
556            resource = self.metadataService.getResourceData(dom=metaDom)
557            if resource:
558                docinfo = self.getDocinfoFromResource(docinfo, resource)
559
560            # texttool info
561            texttool = self.metadataService.getTexttoolData(dom=metaDom, recursive=1, all=True)
562            if texttool:
563                docinfo = self.getDocinfoFromTexttool(docinfo, texttool)
564                # document info from full text server
565                if docinfo.get('textURLPath', None):
566                    docinfo = self.getTextInfo(mode=None, docinfo=docinfo)
567                    # include list of pages TODO: do we need this always?
568                    docinfo = self.getTextInfo(mode='pages', docinfo=docinfo)
569           
570            # bib info
571            bib = self.metadataService.getBibData(dom=metaDom)
572            if bib:
573                # save extended version as 'bibx' TODO: ugly
574                bibx = self.metadataService.getBibData(dom=metaDom, all=True, recursive=1)
575                if len(bibx) == 1:
576                    # unwrap list if possible
577                    bibx = bibx[0]
578                   
579                docinfo['bibx'] = bibx
580                docinfo = self.getDocinfoFromBib(docinfo, bib, bibx)
581            else:
582                # no bib - try info.xml
583                docinfo = self.getDocinfoFromPresentationInfoXml(docinfo)
584               
585            # auth info
586            access = self.metadataService.getAccessData(dom=metaDom)
587            if access:
588                docinfo = self.getDocinfoFromAccess(docinfo, access)
589
590            # attribution info
591            attribution = self.metadataService.getAttributionData(dom=metaDom)
592            if attribution:
593                logging.debug("getDocinfo: attribution=%s"%repr(attribution))
594                docinfo['attribution'] = attribution
595
596            # copyright info
597            copyright = self.metadataService.getCopyrightData(dom=metaDom)
598            if copyright:
599                logging.debug("getDocinfo: copyright=%s"%repr(copyright))
600                docinfo['copyright'] = copyright
601
602            # DRI (permanent ID)
603            dri = self.metadataService.getDRI(dom=metaDom, type='mpiwg')
604            if dri:
605                docinfo['DRI'] = dri
606
607            # (presentation) context
608            ctx = self.metadataService.getContextData(dom=metaDom, all=True)
609            if ctx:
610                logging.debug("getcontext: ctx=%s"%repr(ctx))
611                docinfo['presentationContext'] = ctx
612
613        # image path
614        if mode != 'texttool':
615            # override image path from texttool with url parameter TODO: how about mode=auto?
616            docinfo['imagePath'] = url.replace('/mpiwg/online', '', 1)
617
618        # check numPages
619        if docinfo.get('numPages', 0) == 0:
620            # number of images from digilib
621            if docinfo.get('imagePath', None):
622                imgpath = docinfo['imagePath'].replace('/mpiwg/online', '', 1)
623                logging.debug("imgpath=%s"%imgpath)
624                docinfo['imageURL'] = "%s?fn=%s"%(self.digilibScalerUrl, imgpath)
625                docinfo = self.getDocinfoFromDigilib(docinfo, imgpath)
626            else:
627                # imagePath still missing? try "./pageimg"
628                imgPath = os.path.join(docUrl, 'pageimg')
629                docinfo = self.getDocinfoFromDigilib(docinfo, imgPath)
630                if docinfo.get('numPages', 0) > 0:
631                    # there are pages
632                    docinfo['imagePath'] = imgPath
633                    docinfo['imageURL'] = "%s?fn=%s"%(self.digilibScalerUrl, docinfo['imagePath'])
634
635        # check numPages
636        if docinfo.get('numPages', 0) == 0:
637            if docinfo.get('numTextPages', 0) > 0:
638                # replace with numTextPages (text-only?)
639                docinfo['numPages'] = docinfo['numTextPages']
640
641        # normalize path
642        if 'imagePath' in docinfo and not docinfo['imagePath'].startswith('/'):
643            docinfo['imagePath'] = '/' + docinfo['imagePath']
644
645        logging.debug("documentViewer (getdocinfo) docinfo: keys=%s"%docinfo.keys())
646        # store in session
647        self.REQUEST.SESSION['docinfo'] = docinfo
648        return docinfo
649
650
651    def getDocinfoFromResource(self, docinfo, resource):
652        """reads contents of resource element into docinfo"""
653        docName = resource.get('name', None)
654        docinfo['documentName'] = docName
655        docPath = resource.get('archive-path', None)
656        if docPath:
657            # clean up document path
658            if docPath[0] != '/':
659                docPath = '/' + docPath
660               
661            if docName and (not docPath.endswith(docName)):
662                docPath += "/" + docName
663           
664        else:
665            # use docUrl as docPath
666            docUrl = docinfo['documentURL']
667            if not docUrl.startswith('http:'):
668                docPath = docUrl
669        if docPath:
670            # fix URLs starting with /mpiwg/online
671            docPath = docPath.replace('/mpiwg/online', '', 1)
672
673        docinfo['documentPath'] = docPath
674        return docinfo
675
676    def getDocinfoFromTexttool(self, docinfo, texttool):
677        """reads contents of texttool element into docinfo"""
678        logging.debug("texttool=%s"%repr(texttool))
679        # unpack list if necessary
680        if isinstance(texttool, list):
681            texttool = texttool[0]
682                                   
683        # image dir
684        imageDir = getMDText(texttool.get('image', None))
685        docPath = getMDText(docinfo.get('documentPath', None))
686        if imageDir and docPath:
687            imageDir = os.path.join(docPath, imageDir)
688            imageDir = imageDir.replace('/mpiwg/online', '', 1)
689            docinfo['imagePath'] = imageDir
690       
691        # old style text URL
692        textUrl = getMDText(texttool.get('text', None))
693        if textUrl and docPath:
694            if urlparse.urlparse(textUrl)[0] == "": #keine url
695                textUrl = os.path.join(docPath, textUrl) 
696           
697            docinfo['textURL'] = textUrl
698   
699        # new style text-url-path (can be more than one with "repository" attribute)
700        textUrlNode = texttool.get('text-url-path', None)
701        if not isinstance(textUrlNode, list):
702            textUrlNode = [textUrlNode]
703
704        for tun in textUrlNode:
705            textUrl = getMDText(tun)
706            if textUrl:
707                textUrlAtts = tun.get('@attr')
708                if (textUrlAtts and 'repository' in textUrlAtts):
709                    textRepo = textUrlAtts['repository']
710                    # use matching repository
711                    if self.getRepositoryType() == textRepo:
712                        docinfo['textURLPath'] = textUrl
713                        docinfo['textURLRepository'] = textRepo
714                        break
715               
716                else:
717                    # no repo attribute - use always
718                    docinfo['textURLPath'] = textUrl
719           
720        # page flow
721        docinfo['pageFlow'] = getMDText(texttool.get('page-flow', 'ltr'))
722           
723        # odd pages are left
724        docinfo['oddPage'] = getMDText(texttool.get('odd-scan-position', 'left'))
725           
726        # number of title page (default 1)
727        docinfo['titlePage'] = getMDText(texttool.get('title-scan-no', 1))
728           
729        # old presentation stuff
730        presentation = getMDText(texttool.get('presentation', None))
731        if presentation and docPath:
732            if presentation.startswith('http:'):
733                docinfo['presentationUrl'] = presentation
734            else:
735                docinfo['presentationUrl'] = os.path.join(docPath, presentation)
736       
737        return docinfo
738
739    def getDocinfoFromBib(self, docinfo, bib, bibx=None):
740        """reads contents of bib element into docinfo"""
741        logging.debug("getDocinfoFromBib bib=%s"%repr(bib))
742        # put all raw bib fields in dict "bib"
743        docinfo['bib'] = bib
744        bibtype = bib.get('@type', None)
745        docinfo['bibType'] = bibtype
746        # also store DC metadata for convenience
747        dc = self.metadataService.getDCMappedData(bib)
748        docinfo['creator'] = dc.get('creator','')
749        docinfo['title'] = dc.get('title','')
750        docinfo['date'] = dc.get('date','')
751        return docinfo
752           
753    def getDocinfoFromAccess(self, docinfo, acc):
754        """reads contents of access element into docinfo"""
755        #TODO: also read resource type
756        logging.debug("getDocinfoFromAccess acc=%s"%repr(acc))
757        try:
758            acctype = acc['@attr']['type']
759            if acctype:
760                access=acctype
761                if access in ['group', 'institution']:
762                    access = acc['name'].lower()
763               
764                docinfo['accessType'] = access
765
766        except:
767            pass
768       
769        return docinfo
770
771    def getDocinfoFromDigilib(self, docinfo, path):
772        infoUrl=self.digilibBaseUrl+"/dirInfo-xml.jsp?fn="+path
773        # fetch data
774        txt = getHttpData(infoUrl)
775        if not txt:
776            logging.error("Unable to get dir-info from %s"%(infoUrl))
777            return docinfo
778
779        dom = ET.fromstring(txt)
780        dir = dom
781        # save size
782        size = dir.findtext('size')
783        logging.debug("getDocinfoFromDigilib: size=%s"%size)
784        if size:
785            docinfo['numPages'] = int(size)
786        else:
787            docinfo['numPages'] = 0
788            return docinfo
789           
790        # save list of image names and numbers
791        imgNames = {}
792        imgIndexes = {}
793        for f in dir:
794            fn = f.findtext('name')
795            pn = getInt(f.findtext('index'))
796            imgNames[fn] = pn
797            imgIndexes[pn] = fn
798           
799        docinfo['imgFileNames'] = imgNames
800        docinfo['imgFileIndexes'] = imgIndexes
801        return docinfo
802           
803           
804    def getDocinfoFromPresentationInfoXml(self,docinfo):
805        """gets DC-like bibliographical information from the presentation entry in texttools"""
806        url = docinfo.get('presentationUrl', None)
807        if not url:
808            logging.error("getDocinfoFromPresentation: no URL!")
809            return docinfo
810       
811        dom = None
812        metaUrl = None
813        if url.startswith("http://"):
814            # real URL
815            metaUrl = url
816        else:
817            # online path
818            server=self.digilibBaseUrl+"/servlet/Texter?fn="
819            metaUrl=server+url
820       
821        txt=getHttpData(metaUrl)
822        if txt is None:
823            logging.error("Unable to read info.xml from %s"%(url))
824            return docinfo
825           
826        dom = ET.fromstring(txt)
827        docinfo['creator']=getText(dom.find(".//author"))
828        docinfo['title']=getText(dom.find(".//title"))
829        docinfo['date']=getText(dom.find(".//date"))
830        return docinfo
831   
832
833    def getPageinfo(self, pn=None, pf=None, start=None, rows=None, cols=None, docinfo=None, userinfo=None, viewMode=None, viewLayer=None, tocMode=None):
834        """returns pageinfo with the given parameters"""
835        logging.debug("getPageInfo(pn=%s, pf=%s, start=%s, rows=%s, cols=%s, viewMode=%s, viewLayer=%s, tocMode=%s)"%(pn,pf,start,rows,cols,viewMode,viewLayer,tocMode))
836        pageinfo = {}
837        pageinfo['viewMode'] = viewMode
838        # split viewLayer if necessary
839        if isinstance(viewLayer,basestring):
840            viewLayer = viewLayer.split(',')
841           
842        if isinstance(viewLayer, list):
843            logging.debug("getPageinfo: viewLayer is list:%s"%viewLayer)
844            # save (unique) list in viewLayers
845            seen = set()
846            viewLayers = [l for l in viewLayer if l and l not in seen and not seen.add(l)]
847            pageinfo['viewLayers'] = viewLayers
848            # stringify viewLayer
849            viewLayer = ','.join(viewLayers)
850        else:
851            #create list
852            pageinfo['viewLayers'] = [viewLayer]
853                       
854        pageinfo['viewLayer'] = viewLayer
855        pageinfo['tocMode'] = tocMode
856
857        # pf takes precedence over pn
858        if pf:
859            pageinfo['pf'] = pf
860            pn = getPnForPf(docinfo, pf)
861            # replace pf in request params (used for creating new URLs)
862            self.REQUEST.form.pop('pf', None)
863            self.REQUEST.form['pn'] = pn
864        else:
865            pn = getInt(pn, 1)
866            pf = getPfForPn(docinfo, pn)
867            pageinfo['pf'] = pf
868           
869        pageinfo['pn'] = pn
870        rows = int(rows or self.thumbrows)
871        pageinfo['rows'] = rows
872        cols = int(cols or self.thumbcols)
873        pageinfo['cols'] = cols
874        grpsize = cols * rows
875        pageinfo['groupsize'] = grpsize
876        # is start is empty use one around pn
877        start = getInt(start, default=(math.ceil(float(pn)/float(grpsize))*grpsize-(grpsize-1)))
878        # int(current / grpsize) * grpsize +1))
879        pageinfo['start'] = start
880        # get number of pages
881        np = int(docinfo.get('numPages', 0))
882        if np == 0:
883            # try numTextPages
884            np = docinfo.get('numTextPages', 0)
885            if np != 0:
886                docinfo['numPages'] = np
887
888        # cache table of contents
889        pageinfo['tocPageSize'] = getInt(self.REQUEST.get('tocPageSize', 30))
890        pageinfo['numgroups'] = int(np / grpsize)
891        if np % grpsize > 0:
892            pageinfo['numgroups'] += 1
893
894        pageFlowLtr = docinfo.get('pageFlow', 'ltr') != 'rtl'
895        oddScanLeft = docinfo.get('oddPage', 'left') != 'right'
896        # add zeroth page for two columns
897        pageZero = (cols == 2 and (pageFlowLtr != oddScanLeft))
898        pageinfo['pageZero'] = pageZero
899        pageinfo['pageBatch'] = self.getPageBatch(start=start, rows=rows, cols=cols, pageFlowLtr=pageFlowLtr, pageZero=pageZero, minIdx=1, maxIdx=np)
900        # more page parameters
901        pageinfo['characterNormalization'] = self.REQUEST.get('characterNormalization','reg')
902        if docinfo.get('pageNumbers'):
903            # get original page numbers
904            pageNumber = docinfo['pageNumbers'].get(pn, None)
905            if pageNumber is not None:
906                pageinfo['pageNumberOrig'] = pageNumber['no']
907                pageinfo['pageNumberOrigNorm'] = pageNumber['non']
908       
909        # cache search results
910        query = self.REQUEST.get('query',None)
911        pageinfo['query'] = query
912        if query and viewMode == 'text':
913            pageinfo['resultPageSize'] = getInt(self.REQUEST.get('resultPageSize', 10))
914            queryType = self.REQUEST.get('queryType', 'fulltextMorph')
915            pageinfo['queryType'] = queryType
916            pageinfo['resultStart'] = getInt(self.REQUEST.get('resultStart', '1'))
917            self.getSearchResults(mode=queryType, query=query, pageinfo=pageinfo, docinfo=docinfo)
918           
919            # highlighting
920            highlightQuery = self.REQUEST.get('highlightQuery', None)
921            if highlightQuery:
922                pageinfo['highlightQuery'] = highlightQuery
923                pageinfo['highlightElement'] = self.REQUEST.get('highlightElement', '')
924                pageinfo['highlightElementPos'] = self.REQUEST.get('highlightElementPos', '')
925           
926        return pageinfo
927
928
929    def getPageBatch(self, start=1, rows=10, cols=2, pageFlowLtr=True, pageZero=False, minIdx=1, maxIdx=0):
930        """returns dict with array of page information for one screenfull of thumbnails"""
931        batch = {}
932        grpsize = rows * cols
933        if maxIdx == 0:
934            maxIdx = start + grpsize
935
936        nb = int(math.ceil(maxIdx / float(grpsize)))
937        # list of all batch start and end points
938        batches = []
939        if pageZero:
940            ofs = 0
941        else:
942            ofs = 1
943           
944        for i in range(nb):
945            s = i * grpsize + ofs
946            e = min((i + 1) * grpsize + ofs - 1, maxIdx)
947            batches.append({'start':s, 'end':e})
948           
949        batch['batches'] = batches
950
951        pages = []
952        if pageZero and start == 1:
953            # correct beginning
954            idx = 0
955        else:
956            idx = start
957           
958        for r in range(rows):
959            row = []
960            for c in range(cols):
961                if idx < minIdx or idx > maxIdx:
962                    page = {'idx':None}
963                else:
964                    page = {'idx':idx}
965                   
966                idx += 1
967                if pageFlowLtr:
968                    row.append(page)
969                else:
970                    row.insert(0, page) 
971               
972            pages.append(row)
973           
974        if start > 1:
975            batch['prevStart'] = max(start - grpsize, 1)
976        else:
977            batch['prevStart'] = None
978           
979        if start + grpsize <= maxIdx:
980            batch['nextStart'] = start + grpsize
981        else:
982            batch['nextStart'] = None
983
984        batch['pages'] = pages
985        batch['first'] = minIdx
986        batch['last'] = maxIdx
987        return batch
988       
989    def getBatch(self, start=1, size=10, end=0, data=None, fullData=True):
990        """returns dict with information for one screenfull of data."""
991        batch = {}
992        if end == 0:
993            end = start + size                   
994           
995        nb = int(math.ceil(end / float(size)))
996        # list of all batch start and end points
997        batches = []
998        for i in range(nb):
999            s = i * size + 1
1000            e = min((i + 1) * size, end)
1001            batches.append({'start':s, 'end':e})
1002           
1003        batch['batches'] = batches
1004        # list of elements in this batch
1005        this = []
1006        j = 0
1007        for i in range(start, min(start+size, end+1)):
1008            if data:
1009                if fullData:
1010                    d = data.get(i, None)
1011                else:
1012                    d = data.get(j, None)
1013                    j += 1
1014           
1015            else:
1016                d = i+1
1017               
1018            this.append(d)
1019           
1020        batch['this'] = this
1021        if start > 1:
1022            batch['prevStart'] = max(start - size, 1)
1023        else:
1024            batch['prevStart'] = None
1025           
1026        if start + size < end:
1027            batch['nextStart'] = start + size
1028        else:
1029            batch['nextStart'] = None
1030       
1031        batch['first'] = start
1032        batch['last'] = end
1033        return batch
1034       
1035
1036    def getAnnotatorGroupsForUser(self, user, annotationServerUrl="http://tuxserve03.mpiwg-berlin.mpg.de/AnnotationManager"):
1037        """returns list of groups {name:*, id:*} on the annotation server for the user"""
1038        groups = []
1039        groupsUrl = "%s/annotator/groups?user=%s"%(annotationServerUrl,user)
1040        data = getHttpData(url=groupsUrl, noExceptions=True)
1041        if data:
1042            res = json.loads(data)
1043            rows = res.get('rows', None)
1044            if rows is None:
1045                return groups
1046            for r in rows:
1047                groups.append({'id': r.get('id', None), 'name': r.get('name', None), 'uri': r.get('uri', None)})
1048               
1049        return groups
1050   
1051
1052    security.declareProtected('View management screens','changeDocumentViewerForm')   
1053    changeDocumentViewerForm = PageTemplateFile('zpt/changeDocumentViewer', globals())
1054   
1055    def changeDocumentViewer(self,title="",digilibBaseUrl=None,thumbrows=2,thumbcols=5,authgroups='mpiwg',availableLayers=None,RESPONSE=None):
1056        """init document viewer"""
1057        self.title=title
1058        self.digilibBaseUrl = digilibBaseUrl
1059        self.digilibScalerUrl = digilibBaseUrl + '/servlet/Scaler'
1060        self.digilibViewerUrl = digilibBaseUrl + '/jquery/digilib.html'
1061        self.thumbrows = thumbrows
1062        self.thumbcols = thumbcols
1063        self.authgroups = [s.strip().lower() for s in authgroups.split(',')]
1064        try:
1065            # assume MetaDataFolder instance is called metadata
1066            self.metadataService = getattr(self, 'metadata')
1067        except Exception, e:
1068            logging.error("Unable to find MetaDataFolder 'metadata': "+str(e))
1069           
1070        self.setAvailableLayers(availableLayers)
1071
1072        if RESPONSE is not None:
1073            RESPONSE.redirect('manage_main')
1074       
1075def manage_AddDocumentViewerForm(self):
1076    """add the viewer form"""
1077    pt=PageTemplateFile('zpt/addDocumentViewer', globals()).__of__(self)
1078    return pt()
1079 
1080def manage_AddDocumentViewer(self,id,imageScalerUrl="",textServerName="",title="",RESPONSE=None):
1081    """add the viewer"""
1082    newObj=documentViewer(id,imageScalerUrl=imageScalerUrl,title=title,textServerName=textServerName)
1083    self._setObject(id,newObj)
1084   
1085    if RESPONSE is not None:
1086        RESPONSE.redirect('manage_main')
Note: See TracBrowser for help on using the repository browser.