source: documentViewer/MpiwgXmlTextServer.py @ 575:f0e5e9c6737f

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

new w-tag solution with css.
(processWTags doesn't work)

File size: 23.9 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 MpiwgXmlTextServer(SimpleItem):
26    """TextServer implementation for MPIWG-XML server"""
27    meta_type="MPIWG-XML TextServer"
28
29    manage_options=(
30        {'label':'Config','action':'manage_changeMpiwgXmlTextServerForm'},
31       )+SimpleItem.manage_options
32   
33    manage_changeMpiwgXmlTextServerForm = PageTemplateFile("zpt/manage_changeMpiwgXmlTextServer", globals())
34       
35    def __init__(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpiwg-mpdl-cms-web/", timeout=40, serverName=None, repositoryType='production'):
36        """constructor"""
37        self.id=id
38        self.title=title
39        self.timeout = timeout
40        self.repositoryType = repositoryType
41        if serverName is None:
42            self.serverUrl = serverUrl
43        else:
44            self.serverUrl = "http://%s/mpiwg-mpdl-cms-web/"%serverName
45       
46    def getHttpData(self, url, data=None):
47        """returns result from url+data HTTP request"""
48        return getHttpData(url,data,timeout=self.timeout)
49   
50    def getServerData(self, method, data=None):
51        """returns result from text server for method+data"""
52        url = self.serverUrl+method
53        return getHttpData(url,data,timeout=self.timeout)
54
55
56    def getRepositoryType(self):
57        """returns the repository type, e.g. 'production'"""
58        return getattr(self, 'repositoryType', None)
59
60    def getTextDownloadUrl(self, type='xml', docinfo=None):
61        """returns a URL to download the current text"""
62        docpath = docinfo.get('textURLPath', None)
63        if not docpath:
64            return None
65
66        docpath = docpath.replace('.xml','.'+type)
67        url = '%sdoc/GetDocument?id=%s'%(self.serverUrl.replace('interface/',''), docpath)
68        return url
69
70
71    def getPlacesOnPage(self, docinfo=None, pn=None):
72        """Returns list of GIS places of page pn"""
73        #FIXME!
74        docpath = docinfo.get('textURLPath',None)
75        if not docpath:
76            return None
77
78        places=[]
79        text=self.getServerData("xpath.xql", "document=%s&xpath=//place&pn=%s"%(docpath,pn))
80        dom = ET.fromstring(text)
81        result = dom.findall(".//resultPage/place")
82        for l in result:
83            id = l.get("id")
84            name = l.text
85            place = {'id': id, 'name': name}
86            places.append(place)
87
88        return places
89   
90         
91    def getTextInfo(self, mode=None, docinfo=None):
92        """reads document info, including page concordance, from text server"""
93        logging.debug("getTextInfo mode=%s"%mode)
94       
95        field = ''
96        if mode in ['pages', 'toc', 'figures', 'handwritten']:
97            # translate mode to field param
98            field = '&field=%s'%mode
99        else:
100            mode = None
101
102        # check cached info
103        if mode:
104            # cached toc-request?
105            if 'full_%s'%mode in docinfo:
106                return docinfo
107           
108        else:
109            # cached but no toc-request?
110            if 'numTextPages' in docinfo:
111                return docinfo
112               
113        docpath = docinfo.get('textURLPath', None)
114        if docpath is None:
115            logging.error("getTextInfo: no textURLPath!")
116            return docinfo
117               
118        # fetch docinfo           
119        pagexml = self.getServerData("query/GetDocInfo","docId=%s%s"%(docpath,field))
120        dom = ET.fromstring(pagexml)
121        # all info in tag <doc>
122        doc = dom
123        if doc is None:
124            logging.error("getTextInfo: unable to find document-tag!")
125        else:
126            if mode is None:
127                # get general info from system-tag
128                sys = doc.find('system')
129                if sys is not None:
130                    docinfo['numTextPages'] = getInt(getText(sys.find('countPages'))) 
131                    docinfo['numFigureEntries'] = getInt(getText(sys.find('countFigures'))) 
132                    docinfo['numHandwritten'] = getInt(getText(sys.find('countHandwritten'))) 
133                    docinfo['numTocEntries'] = getInt(getText(sys.find('countTocEntries'))) 
134                   
135            else:
136                # result is in list-tag
137                l = doc.find('list')
138                if l is not None:
139                    lt = l.get('type')
140                    # pageNumbers
141                    if lt == 'pages':
142                        # contains tags with page numbers
143                        # <item n="14" o="2" o-norm="2" file="0014"/>
144                        # n=scan number, o=original page no, on=normalized original page no
145                        # pageNumbers is a dict indexed by scan number
146                        pages = {}
147                        for i in l:
148                            page = {}
149                            pn = getInt(i.get('n'))
150                            page['pn'] = pn
151                            no = i.get('o')
152                            page['no'] = no
153                            non = i.get('o-norm')
154                            page['non'] = non
155                                   
156                            if pn > 0:
157                                pages[pn] = page
158                           
159                        docinfo['pageNumbers'] = pages
160                        logging.debug("got pageNumbers=%s"%repr(pages))
161                                   
162                    # toc
163                    elif lt == 'toc' or lt == 'figures' or lt == 'handwritten':
164                        # contains tags with table of contents/figures
165                        # <item n="2.1." lv="2">CAP.I. <ref o="119">132</ref></item>
166                        tocs = []
167                        for te in l:
168                            if te.tag == 'item':
169                                toc = {}
170                                toc['level-string'] = te.get('n')
171                                toc['level'] = te.get('lv')
172                                toc['content'] = te.text.strip()
173                                ref = te.find('ref')
174                                toc['pn'] = getInt(ref.text)
175                                toc['no'] = ref.get('o')
176                                toc['non'] = ref.get('o-norm')
177                                tocs.append(toc)
178                       
179                        # save as full_toc/full_figures
180                        docinfo['full_%s'%mode] = tocs
181
182        return docinfo
183       
184         
185    def getTextPage(self, mode="text", pn=1, docinfo=None, pageinfo=None):
186        """returns single page from fulltext"""
187       
188        logging.debug("getTextPage mode=%s, pn=%s"%(mode,pn))
189        # check for cached text -- but ideally this shouldn't be called twice
190        if pageinfo.has_key('textPage'):
191            logging.debug("getTextPage: using cached text")
192            return pageinfo['textPage']
193       
194        docpath = docinfo.get('textURLPath', None)
195        if not docpath:
196            return None
197       
198        # just checking
199        if pageinfo['current'] != pn:
200            logging.warning("getTextPage: current!=pn!")
201           
202        # stuff for constructing full urls
203        selfurl = docinfo['viewerUrl']
204        textParams = {'docId': docpath,
205                      'page': pn}
206       
207        normMode = pageinfo.get('characterNormalization', 'reg')
208        # TODO: change values in form
209        if normMode == 'regPlusNorm':
210            normMode = 'norm'
211                       
212        if not mode:
213            # default is dict
214            mode = 'text'
215
216        modes = mode.split(',')
217        # check for multiple layers
218        if len(modes) > 1:
219            logging.debug("getTextPage: more than one mode=%s"%mode)
220                       
221        # search mode
222        if 'search' in modes:
223            # add highlighting
224            highlightQuery = pageinfo.get('highlightQuery', None)
225            if highlightQuery:
226                textParams['highlightQuery'] = highlightQuery
227                textParams['highlightElem'] = pageinfo.get('highlightElement', '')
228                textParams['highlightElemPos'] = pageinfo.get('highlightElementPos', '')
229               
230            # ignore mode in the following
231            modes.remove('search')
232                           
233        # pundit mode
234        punditMode = False
235        if 'pundit' in modes:
236            punditMode = True
237            # ignore mode in the following
238            modes.remove('pundit')
239                           
240        # other modes don't combine
241        if 'dict' in modes:
242            textmode = 'dict'
243            textParams['mode'] = 'tokenized'
244            textParams['outputFormat'] = 'html'
245        elif 'xml' in modes:
246            textmode = 'xml'
247            textParams['mode'] = 'untokenized'
248            textParams['outputFormat'] = 'xmlDisplay'
249            textParams['normMode'] = 'orig'
250        elif 'gis' in modes:
251            #FIXME!
252            textmode = 'gis'
253        else:
254            # text is default mode
255            textmode = 'plain'
256            textParams['mode'] = 'untokenized'
257            textParams['outputFormat'] = 'html'
258       
259        try:
260            # fetch the page
261            pagexml = self.getServerData("query/GetPage",urllib.urlencode(textParams))
262            dom = ET.fromstring(pagexml)
263        except Exception, e:
264            logging.error("Error reading page: %s"%e)
265            return None
266       
267        # plain text or text-with-links mode
268        if textmode == "plain" or textmode == "dict":
269            # the text is in div@class=text
270            pagediv = dom.find(".//div[@class='text']")
271            logging.debug("pagediv: %s"%repr(pagediv))
272            if pagediv is not None:
273                # add textmode and normMode classes
274                pagediv.set('class', 'text %s %s'%(textmode, normMode))
275                #self._processWTags(textmode, normMode, pagediv)
276                #self._processPbTag(pagediv, pageinfo)
277                self._processFigures(pagediv, docinfo)
278                #self._fixEmptyDivs(pagediv)
279                # get full url assuming documentViewer is parent
280                selfurl = self.getLink()
281                # check all a-tags
282                links = pagediv.findall('.//a')
283                for l in links:
284                    href = l.get('href')
285                    if href:
286                        # is link with href
287                        linkurl = urlparse.urlparse(href)
288                        if linkurl.path.endswith('GetDictionaryEntries'):
289                            #TODO: replace wordInfo page
290                            # is dictionary link - change href (keeping parameters)
291                            #l.set('href', href.replace('http://mpdl-proto.mpiwg-berlin.mpg.de/mpdl/interface/lt/wordInfo.xql','%s/template/viewer_wordinfo'%viewerurl))
292                            # add target to open new page
293                            l.set('target', '_blank')
294                                                         
295                        elif href.startswith('#note-'):
296                            # note link FIXME!
297                            l.set('href', href.replace('#note-',"%s#note-"%selfurl))
298                       
299                if punditMode:
300                    self._addPunditAttributes(pagediv, pageinfo, docinfo)
301                   
302                return serialize(pagediv)
303           
304        # xml mode
305        elif textmode == "xml":
306            # the text is in body
307            pagediv = dom.find(".//body")
308            logging.debug("pagediv: %s"%repr(pagediv))
309            if pagediv is not None:
310                return serialize(pagediv)
311           
312        # pureXml mode WTF?
313        elif textmode == "pureXml":
314            # the text is in body
315            pagediv = dom.find(".//body")
316            logging.debug("pagediv: %s"%repr(pagediv))
317            if pagediv is not None:
318                return serialize(pagediv)
319                 
320        # gis mode FIXME!
321        elif textmode == "gis":
322            # the text is in div@class=text
323            pagediv = dom.find(".//div[@class='text']")
324            logging.debug("pagediv: %s"%repr(pagediv))
325            if pagediv is not None:
326                # fix empty div tags
327                self._fixEmptyDivs(pagediv)
328                # check all a-tags
329                links = pagediv.findall(".//a")
330                # add our URL as backlink
331                selfurl = self.getLink()
332                doc = base64.b64encode(selfurl)
333                for l in links:
334                    href = l.get('href')
335                    if href:
336                        if href.startswith('http://mappit.mpiwg-berlin.mpg.de'):
337                            l.set('href', re.sub(r'doc=[\w+/=]+', 'doc=%s'%doc, href))
338                            l.set('target', '_blank')
339                           
340                return serialize(pagediv)
341                   
342        logging.error("getTextPage: error in text mode %s or text!"%(textmode))
343        return None
344
345    def _processWTags(self, textMode, normMode, pagediv):
346        """selects the necessary information from w-spans and removes the rest from pagediv"""
347        logging.debug("processWTags(textMode=%s,norm=%s,pagediv"%(repr(textMode),repr(normMode)))
348        wtags = pagediv.findall(".//span[@class='w']")
349        for wtag in wtags:
350            text = None
351            attr = None
352            if textMode == 'dict':
353                # take a-tag and matching child
354                attr = wtag.find('a').items()
355                text = wtag.find("a/span[@class='%s']"%normMode).text
356            else:
357                # take matching child
358                text = wtag.find("span[@class='nodictionary %s']"%normMode).text
359               
360            if text:
361                # replace wtag by new content
362                logging.debug("new w-tag attr=%s text=%s"%(attr,text))
363                wtag.clear()
364                   
365                if attr:
366                    # make dictionary link
367                    wtag.tag = 'a'
368                    wtag.attrib.update(dict(attr))
369                   
370                # text content
371                wtag.text = text
372               
373        return pagediv
374       
375    def _processPbTag(self, pagediv, pageinfo):
376        """extracts information from pb-tag and removes it from pagediv"""
377        pbdiv = pagediv.find(".//span[@class='pb']")
378        if pbdiv is None:
379            logging.warning("getTextPage: no pb-span!")
380            return pagediv
381       
382        # extract running head
383        rh = pbdiv.find(".//span[@class='rhead']")
384        if rh is not None:
385            pageinfo['pageHeaderTitle'] = getText(rh)
386           
387        # remove pb-div from parent
388        ppdiv = pagediv.find(".//span[@class='pb']/..")
389        ppdiv.remove(pbdiv)       
390        return pagediv
391   
392    def _addPunditAttributes(self, pagediv, pageinfo, docinfo):
393        """add about attributes for pundit annotation tool"""
394        textid = docinfo.get('DRI', "fn=%s"%docinfo.get('documentPath', '???'))
395        pn = pageinfo.get('pn', '1')
396        #  TODO: use pn as well?
397        # check all div-tags
398        divs = pagediv.findall(".//div")
399        for d in divs:
400            id = d.get('id')
401            if id:
402                # TODO: check path (cf RFC2396)
403                d.set('about', "http://echo.mpiwg-berlin.mpg.de/%s/pn=%s/#%s"%(textid,pn,id))
404                cls = d.get('class','')
405                cls += ' pundit-content'
406                d.set('class', cls.strip())
407
408        return pagediv
409
410    def _processFigures(self, pagediv, docinfo):
411        """processes figure-tags"""
412        divs = pagediv.findall(".//span[@class='figure']")
413        scalerUrl = docinfo['digilibScalerUrl']
414        viewerUrl = docinfo['digilibViewerUrl']
415        for d in divs:
416            try:
417                a = d.find('a')
418                img = a.find('img')
419                imgsrc = img.get('src')
420                imgurl = urlparse.urlparse(imgsrc)
421                imgq = imgurl.query
422                imgparams = urlparse.parse_qs(imgq)
423                fn = imgparams.get('fn', None)
424                if fn is not None:
425                    # parse_qs puts parameters in lists
426                    fn = fn[0]
427                    # TODO: check valid path
428                    # fix img@src
429                    newsrc = '%s?fn=%s&dw=200&dh=200'%(scalerUrl,fn)
430                    img.set('src', newsrc)
431                    # fix a@href
432                    newlink = '%s?fn=%s'%(viewerUrl,fn)
433                    a.set('href', newlink)
434                    a.set('target', '_blank')
435                   
436            except:
437                logging.warn("processFigures: strange figure!")
438               
439   
440    def _fixEmptyDivs(self, pagediv):
441        """fixes empty div-tags by inserting a space"""
442        divs = pagediv.findall('.//div')
443        for d in divs:
444            if len(d) == 0 and not d.text:
445                # make empty divs non-empty
446                d.text = ' '
447 
448        return pagediv
449
450
451    def getSearchResults(self, mode, query=None, pageinfo=None, docinfo=None):
452        """loads list of search results and stores XML in docinfo"""
453       
454        logging.debug("getSearchResults mode=%s query=%s"%(mode, query))
455        if mode == "none":
456            return docinfo
457             
458        #TODO: put mode into query
459       
460        cachedQuery = docinfo.get('cachedQuery', None)
461        if cachedQuery is not None:
462            # cached search result
463            if cachedQuery == '%s_%s'%(mode,query):
464                # same query
465                return docinfo
466           
467            else:
468                # different query
469                del docinfo['resultSize']
470                del docinfo['results']
471       
472        # cache query
473        docinfo['cachedQuery'] = '%s_%s'%(mode,query)
474       
475        # fetch full results
476        docpath = docinfo['textURLPath']
477        params = {'docId': docpath,
478                  'query': query,
479                  'pageSize': 1000,
480                  'page': 1,
481                  'outputFormat': 'html'}
482        pagexml = self.getServerData("query/QueryDocument",urllib.urlencode(params))
483        results = []
484        try:
485            dom = ET.fromstring(pagexml)
486            # page content is currently in multiple <td align=left>
487            alldivs = dom.findall(".//td[@align='left']")
488            for div in alldivs:
489                # TODO: can we put etree in the session?
490                results.append(div)
491       
492        except Exception, e:
493            logging.error("GetSearchResults: Error parsing search result: %s"%e)
494               
495        # store results in docinfo
496        docinfo['resultSize'] = len(results)
497        docinfo['results'] = results
498
499        return docinfo
500   
501
502    def getResultsPage(self, mode="text", query=None, pn=None, start=None, size=None, pageinfo=None, docinfo=None):
503        """returns single page from the table of contents"""
504        logging.debug("getResultsPage mode=%s, pn=%s"%(mode,pn))
505        # get (cached) result
506        self.getSearchResults(mode=mode, query=query, pageinfo=pageinfo, docinfo=docinfo)
507           
508        resultxml = docinfo.get('results', None)
509        if not resultxml:
510            logging.error("getResultPage: unable to find results")
511            return "Error: no result!"
512       
513        if size is None:
514            size = pageinfo.get('resultPageSize', 10)
515           
516        if start is None:
517            start = (pn - 1) * size
518
519        fullresult = ET.fromstring(resultxml)
520       
521        if fullresult is not None:
522            # paginate
523            first = start-1
524            len = size
525            del fullresult[:first]
526            del fullresult[len:]
527            tocdivs = fullresult
528           
529            # check all a-tags
530            links = tocdivs.findall(".//a")
531            for l in links:
532                href = l.get('href')
533                if href:
534                    # assume all links go to pages
535                    linkUrl = urlparse.urlparse(href)
536                    linkParams = urlparse.parse_qs(linkUrl.query)
537                    # take some parameters
538                    params = {'pn': linkParams['pn'],
539                              'highlightQuery': linkParams.get('highlightQuery',''),
540                              'highlightElement': linkParams.get('highlightElement',''),
541                              'highlightElementPos': linkParams.get('highlightElementPos','')
542                              }
543                    url = self.getLink(params=params)
544                    l.set('href', url)
545                       
546            return serialize(tocdivs)
547       
548        return "ERROR: no results!"
549
550
551    def getToc(self, mode='text', docinfo=None):
552        """returns list of table of contents from docinfo"""
553        logging.debug("getToc mode=%s"%mode)
554        if mode == 'text':
555            queryType = 'toc'
556        else:
557            queryType = mode
558           
559        if not 'full_%s'%queryType in docinfo:
560            # get new toc
561            docinfo = self.getTextInfo(queryType, docinfo)
562           
563        return docinfo.get('full_%s'%queryType, [])
564
565
566    def getTocPage(self, mode='text', pn=None, start=None, size=None, pageinfo=None, docinfo=None):
567        """returns single page from the table of contents"""
568        logging.debug("getTocPage mode=%s, pn=%s start=%s size=%s"%(mode,repr(pn),repr(start),repr(size)))
569        fulltoc = self.getToc(mode=mode, docinfo=docinfo)
570        if len(fulltoc) < 1:
571            logging.error("getTocPage: unable to find toc!")
572            return "Error: no table of contents!"       
573       
574        if size is None:
575            size = pageinfo.get('tocPageSize', 30)
576           
577        if start is None:
578            start = (pn - 1) * size
579
580        # paginate
581        first = (start - 1)
582        last = first + size
583        tocs = fulltoc[first:last]
584        tp = '<div>'
585        for toc in tocs:
586            pageurl = self.getLink('pn', toc['pn'])
587            tp += '<div class="tocline">'
588            content = toc['content']
589            if content:
590                tp += '<div class="toc name">[%s] %s</div>'%(toc['level-string'], toc['content'])
591            else:
592                tp += '<div class="toc name">[Figure %s]</div>'%(toc['level-string'])
593           
594            if toc.get('no', None):
595                tp += '<div class="toc page"><a href="%s">Page: %s (%s)</a></div>'%(pageurl, toc['pn'], toc['no'])
596            else:
597                tp += '<div class="toc page"><a href="%s">Page: %s</a></div>'%(pageurl, toc['pn'])
598               
599            tp += '</div>\n'
600           
601        tp += '</div>\n'
602       
603        return tp
604           
605   
606    def manage_changeMpiwgXmlTextServer(self,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,repositoryType=None,RESPONSE=None):
607        """change settings"""
608        self.title=title
609        self.timeout = timeout
610        self.serverUrl = serverUrl
611        if repositoryType:
612            self.repositoryType = repositoryType
613        if RESPONSE is not None:
614            RESPONSE.redirect('manage_main')
615       
616# management methods
617def manage_addMpiwgXmlTextServerForm(self):
618    """Form for adding"""
619    pt = PageTemplateFile("zpt/manage_addMpiwgXmlTextServer", globals()).__of__(self)
620    return pt()
621
622def manage_addMpiwgXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de/mpdl/interface/",timeout=40,RESPONSE=None):
623#def manage_addMpiwgXmlTextServer(self,id,title="",serverUrl="http://mpdl-text.mpiwg-berlin.mpg.de:30030/mpdl/interface/",timeout=40,RESPONSE=None):   
624    """add zogiimage"""
625    newObj = MpiwgXmlTextServer(id=id,title=title,serverUrl=serverUrl,timeout=timeout)
626    self.Destination()._setObject(id, newObj)
627    if RESPONSE is not None:
628        RESPONSE.redirect('manage_main')
629       
630       
Note: See TracBrowser for help on using the repository browser.