source: documentViewer/MpdlXmlTextServer.py

Last change on this file was 613:c57d80a649ea, checked in by casties, 11 years ago

CLOSED - # 281: List of thumbnails verschluckt Seite, wenn odd-scan-position gesetzt ist
https://it-dev.mpiwg-berlin.mpg.de/tracs/mpdl-project-software/ticket/281

File size: 22.8 KB
Line 
1from OFS.SimpleItem import SimpleItem
2from Products.PageTemplates.PageTemplateFile import PageTemplateFile
3
4import xml.etree.ElementTree as ET
5
6import re
7import logging
8import urllib
9import urlparse
10import base64
11
12from SrvTxtUtils import getInt, getText, getHttpData, serialize
13
14
15class MpdlXmlTextServer(SimpleItem):
16    """TextServer implementation for MPDL-XML eXist server"""
17    meta_type="MPDL-XML TextServer"
18
19    manage_options=(
20        {'label':'Config','action':'manage_changeMpdlXmlTextServerForm'},
21       )+SimpleItem.manage_options
22   
23    manage_changeMpdlXmlTextServerForm = PageTemplateFile("zpt/manage_changeMpdlXmlTextServer", globals())
24       
25    def __init__(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/", serverName=None, timeout=40, repositoryType='production'):
26        """constructor"""
27        self.id=id
28        self.title=title
29        self.timeout = timeout
30        self.repositoryType = repositoryType
31        if serverName is None:
32            self.serverUrl = serverUrl
33        else:
34            self.serverUrl = "http://%s/mpdl/interface/"%serverName
35       
36    def getHttpData(self, url, data=None):
37        """returns result from url+data HTTP request"""
38        return getHttpData(url,data,timeout=self.timeout)
39   
40    def getServerData(self, method, data=None):
41        """returns result from text server for method+data"""
42        url = self.serverUrl+method
43        return getHttpData(url,data,timeout=self.timeout)
44
45
46    def getRepositoryType(self):
47        """returns the repository type, e.g. 'production'"""
48        return getattr(self, 'repositoryType', None)
49
50    def getTextDownloadUrl(self, type='xml', docinfo=None):
51        """returns a URL to download the current text"""
52        docpath = docinfo.get('textURLPath', None)
53        if not docpath:
54            return None
55
56        docpath = docpath.replace('.xml','.'+type)
57        url = '%sgetDoc?doc=%s'%(self.serverUrl.replace('interface/',''), docpath)
58        return url
59
60
61    def getPlacesOnPage(self, docinfo=None, pn=None):
62        """Returns list of GIS places of page pn"""
63        docpath = docinfo.get('textURLPath',None)
64        if not docpath:
65            return None
66
67        places=[]
68        text=self.getServerData("xpath.xql", "document=%s&xpath=//place&pn=%s"%(docpath,pn))
69        dom = ET.fromstring(text)
70        result = dom.findall(".//resultPage/place")
71        for l in result:
72            id = l.get("id")
73            name = l.text
74            place = {'id': id, 'name': name}
75            places.append(place)
76
77        return places
78   
79         
80    def getTextInfo(self, mode='', docinfo=None):
81        """reads document info, including page concordance, from text server"""
82        logging.debug("getTextInfo mode=%s"%mode)
83        if mode not in ['toc', 'figures', '']:
84            mode = ''
85        # check cached info
86        if mode:
87            # cached toc-request?
88            if 'full_%s'%mode in docinfo:
89                return docinfo
90           
91        else:
92            # no toc-request
93            if 'numTextPages' in docinfo:
94                return docinfo
95               
96        docpath = docinfo.get('textURLPath', None)
97        if docpath is None:
98            logging.error("getTextInfo: no textURLPath!")
99            return docinfo
100             
101        try:
102            # we need to set a result set size
103            pagesize = 10000
104            pn = 1
105            # fetch docinfo           
106            pagexml = self.getServerData("doc-info.xql","document=%s&info=%s&pageSize=%s&pn=%s"%(docpath,mode,pagesize,pn))
107            dom = ET.fromstring(pagexml)
108            # all info in tag <document>
109            doc = dom.find("document")
110        except Exception, e:
111            logging.error("getTextInfo: Error reading doc info: %s"%e)
112            return docinfo
113           
114        if doc is None:
115            logging.error("getTextInfo: unable to find document-tag!")
116        else:
117            # go through all child elements
118            for tag in doc:
119                name = tag.tag
120                # numTextPages
121                if name == 'countPages':
122                    np = getInt(tag.text)                   
123                    if np > 0:
124                        docinfo['numTextPages'] = np
125                   
126                # numFigureEntries
127                elif name == 'countFigureEntries':
128                    docinfo['numFigureEntries'] = getInt(tag.text)
129                   
130                # numTocEntries
131                elif name == 'countTocEntries':
132                    # WTF: s1 = int(s)/30+1
133                    docinfo['numTocEntries'] = getInt(tag.text)
134                   
135                # numPlaces
136                elif name == 'countPlaces':
137                    docinfo['numPlaces'] = getInt(tag.text)
138                   
139                # pageNumbers
140                elif name == 'pageNumbers':
141                    # contains tags with page numbers
142                    # <pn><n>4</n><no>4</no><non/></pn>
143                    # n=scan number, no=original page no, non=normalized original page no
144                    # pageNumbers is a dict indexed by scan number
145                    pages = {}
146                    for pn in tag:
147                        page = {}
148                        n = 0
149                        for p in pn:
150                            if p.tag == 'n':
151                                n = getInt(p.text)
152                                page['pn'] = n
153                            elif p.tag == 'no':
154                                page['no'] = p.text
155                            elif p.tag == 'non':
156                                page['non'] = p.text
157                               
158                        if n > 0:
159                            pages[n] = page
160                       
161                    docinfo['pageNumbers'] = pages
162                    #logging.debug("got pageNumbers=%s"%repr(pages))
163                               
164                # toc
165                elif name == 'toc':
166                    # contains tags with table of contents/figures
167                    # <toc-entry><page>13</page><level>3</level><content>Chapter I</content><level-string>1.</level-string><real-level>1</real-level></toc-entry>
168                    tocs = []
169                    for te in tag:
170                        toc = {}
171                        for t in te:
172                            if t.tag == 'page':
173                                toc['pn'] = getInt(t.text)
174                            elif t.tag == 'level':
175                                toc['level'] = t.text
176                            elif t.tag == 'content':
177                                toc['content'] = t.text
178                            elif t.tag == 'level-string':
179                                toc['level-string'] = t.text
180                            elif t.tag == 'real-level':
181                                toc['real-level'] = t.text
182                               
183                        tocs.append(toc)
184                   
185                    # save as full_toc/full_figures
186                    docinfo['full_%s'%mode] = tocs
187
188        return docinfo
189       
190         
191    def processPageInfo(self, dom, docinfo, pageinfo):
192        """processes page info divs from dom and stores in docinfo and pageinfo"""
193        # assume first second level div is pageMeta
194        alldivs = dom.find("div")
195       
196        if alldivs is None or alldivs.get('class', '') != 'pageMeta':
197            logging.error("processPageInfo: pageMeta div not found!")
198            return
199       
200        for div in alldivs:
201            dc = div.get('class')
202           
203            # pageNumberOrig 
204            if dc == 'pageNumberOrig':
205                pageinfo['pageNumberOrig'] = div.text
206               
207            # pageNumberOrigNorm
208            elif dc == 'pageNumberOrigNorm':
209                pageinfo['pageNumberOrigNorm'] = div.text
210               
211            # pageHeaderTitle
212            elif dc == 'pageHeaderTitle':
213                pageinfo['pageHeaderTitle'] = div.text
214                       
215        #logging.debug("processPageInfo: pageinfo=%s"%repr(pageinfo))
216        return
217         
218           
219    def getTextPage(self, mode="text", pn=1, docinfo=None, pageinfo=None):
220        """returns single page from fulltext"""
221       
222        logging.debug("getTextPage mode=%s, pn=%s"%(mode,pn))
223        # check for cached text -- but ideally this shouldn't be called twice
224        if pageinfo.has_key('textPage'):
225            logging.debug("getTextPage: using cached text")
226            return pageinfo['textPage']
227       
228        docpath = docinfo.get('textURLPath', None)
229        if not docpath:
230            return None
231       
232        # stuff for constructing full urls
233        selfurl = docinfo['viewerUrl']
234        textParams = {'document': docpath,
235                      'pn': pn}
236        if 'characterNormalization' in pageinfo:
237            textParams['characterNormalization'] = pageinfo['characterNormalization']
238       
239        if not mode:
240            # default is dict
241            mode = 'text'
242
243        modes = mode.split(',')
244        # check for multiple layers
245        if len(modes) > 1:
246            logging.debug("getTextPage: more than one mode=%s"%mode)
247                       
248        # search mode
249        if 'search' in modes:
250            # add highlighting
251            highlightQuery = pageinfo.get('highlightQuery', None)
252            if highlightQuery:
253                textParams['highlightQuery'] = highlightQuery
254                textParams['highlightElement'] = pageinfo.get('highlightElement', '')
255                textParams['highlightElementPos'] = pageinfo.get('highlightElementPos', '')
256               
257            # ignore mode in the following
258            modes.remove('search')
259                           
260        # pundit mode
261        punditMode = False
262        if 'pundit' in modes:
263            punditMode = True
264            # ignore mode in the following
265            modes.remove('pundit')
266                           
267        # other modes don't combine
268        if 'dict' in modes:
269            # dict is called textPollux in the backend
270            textmode = 'textPollux'
271        elif 'xml' in modes:
272            # xml mode
273            textmode = 'xml'
274            textParams['characterNormalization'] = 'orig'
275        elif 'gis' in modes:
276            textmode = 'gis'
277        else:
278            # text is default mode
279            textmode = 'text'
280       
281        textParams['mode'] = textmode
282       
283        try:
284            # fetch the page
285            pagexml = self.getServerData("page-fragment.xql",urllib.urlencode(textParams))
286            dom = ET.fromstring(pagexml)
287        except Exception, e:
288            logging.error("getTextPage: Error reading page: %s"%e)
289            return None
290           
291        # extract additional info
292        self.processPageInfo(dom, docinfo, pageinfo)
293        # page content is in <div class="pageContent">
294        pagediv = None
295        # ElementTree 1.2 in Python 2.6 can't do div[@class='pageContent']
296        # so we look at the second level divs
297        alldivs = dom.findall('div')
298        for div in alldivs:
299            dc = div.get('class')
300            # page content div
301            if dc == 'pageContent':
302                pagediv = div
303                break
304       
305        # plain text mode
306        if textmode == "text":
307            # get full url assuming documentViewer is parent
308            selfurl = self.getLink()
309            if pagediv is not None:
310                if punditMode:
311                    pagediv = self.addPunditAttributes(pagediv, pageinfo, docinfo)
312                   
313                # fix empty div tags
314                divs = pagediv.findall('.//div')
315                for d in divs:
316                    if len(d) == 0 and not d.text:
317                        # make empty divs non-empty
318                        d.text = ' '
319                   
320                # check all a-tags
321                links = pagediv.findall('.//a')
322                for l in links:
323                    href = l.get('href')
324                    if href and href.startswith('#note-'):
325                        href = href.replace('#note-',"%s#note-"%selfurl)
326                        l.set('href', href)
327
328                return serialize(pagediv)
329           
330        # text-with-links mode
331        elif textmode == "textPollux":
332            if pagediv is not None:
333                viewerurl = docinfo['viewerUrl']
334                selfurl = self.getLink()
335                if punditMode:
336                    pagediv = self.addPunditAttributes(pagediv, pageinfo, docinfo)
337                   
338                # fix empty div tags
339                divs = pagediv.findall('.//div')
340                for d in divs:
341                    if len(d) == 0 and not d.text:
342                        # make empty divs non-empty
343                        d.text = ' '
344                   
345                # check all a-tags
346                links = pagediv.findall(".//a")
347                for l in links:
348                    href = l.get('href')
349                   
350                    if href:
351                        # is link with href
352                        linkurl = urlparse.urlparse(href)
353                        #logging.debug("getTextPage: linkurl=%s"%repr(linkurl))
354                        if linkurl.path.endswith('GetDictionaryEntries'):
355                            #TODO: replace wordInfo page
356                            # is dictionary link - change href (keeping parameters)
357                            #l.set('href', href.replace('http://mpdl-proto.mpiwg-berlin.mpg.de/mpdl/interface/lt/wordInfo.xql','%s/template/viewer_wordinfo'%viewerurl))
358                            # add target to open new page
359                            l.set('target', '_blank')
360                                                         
361                        if href.startswith('#note-'):
362                            # note link
363                            l.set('href', href.replace('#note-',"%s#note-"%selfurl))
364                             
365                return serialize(pagediv)
366           
367        # xml mode
368        elif textmode == "xml":
369            if pagediv is not None:
370                return serialize(pagediv)
371           
372        # pureXml mode WTF?
373        elif textmode == "pureXml":
374            if pagediv is not None:
375                return serialize(pagediv)
376                 
377        # gis mode
378        elif textmode == "gis":
379            if pagediv is not None:
380                # fix empty div tags
381                divs = pagediv.findall('.//div')
382                for d in divs:
383                    if len(d) == 0 and not d.text:
384                        # make empty divs non-empty
385                        d.text = ' '
386                   
387                # check all a-tags
388                links = pagediv.findall(".//a")
389                # add our URL as backlink
390                selfurl = self.getLink()
391                doc = base64.b64encode(selfurl)
392                for l in links:
393                    href = l.get('href')
394                    if href:
395                        if href.startswith('http://mappit.mpiwg-berlin.mpg.de'):
396                            l.set('href', re.sub(r'doc=[\w+/=]+', 'doc=%s'%doc, href))
397                            l.set('target', '_blank')
398                           
399                return serialize(pagediv)
400                   
401        return None
402   
403    def addPunditAttributes(self, pagediv, pageinfo, docinfo):
404        """add about attributes for pundit annotation tool"""
405        textid = docinfo.get('DRI', "fn=%s"%docinfo.get('documentPath', '???'))
406        pn = pageinfo.get('pn', '1')
407        #  TODO: use pn as well?
408        # check all div-tags
409        divs = pagediv.findall(".//div")
410        for d in divs:
411            id = d.get('id')
412            if id:
413                d.set('about', "http://echo.mpiwg-berlin.mpg.de/%s/pn=%s/#%s"%(textid,pn,id))
414                cls = d.get('class','')
415                cls += ' pundit-content'
416                d.set('class', cls.strip())
417
418        return pagediv
419
420    def getSearchResults(self, mode, query=None, pageinfo=None, docinfo=None):
421        """loads list of search results and stores XML in docinfo"""
422       
423        logging.debug("getSearchResults mode=%s query=%s"%(mode, query))
424        if mode == "none":
425            return docinfo
426             
427        cachedQuery = docinfo.get('cachedQuery', None)
428        if cachedQuery is not None:
429            # cached search result
430            if cachedQuery == '%s_%s'%(mode,query):
431                # same query
432                return docinfo
433           
434            else:
435                # different query
436                del docinfo['resultSize']
437                del docinfo['resultXML']
438       
439        # cache query
440        docinfo['cachedQuery'] = '%s_%s'%(mode,query)
441       
442        # fetch full results
443        docpath = docinfo['textURLPath']
444        params = {'document': docpath,
445                  'mode': 'text',
446                  'queryType': mode,
447                  'query': query,
448                  'queryResultPageSize': 1000,
449                  'queryResultPN': 1,
450                  'characterNormalization': pageinfo.get('characterNormalization', 'reg')}
451        pagexml = self.getServerData("doc-query.xql",urllib.urlencode(params))
452        #pagexml = self.getServerData("doc-query.xql","document=%s&mode=%s&queryType=%s&query=%s&queryResultPageSize=%s&queryResultPN=%s&s=%s&viewMode=%s&characterNormalization=%s&highlightElementPos=%s&highlightElement=%s&highlightQuery=%s"%(docpath, 'text', queryType, urllib.quote(query), pagesize, pn, s, viewMode,characterNormalization, highlightElementPos, highlightElement, urllib.quote(highlightQuery)))
453        dom = ET.fromstring(pagexml)
454        # page content is in <div class="queryResultPage">
455        pagediv = None
456        # ElementTree 1.2 in Python 2.6 can't do div[@class='queryResultPage']
457        alldivs = dom.findall("div")
458        for div in alldivs:
459            dc = div.get('class')
460            # page content div
461            if dc == 'queryResultPage':
462                pagediv = div
463               
464            elif dc == 'queryResultHits':
465                docinfo['resultSize'] = getInt(div.text)
466
467        if pagediv is not None:
468            # store XML in docinfo
469            docinfo['resultXML'] = ET.tostring(pagediv, 'UTF-8')
470
471        return docinfo
472   
473
474    def getResultsPage(self, mode="text", query=None, pn=None, start=None, size=None, pageinfo=None, docinfo=None):
475        """returns single page from the table of contents"""
476        logging.debug("getResultsPage mode=%s, pn=%s"%(mode,pn))
477        # get (cached) result
478        self.getSearchResults(mode=mode, query=query, pageinfo=pageinfo, docinfo=docinfo)
479           
480        resultxml = docinfo.get('resultXML', None)
481        if not resultxml:
482            logging.error("getResultPage: unable to find resultXML")
483            return "Error: no result!"
484       
485        if size is None:
486            size = pageinfo.get('resultPageSize', 10)
487           
488        if start is None:
489            start = (pn - 1) * size
490
491        fullresult = ET.fromstring(resultxml)
492       
493        if fullresult is not None:
494            # paginate
495            first = start-1
496            len = size
497            del fullresult[:first]
498            del fullresult[len:]
499            tocdivs = fullresult
500           
501            # check all a-tags
502            links = tocdivs.findall(".//a")
503            for l in links:
504                href = l.get('href')
505                if href:
506                    # assume all links go to pages
507                    linkUrl = urlparse.urlparse(href)
508                    linkParams = urlparse.parse_qs(linkUrl.query)
509                    # take some parameters
510                    params = {'pn': linkParams['pn'],
511                              'highlightQuery': linkParams.get('highlightQuery',''),
512                              'highlightElement': linkParams.get('highlightElement',''),
513                              'highlightElementPos': linkParams.get('highlightElementPos','')
514                              }
515                    url = self.getLink(params=params)
516                    l.set('href', url)
517                       
518            return serialize(tocdivs)
519       
520        return "ERROR: no results!"
521
522
523    def getToc(self, mode='text', docinfo=None):
524        """returns list of table of contents from docinfo"""
525        logging.debug("getToc mode=%s"%mode)
526        if mode == 'text':
527            queryType = 'toc'
528        else:
529            queryType = mode
530           
531        if not 'full_%s'%queryType in docinfo:
532            # get new toc
533            docinfo = self.getTextInfo(queryType, docinfo)
534           
535        return docinfo.get('full_%s'%queryType, [])
536
537    def getTocPage(self, mode='text', pn=None, start=None, size=None, pageinfo=None, docinfo=None):
538        """returns single page from the table of contents"""
539        logging.debug("getTocPage mode=%s, pn=%s start=%s size=%s"%(mode,repr(pn),repr(start),repr(size)))
540        fulltoc = self.getToc(mode=mode, docinfo=docinfo)
541        if len(fulltoc) < 1:
542            logging.error("getTocPage: unable to find toc!")
543            return "Error: no table of contents!"       
544       
545        if size is None:
546            size = pageinfo.get('tocPageSize', 30)
547           
548        if start is None:
549            start = (pn - 1) * size
550
551        # paginate
552        first = (start - 1)
553        last = first + size
554        tocs = fulltoc[first:last]
555        tp = '<div>'
556        for toc in tocs:
557            pageurl = self.getLink('pn', toc['pn'])
558            tp += '<div class="tocline">'
559            tp += '<div class="toc name">[%s %s]</div>'%(toc['level-string'], toc['content'])
560            tp += '<div class="toc float right page"><a href="%s">Page: %s</a></div>'%(pageurl, toc['pn'])
561            tp += '</div>\n'
562           
563        tp += '</div>\n'
564       
565        return tp
566           
567   
568    def manage_changeMpdlXmlTextServer(self,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,repositoryType=None,RESPONSE=None):
569        """change settings"""
570        self.title=title
571        self.timeout = timeout
572        self.serverUrl = serverUrl
573        if repositoryType:
574            self.repositoryType = repositoryType
575        if RESPONSE is not None:
576            RESPONSE.redirect('manage_main')
577       
578# management methods
579def manage_addMpdlXmlTextServerForm(self):
580    """Form for adding"""
581    pt = PageTemplateFile("zpt/manage_addMpdlXmlTextServer", globals()).__of__(self)
582    return pt()
583
584def manage_addMpdlXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,RESPONSE=None):
585#def manage_addMpdlXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de:30030/mpdl/interface/",timeout=40,RESPONSE=None):   
586    """add zogiimage"""
587    newObj = MpdlXmlTextServer(id,title,serverUrl,timeout)
588    self.Destination()._setObject(id, newObj)
589    if RESPONSE is not None:
590        RESPONSE.redirect('manage_main')
591       
592       
Note: See TracBrowser for help on using the repository browser.