source: documentViewer/MpdlXmlTextServer.py @ 559:eabfbad6aeb4

Last change on this file since 559:eabfbad6aeb4 was 559:eabfbad6aeb4, checked in by casties, 12 years ago

"extended" layer for index view and some bugfixes.

File size: 22.5 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
13
14def serialize(node):
15    """returns a string containing an XML snippet of node"""
16    s = ET.tostring(node, 'UTF-8')
17    # snip off XML declaration
18    if s.startswith('<?xml'):
19        i = s.find('?>')
20        return s[i+3:]
21
22    return s
23
24
25class MpdlXmlTextServer(SimpleItem):
26    """TextServer implementation for MPDL-XML eXist server"""
27    meta_type="MPDL-XML TextServer"
28
29    manage_options=(
30        {'label':'Config','action':'manage_changeMpdlXmlTextServerForm'},
31       )+SimpleItem.manage_options
32   
33    manage_changeMpdlXmlTextServerForm = PageTemplateFile("zpt/manage_changeMpdlXmlTextServer", globals())
34       
35    def __init__(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/", serverName=None, timeout=40):
36        """constructor"""
37        self.id=id
38        self.title=title
39        self.timeout = timeout
40        if serverName is None:
41            self.serverUrl = serverUrl
42        else:
43            self.serverUrl = "http://%s/mpdl/interface/"%serverName
44       
45    def getHttpData(self, url, data=None):
46        """returns result from url+data HTTP request"""
47        return getHttpData(url,data,timeout=self.timeout)
48   
49    def getServerData(self, method, data=None):
50        """returns result from text server for method+data"""
51        url = self.serverUrl+method
52        return getHttpData(url,data,timeout=self.timeout)
53
54
55    def getTextDownloadUrl(self, type='xml', docinfo=None):
56        """returns a URL to download the current text"""
57        docpath = docinfo.get('textURLPath', None)
58        if not docpath:
59            return None
60
61        docpath = docpath.replace('.xml','.'+type)
62        url = '%sgetDoc?doc=%s'%(self.serverUrl.replace('interface/',''), docpath)
63        return url
64
65
66    def getPlacesOnPage(self, docinfo=None, pn=None):
67        """Returns list of GIS places of page pn"""
68        docpath = docinfo.get('textURLPath',None)
69        if not docpath:
70            return None
71
72        places=[]
73        text=self.getServerData("xpath.xql", "document=%s&xpath=//place&pn=%s"%(docpath,pn))
74        dom = ET.fromstring(text)
75        result = dom.findall(".//resultPage/place")
76        for l in result:
77            id = l.get("id")
78            name = l.text
79            place = {'id': id, 'name': name}
80            places.append(place)
81
82        return places
83   
84         
85    def getTextInfo(self, mode='', docinfo=None):
86        """reads document info, including page concordance, from text server"""
87        logging.debug("getTextInfo mode=%s"%mode)
88        if mode not in ['toc', 'figures', '']:
89            mode = ''
90        # check cached info
91        if mode:
92            # cached toc-request?
93            if 'full_%s'%mode in docinfo:
94                return docinfo
95           
96        else:
97            # no toc-request
98            if 'numTextPages' in docinfo:
99                return docinfo
100               
101        docpath = docinfo.get('textURLPath', None)
102        if docpath is None:
103            logging.error("getTextInfo: no textURLPath!")
104            return docinfo
105               
106        # we need to set a result set size
107        pagesize = 10000
108        pn = 1
109        # fetch docinfo           
110        pagexml = self.getServerData("doc-info.xql","document=%s&info=%s&pageSize=%s&pn=%s"%(docpath,mode,pagesize,pn))
111        dom = ET.fromstring(pagexml)
112        # all info in tag <document>
113        doc = dom.find("document")
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        # just checking
233        if pageinfo['current'] != pn:
234            logging.warning("getTextPage: current!=pn!")
235           
236        # stuff for constructing full urls
237        selfurl = docinfo['viewerUrl']
238        textParams = {'document': docpath,
239                      'pn': pn}
240        if 'characterNormalization' in pageinfo:
241            textParams['characterNormalization'] = pageinfo['characterNormalization']
242       
243        if not mode:
244            # default is dict
245            mode = 'text'
246
247        modes = mode.split(',')
248        # check for multiple layers
249        if len(modes) > 1:
250            logging.debug("getTextPage: more than one mode=%s"%mode)
251                       
252        # search mode
253        if 'search' in modes:
254            # add highlighting
255            highlightQuery = pageinfo.get('highlightQuery', None)
256            if highlightQuery:
257                textParams['highlightQuery'] = highlightQuery
258                textParams['highlightElement'] = pageinfo.get('highlightElement', '')
259                textParams['highlightElementPos'] = pageinfo.get('highlightElementPos', '')
260               
261            # ignore mode in the following
262            modes.remove('search')
263                           
264        # pundit mode
265        punditMode = False
266        if 'pundit' in modes:
267            punditMode = True
268            # ignore mode in the following
269            modes.remove('pundit')
270                           
271        # other modes don't combine
272        if 'dict' in modes:
273            # dict is called textPollux in the backend
274            textmode = 'textPollux'
275        elif 'xml' in modes:
276            # xml mode
277            textmode = 'xml'
278            textParams['characterNormalization'] = 'orig'
279        elif 'gis' in modes:
280            textmode = 'gis'
281        else:
282            # text is default mode
283            textmode = 'text'
284       
285        textParams['mode'] = textmode
286       
287        # fetch the page
288        pagexml = self.getServerData("page-fragment.xql",urllib.urlencode(textParams))
289        dom = ET.fromstring(pagexml)
290        # extract additional info
291        self.processPageInfo(dom, docinfo, pageinfo)
292        # page content is in <div class="pageContent">
293        pagediv = None
294        # ElementTree 1.2 in Python 2.6 can't do div[@class='pageContent']
295        # so we look at the second level divs
296        alldivs = dom.findall('div')
297        for div in alldivs:
298            dc = div.get('class')
299            # page content div
300            if dc == 'pageContent':
301                pagediv = div
302                break
303       
304        # plain text mode
305        if textmode == "text":
306            # get full url assuming documentViewer is parent
307            selfurl = self.getLink()
308            if pagediv is not None:
309                if punditMode:
310                    pagediv = self.addPunditAttributes(pagediv, pageinfo, docinfo)
311                   
312                # fix empty div tags
313                divs = pagediv.findall('.//div')
314                for d in divs:
315                    if len(d) == 0 and not d.text:
316                        # make empty divs non-empty
317                        d.text = ' '
318                   
319                # check all a-tags
320                links = pagediv.findall('.//a')
321                for l in links:
322                    href = l.get('href')
323                    if href and href.startswith('#note-'):
324                        href = href.replace('#note-',"%s#note-"%selfurl)
325                        l.set('href', href)
326
327                return serialize(pagediv)
328           
329        # text-with-links mode
330        elif textmode == "textPollux":
331            if pagediv is not None:
332                viewerurl = docinfo['viewerUrl']
333                selfurl = self.getLink()
334                if punditMode:
335                    pagediv = self.addPunditAttributes(pagediv, pageinfo, docinfo)
336                   
337                # fix empty div tags
338                divs = pagediv.findall('.//div')
339                for d in divs:
340                    if len(d) == 0 and not d.text:
341                        # make empty divs non-empty
342                        d.text = ' '
343                   
344                # check all a-tags
345                links = pagediv.findall(".//a")
346                for l in links:
347                    href = l.get('href')
348                   
349                    if href:
350                        # is link with href
351                        linkurl = urlparse.urlparse(href)
352                        #logging.debug("getTextPage: linkurl=%s"%repr(linkurl))
353                        if linkurl.path.endswith('GetDictionaryEntries'):
354                            #TODO: replace wordInfo page
355                            # is dictionary link - change href (keeping parameters)
356                            #l.set('href', href.replace('http://mpdl-proto.mpiwg-berlin.mpg.de/mpdl/interface/lt/wordInfo.xql','%s/template/viewer_wordinfo'%viewerurl))
357                            # add target to open new page
358                            l.set('target', '_blank')
359                                                         
360                        if href.startswith('#note-'):
361                            # note link
362                            l.set('href', href.replace('#note-',"%s#note-"%selfurl))
363                             
364                return serialize(pagediv)
365           
366        # xml mode
367        elif textmode == "xml":
368            if pagediv is not None:
369                return serialize(pagediv)
370           
371        # pureXml mode WTF?
372        elif textmode == "pureXml":
373            if pagediv is not None:
374                return serialize(pagediv)
375                 
376        # gis mode
377        elif textmode == "gis":
378            if pagediv is not None:
379                # fix empty div tags
380                divs = pagediv.findall('.//div')
381                for d in divs:
382                    if len(d) == 0 and not d.text:
383                        # make empty divs non-empty
384                        d.text = ' '
385                   
386                # check all a-tags
387                links = pagediv.findall(".//a")
388                # add our URL as backlink
389                selfurl = self.getLink()
390                doc = base64.b64encode(selfurl)
391                for l in links:
392                    href = l.get('href')
393                    if href:
394                        if href.startswith('http://mappit.mpiwg-berlin.mpg.de'):
395                            l.set('href', re.sub(r'doc=[\w+/=]+', 'doc=%s'%doc, href))
396                            l.set('target', '_blank')
397                           
398                return serialize(pagediv)
399                   
400        return None
401   
402    def addPunditAttributes(self, pagediv, pageinfo, docinfo):
403        """add about attributes for pundit annotation tool"""
404        textid = docinfo.get('DRI', "fn=%s"%docinfo.get('documentPath', '???'))
405        pn = pageinfo.get('pn', '1')
406        #  TODO: use pn as well?
407        # check all div-tags
408        divs = pagediv.findall(".//div")
409        for d in divs:
410            id = d.get('id')
411            if id:
412                d.set('about', "http://echo.mpiwg-berlin.mpg.de/%s/pn=%s/#%s"%(textid,pn,id))
413                cls = d.get('class','')
414                cls += ' pundit-content'
415                d.set('class', cls.strip())
416
417        return pagediv
418
419    def getSearchResults(self, mode, query=None, pageinfo=None, docinfo=None):
420        """loads list of search results and stores XML in docinfo"""
421       
422        logging.debug("getSearchResults mode=%s query=%s"%(mode, query))
423        if mode == "none":
424            return docinfo
425             
426        cachedQuery = docinfo.get('cachedQuery', None)
427        if cachedQuery is not None:
428            # cached search result
429            if cachedQuery == '%s_%s'%(mode,query):
430                # same query
431                return docinfo
432           
433            else:
434                # different query
435                del docinfo['resultSize']
436                del docinfo['resultXML']
437       
438        # cache query
439        docinfo['cachedQuery'] = '%s_%s'%(mode,query)
440       
441        # fetch full results
442        docpath = docinfo['textURLPath']
443        params = {'document': docpath,
444                  'mode': 'text',
445                  'queryType': mode,
446                  'query': query,
447                  'queryResultPageSize': 1000,
448                  'queryResultPN': 1,
449                  'characterNormalization': pageinfo.get('characterNormalization', 'reg')}
450        pagexml = self.getServerData("doc-query.xql",urllib.urlencode(params))
451        #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)))
452        dom = ET.fromstring(pagexml)
453        # page content is in <div class="queryResultPage">
454        pagediv = None
455        # ElementTree 1.2 in Python 2.6 can't do div[@class='queryResultPage']
456        alldivs = dom.findall("div")
457        for div in alldivs:
458            dc = div.get('class')
459            # page content div
460            if dc == 'queryResultPage':
461                pagediv = div
462               
463            elif dc == 'queryResultHits':
464                docinfo['resultSize'] = getInt(div.text)
465
466        if pagediv is not None:
467            # store XML in docinfo
468            docinfo['resultXML'] = ET.tostring(pagediv, 'UTF-8')
469
470        return docinfo
471   
472
473    def getResultsPage(self, mode="text", query=None, pn=None, start=None, size=None, pageinfo=None, docinfo=None):
474        """returns single page from the table of contents"""
475        logging.debug("getResultsPage mode=%s, pn=%s"%(mode,pn))
476        # get (cached) result
477        self.getSearchResults(mode=mode, query=query, pageinfo=pageinfo, docinfo=docinfo)
478           
479        resultxml = docinfo.get('resultXML', None)
480        if not resultxml:
481            logging.error("getResultPage: unable to find resultXML")
482            return "Error: no result!"
483       
484        if size is None:
485            size = pageinfo.get('resultPageSize', 10)
486           
487        if start is None:
488            start = (pn - 1) * size
489
490        fullresult = ET.fromstring(resultxml)
491       
492        if fullresult is not None:
493            # paginate
494            first = start-1
495            len = size
496            del fullresult[:first]
497            del fullresult[len:]
498            tocdivs = fullresult
499           
500            # check all a-tags
501            links = tocdivs.findall(".//a")
502            for l in links:
503                href = l.get('href')
504                if href:
505                    # assume all links go to pages
506                    linkUrl = urlparse.urlparse(href)
507                    linkParams = urlparse.parse_qs(linkUrl.query)
508                    # take some parameters
509                    params = {'pn': linkParams['pn'],
510                              'highlightQuery': linkParams.get('highlightQuery',''),
511                              'highlightElement': linkParams.get('highlightElement',''),
512                              'highlightElementPos': linkParams.get('highlightElementPos','')
513                              }
514                    url = self.getLink(params=params)
515                    l.set('href', url)
516                       
517            return serialize(tocdivs)
518       
519        return "ERROR: no results!"
520
521
522    def getToc(self, mode='text', docinfo=None):
523        """returns list of table of contents from docinfo"""
524        logging.debug("getToc mode=%s"%mode)
525        if mode == 'text':
526            queryType = 'toc'
527        else:
528            queryType = mode
529           
530        if not 'full_%s'%queryType in docinfo:
531            # get new toc
532            docinfo = self.getTextInfo(queryType, docinfo)
533           
534        return docinfo.get('full_%s'%queryType, [])
535
536    def getTocPage(self, mode='text', pn=None, start=None, size=None, pageinfo=None, docinfo=None):
537        """returns single page from the table of contents"""
538        logging.debug("getTocPage mode=%s, pn=%s start=%s size=%s"%(mode,repr(pn),repr(start),repr(size)))
539        fulltoc = self.getToc(mode=mode, docinfo=docinfo)
540        if len(fulltoc) < 1:
541            logging.error("getTocPage: unable to find toc!")
542            return "Error: no table of contents!"       
543       
544        if size is None:
545            size = pageinfo.get('tocPageSize', 30)
546           
547        if start is None:
548            start = (pn - 1) * size
549
550        # paginate
551        first = (start - 1)
552        last = first + size
553        tocs = fulltoc[first:last]
554        tp = '<div>'
555        for toc in tocs:
556            pageurl = self.getLink('pn', toc['pn'])
557            tp += '<div class="tocline">'
558            tp += '<div class="toc name">[%s %s]</div>'%(toc['level-string'], toc['content'])
559            tp += '<div class="toc float right page"><a href="%s">Page: %s</a></div>'%(pageurl, toc['pn'])
560            tp += '</div>\n'
561           
562        tp += '</div>\n'
563       
564        return tp
565           
566   
567    def manage_changeMpdlXmlTextServer(self,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,RESPONSE=None):
568        """change settings"""
569        self.title=title
570        self.timeout = timeout
571        self.serverUrl = serverUrl
572        if RESPONSE is not None:
573            RESPONSE.redirect('manage_main')
574       
575# management methods
576def manage_addMpdlXmlTextServerForm(self):
577    """Form for adding"""
578    pt = PageTemplateFile("zpt/manage_addMpdlXmlTextServer", globals()).__of__(self)
579    return pt()
580
581def manage_addMpdlXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,RESPONSE=None):
582#def manage_addMpdlXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de:30030/mpdl/interface/",timeout=40,RESPONSE=None):   
583    """add zogiimage"""
584    newObj = MpdlXmlTextServer(id,title,serverUrl,timeout)
585    self.Destination()._setObject(id, newObj)
586    if RESPONSE is not None:
587        RESPONSE.redirect('manage_main')
588       
589       
Note: See TracBrowser for help on using the repository browser.