source: documentViewer/documentViewer.py @ 615:d6eca930a534

Last change on this file since 615:d6eca930a534 was 615:d6eca930a534, checked in by Dirk Wintergruen <dwinter@…>, 10 years ago

hocr viewer eingebaut

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