source: documentViewer/documentViewer.py @ 629:e36bf3226fde

Last change on this file since 629:e36bf3226fde was 629:e36bf3226fde, checked in by dwinter, 9 years ago

text image viewer eingecheckt

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