    1: from Acquisition import aq_base, aq_inner
    1: import tempfile, logging, os, re, StringIO
    1: from types import *
    1: from interfaces import IPDFParser
    1: from zope import interface
    1: from zope import component
    1: from zope.app.component.hooks import getSite
    1: from Products.CMFCore.utils import getToolByName
       
    1: logger = logging.getLogger('slc.publications.pdf')
       
    2: class PDFParser(object):
           """ parses metadata from pdf files """
       
    1:     interface.implements(IPDFParser)
           
    1:     def parse(self, pdf, owner_password='', user_password=''):
               """ parses the given pdf file and returns a mapping of attributes """
               
               # This will store the parsed metadata
    2:         META_MAP = {}
             
    2:         statement = "pdfinfo -meta"
    2:         if owner_password !="":
>>>>>>             statement += ' -opw ' + owner_password
    2:         if user_password !="":
>>>>>>             statement += ' -upw ' + user_password
       
               # pdfinfo needs to work on a file. Write the file and start pdfinfo
    2:         tmp_pdf = tempfile.mkstemp(suffix='.pdf')
    2:         fd = open(tmp_pdf[1], 'w')
    2:         if type(pdf) == InstanceType and pdf.__class__ == StringIO.StringIO:
    1:             fd.write( pdf.getvalue() )
    1:         elif type(pdf) in [StringType, UnicodeType]:
    1:             fd.write( pdf )
>>>>>>         elif type(pdf) == FileType:
>>>>>>             fd.write( str(pdf) )
               else:
>>>>>>             raise ValueError, 'Cannot determine type of pdf variable'
    2:         fd.close()
       
    2:         statement += ' '+tmp_pdf[1]
    2:         logger.debug('pdfinfo commandline: %s' % statement)
    2:         ph = os.popen4( statement )
       
               # get the result
    2:         result = ph[1].read()
    2:         logger.debug('metadata extracted by pdfinfo :\n--------------------------------\n%s ' % result)
       
    2:         ph[0].close()
    2:         ph[1].close()
               
               # cleanup the tempfile
    2:         os.remove(tmp_pdf[1])
       
               # check for errors or encryption
    2:         if result.startswith('Error: No paper information available - using defaults'):
                   # Irritating error if libpaper is not configured correctly. For our case this is irrelevant
    2:             pass
>>>>>>         elif result.startswith('Error'):
>>>>>>             error =  result.split('\n')[0]
>>>>>>             logger.error("Error in pdfinfo conversion: %s" % error)
>>>>>>             return False
                   
    2:         crypt_patt = re.compile('Encrypted:.*?copy:no', re.I)
    2:         mobj = crypt_patt.search(result, 1)
    2:         if mobj is not None:
>>>>>>             error = "Error: PDF is encrypted"
>>>>>>             logger.error(error)
>>>>>>             return False
                   
               # Everything seems fine, parse the metadata
               # Caution: do not use the metalist, it's not unicode!
               # Note that pdfinfo returns a ini style list and an xml version.
    2:         METADATA = result.split('Metadata:')
    2:         if len(METADATA)>1:
    2:             metalist, metaxml = METADATA
               else:
>>>>>>             metalist, metaxml = (result, '')
       
       
               # Hooray, metadata in the list part is not the same as the metadata in xml. Uff.
               # But metalist may not be unicode. Lets get it anyway..
       
       #        list_map = {}
       #        for line in metalist.split("\n"):
       #            elems = line.split(":")
       #            if len (elems)>1:
       #                k = elems[0].strip()
       #                v = ":".join(elems[1:]).strip()
       #            else:
       #                continue                
       #            list_map[k] = v
                   
                   
               # Get metadata out of the xml-part
               # XXX: There is probably a proper definition what to expect here. 
               # If would be a good idea to make this generic
               # It even would be smart to use an xml parser here.
    2:         patt_list = []
    2:         patt_list.append( ('Keywords', "<pdf:Keywords>(.*?)</pdf:Keywords>") )
    2:         patt_list.append( ('Keywords', "pdf:Keywords='(.*?)'") )
    2:         patt_list.append( ('Language', "<pdf:Language>(.*?)</pdf:Language>") )
    2:         patt_list.append( ('Language', "pdf:Language='(.*?)'") )
    2:         patt_list.append( ('UUID', "xapMM:DocumentID='uuid:(.*?)'") )
    2:         patt_list.append( ('UUID', 'rdf:about="uuid:(.*?)"') )
    2:         patt_list.append( ('CreationDate', "xap:CreateDate='(.*?)'") )
    2:         patt_list.append( ('CreationDate', "<xap:CreateDate>(.*?)</xap:CreateDate>") )
    2:         patt_list.append( ('ModificationDate', "xap:ModifyDate='(.*?)'") )
    2:         patt_list.append( ('ModificationDate', "<xap:ModifyDate>(.*?)</xap:ModifyDate>") )
    2:         patt_list.append( ('MetadataDate', "xap:MetadataDate='(.*?)'") )
    2:         patt_list.append( ('MetadataDate', "<xap:MetadataDate>(.*?)</xap:MetadataDate>") )
    2:         patt_list.append( ('Rights Webstatement', "<xapRights:WebStatement>(.*?)</xapRights:WebStatement>") )
    2:         patt_list.append( ('Producer', "<pdf:Producer>(.*?)</pdf:Producer>") )
    2:         patt_list.append( ('CreatorTool', "<xap:CreatorTool>(.*?)</xap:CreatorTool>") )
    2:         patt_list.append( ('Title', "<dc:title>(.*?)</dc:title>") )
    2:         patt_list.append( ('Description', "<dc:description>(.*?)</dc:description>") )
    2:         patt_list.append( ('Rights', "<dc:rights>(.*?)</dc:rights>") )
    2:         patt_list.append( ('Format', "<dc:format>(.*?)</dc:format>") )
    2:         patt_list.append( ('Creator', "<dc:creator>(.*?)</dc:creator>") )
    2:         patt_list.append( ('OPOCE', "pdfx:OPOCE='(.*?)'") )
    2:         patt_list.append( ('OPOCE', "<pdfx:OPOCE>(.*?)</pdfx:OPOCE>") )
       
   46:         for patt in patt_list:
   44:             pobj = re.compile(patt[1], re.I | re.S)
   44:             mobj = pobj.search(metaxml, 1)
   44:             if mobj is not None:
   20:                 value = re.sub('<.*?>', '', mobj.group(1)).strip()
                       # acrobat separates keywords with a semicolon. There is no datatyping
                       # so we assume it is a list if a semicolon appears.
   20:                 if ";" in value:
    2:                     kw = value.split(";")
    6:                     value = tuple([x.strip() for x in kw])
                           
   20:                 META_MAP[patt[0].strip().lower()] = value
                   else:
   24:                 logger.debug("No matches for "+ str(patt[1]))
       
       
               # Get the user-defined meta-data
    2:         add_patt = re.compile("pdfx:(.*?)='(.*?)'", re.I|re.S)
       
    2:         for name, value in add_patt.findall(metaxml):
                   # acrobat separates keywords with a semicolon. There is no datatyping
                   # so we assume it is a list if a semicolon appears.
>>>>>>             if ";" in value:
>>>>>>                 kw = value.split(";")
>>>>>>                 value = tuple([x.strip() for x in kw])
>>>>>>             META_MAP[name.strip().lower()] = value
       
               # And another format
    2:         add_patt = re.compile("pdfx:(.*?)>(.*?)</pdfx:", re.I|re.S)
    5:         for name, value in add_patt.findall(metaxml):
                   # acrobat separates keywords with a semicolon. There is no datatyping
                   # so we assume it is a list if a semicolon appears.
    3:             if ";" in value:
    1:                 kw = value.split(";")
    3:                 value = tuple([x.strip() for x in kw])
    3:             META_MAP[name.strip().lower()] = value
               
               
               # If the language is given in the filename extension, we consider that as 
               # most explicit
               
    2:         l = self._guessLanguage(pdf)
    2:         if l and not META_MAP.has_key('language'):
>>>>>>             META_MAP['language'] = l
               
               # Finally we'll do some plone specific rewritings
               # It would be smart to hook some kind of adapter here so that one can define his own rewritings 
    2:         if META_MAP.has_key('keywords'):
    2:             META_MAP['subject_keywords'] = list(META_MAP['keywords'])
               
    2:         return META_MAP
       
       
    1:     def _guessLanguage(self, file):
               """
               try to find a language abbreviation in the string
               acceptable is a two letter language abbreviation at the start of the string followed by an _
               or at the end of the string prefixed by an _ just before the extension
               """
    2:         if hasattr(file, 'filename'):
    1:             filename = file.filename
    1:         elif hasattr(file, 'id'):
>>>>>>             filename = file.id
    1:         elif hasattr(file, 'getId'):
>>>>>>             filename = file.getId
               else:
    1:             return None
       
    1:         if callable(filename):
>>>>>>             filename = filename()            
                           
    1:         def findAbbrev(id):
    1:             if len(id)>3 and id[2] in ['_', '-']:
>>>>>>                 lang = id[0:2].lower()
>>>>>>                 if lang in langs:
>>>>>>                     return lang
    1:             if len(id)>3 and '.' in id:
    1:                 elems = id.split('.')
    1:                 filename = ".".join(elems[:-1])
    1:                 if len(filename)>3 and filename[-3] in ['_', '-']:
>>>>>>                     lang = filename[-2:].strip()
>>>>>>                     if lang in langs:
>>>>>>                         return lang
    1:                 elif len(filename)==2:
>>>>>>                     lang = filename
>>>>>>                     if lang in langs:
>>>>>>                         return lang
       
       
    1:         site = getSite()
    1:         portal_languages = getToolByName(site, 'portal_languages')
    1:         langs = portal_languages.getSupportedLanguages()
       
    1:         langbyfileid = findAbbrev(filename)
    1:         if langbyfileid in langs:
>>>>>>             return langbyfileid
       
    1:         return ''        