source: TI01-discovery-Ingest/trunk/v4.3.0/ingestAutomation-upgrade/OAIBatch/ExtractISO.py @ 7186

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery-Ingest/trunk/v4.3.0/ingestAutomation-upgrade/OAIBatch/ExtractISO.py@7186
Revision 7186, 35.4 KB checked in by sdonegan, 9 years ago (diff)

Adding files from v4.2.0 tag to form basis for v4.3.0 version

Line 
1
2from xml.etree import ElementTree as ET
3import logging,urllib,os,sys,inspect
4from Utilities import IsoIngestListUtilities # so can use those useful getSingleVal and getMultipleVal methods for discerning lists etc (esp useful when using the isoVal to dbColumn methods..)
5
6
7class ExtractISO:
8
9        def __init__(self,filenameIP,isoFormat):                               
10                               
11                self.inputXMLfile = filenameIP
12                self.isoFormatToExtract = isoFormat
13               
14                #show actual values extracted from the XML in logging.info
15                self.showXMLvalues = True
16               
17                logging.info("Have initialised ISO XML Extractor")
18               
19                #self.createISOdataStructure()
20               
21               
22        '''
23        Method to control re-writing of ISO xml with NDG redirect URL's
24        '''
25        def generateNDGredirectURL(self, ndgRedirectURL, xmlOutputFile, changeUrl):
26               
27                logging.info("Generating XML copy with URLS converted to NDG redirect URL's")
28               
29                self.redirectBaseURL = ndgRedirectURL
30               
31                rewriteIsoXml = self.changeElementUrlVal(self.isoModel.urlsToChange(), xmlOutputFile, changeUrl)
32                               
33                return rewriteIsoXml
34       
35       
36       
37        '''
38        Method to map ISO variables to Discovery Database columns - use in PostgresDAO.py with getattr!
39       
40        returns a dictionary where key is
41    '''
42        def mapIsoFieldsToDBcolumns(self):
43               
44                return {'originalFormat':'original_format_name', \
45        'originalFormatVersion':'original_format_version',\
46        'datasetAbstract':'dataset_abstract', \
47        'resourceType_text':'resource_type', \
48        'resourceType_tsvector':'resource_type_ts_vector', \
49        'topicCategory_text':'topic_category', \
50        'topicCategory_tsvector':'topic_category_ts_vector', \
51        'lineage_text':'lineage', \
52        'lineage_tsvector':'lineage_ts_vector', \
53        'publicAccessLimitations_text':'limitations_public_access', \
54        'publicAccessLimitations_tsvector':'limitations_public_access_ts_vector', \
55        'dataOriginator_text':'data_originator',\
56        'dataOriginator_tsvector':'data_originator_tsvector',\
57        'metadataUpdateDate':'dataset_metadata_update_date', \
58        'dataFormat':'original_format_name',
59        'createDate':'dataset_metadata_creation_date',
60        'publicationDate':'dataset_metadata_publication_date',
61        'authors_text':'authors','parameters_text':'parameters',
62        'dataFormats_text':'data_formats','dataFormats_ts_vector':'data_formats_tsvector'}
63               
64               
65        '''
66                Method to match the columns found in mapIsoFieldsToDBcolumns to the postgres data types
67        '''
68        def mapDBcolumnsToIsoDataTypes(self):
69               
70                return {'original_format_name':'text', \
71        'original_format_version':'text',\
72        'dataset_abstract':'text', \
73        'resource_type':'text', \
74        'resource_type_ts_vector':'tsvector', \
75        'topic_category':'text', \
76        'topic_category_ts_vector':'tsvector', \
77        'lineage':'text', \
78        'limitations_public_access':'text', \
79        'lineage_ts_vector':'tsvector', \
80        'limitations_public_access_ts_vector':'tsvector', \
81        'data_originator':'text',\
82        'data_originator_tsvector':'tsvector',\
83        'dataset_metadata_update_date':'timestamp', \
84        'original_format_name':'text',
85        'dataset_metadata_creation_date':'timestamp',
86        'dataset_metadata_publication_date':'timestamp',
87        'authors':'text','parameters':'text',
88        'data_formats':'text','data_formats_tsvector':'tsvector'}
89       
90               
91        '''
92        Simple method to allow return information on success of extraction ( __init__ method cannot return anything so use this method)
93        '''
94        def createISOdataStructure(self):
95               
96                logging.info("")
97                logging.info("********************************************************************************************************************************************")
98                logging.info("****************** Creating ISO data structure from " + self.inputXMLfile + "****************** ")
99                logging.info("********************************************************************************************************************************************")
100                logging.info("")
101               
102                #set processing message and processing docs
103                self.processingMsg = ''
104                self.validDoc = True
105               
106                self.root = self.getIsoXML(self.inputXMLfile)
107               
108                self.isoFileLocation = self.inputXMLfile       
109               
110                #self.root = self.getIsoXML('/misc/glue1/sdonegan/NDG3_workspace/buildouts/oai_document_ingester_MEDIN/ingestAutomation-upgrade/OAIBatch//data/NEODC/stub_iso/neodc.nerc.ac.uk__stubISO__dataent_11658383444211836_DIF.xml')
111               
112                if self.root is None:
113                        logging.error("Detected possible problem with input xml - cannot parse it!")
114                        self.processingMsg = 'Problem with the XML structure!'
115                        self.validDoc = False
116                        return False
117               
118                #choose the ISO xpath definition class depending on format profile required
119                self.importISOxmlDefinition(self.isoFormatToExtract)
120                                                       
121                #get all the required information from the chosen xpath definition class
122                self.datasetID = self.getElementVal(self.isoModel.datasetID())
123                if self.datasetID == 'None':
124                        self.processingMsg = 'No entry for Dataset ID so record not valid and cannot/will not Ingest!'
125                        logging.error(self.processingMsg)
126                        self.validDoc = False
127               
128                else:
129                        #noticed some MEDIN centres allowing space characters in a unique id.. change to single underscore! (remember only 1 allowed!)
130                        self.datasetID[0][0] = self.datasetID[0][0].replace(" ", "_")
131               
132               
133                self.datasetName = self.getElementVal(self.isoModel.dataSetName())
134                if self.datasetName == 'None':
135                        self.processingMsg = 'No entry for Dataset Name so record not valid and cannot/will not Ingest!'
136                        logging.error(self.processingMsg)
137                        self.validDoc = False
138               
139                self.datasetAbstract = self.getElementVal(self.isoModel.dataSetAbstract())
140                               
141                self.revisionDate = self.getElementVal(self.isoModel.metadataRevisionDate())
142               
143                self.createDate = self.getElementVal(self.isoModel.metadataCreationDate())
144                               
145                self.datasetName = self.getElementVal(self.isoModel.dataSetName())             
146               
147                self.boundingDates = self.getElementVal(self.isoModel.boundingDates())
148               
149                self.boundingDatesRange = self.boundingDateRange(self.boundingDates)
150                                       
151                self.originalFormat = self.getElementVal(self.isoModel.originalFormat())
152               
153                self.dataFormats = self.getElementVal(self.isoModel.dataFormats())
154                               
155                if self.dataFormats != 'None':
156                       
157                        self.dataFormatsOb = IsoIngestListUtilities(self.dataFormats,True)
158                        self.dataFormats_text = self.dataFormatsOb.getDelimitedStringFromList(self.dataFormatsOb.listVals)
159                       
160                        #will be nothing in there due to way we've caught NoneTypes in Utilities.py - so set to 'None' here so can be nulled in sql
161                        if len(self.dataFormats_text) == 0:
162                                self.dataFormats_text = 'None'
163                       
164                        self.dataFormats_ts_vector = self.dataFormats_text
165                                               
166                else:
167                        self.dataFormats_text = 'None'
168                        self.dataFormats_ts_vector = 'None'
169                       
170                self.authors = self.getElementVal(self.isoModel.authors())
171               
172                if self.authors == 'None':
173                        self.processingMsg = 'No entry for Authors so record not valid and cannot/will not Ingest!'
174                        logging.error(self.processingMsg)
175                        self.validDoc = False
176               
177                else:
178                        #need to provide a list for updating relevant db column - keep distinct from other authors based vars just in case of problems..               
179                        self.authorOb = IsoIngestListUtilities(self.authors,True)               
180                        self.authors_text = self.authorOb.getDelimitedStringFromList(self.authorOb.listVals)           
181                        self.authors_ts_vector = self.authors_text
182                               
183                self.datacentreName = self.getElementVal(self.isoModel.dataCentreName())
184                               
185                if self.datacentreName == 'None':
186                        self.processingMsg = 'No entry for Data Centre name so record not valid and cannot/will not Ingest!'
187                        logging.error(self.processingMsg)
188                        self.validDoc = False
189               
190               
191                self.keywords = self.getElementVal(self.isoModel.keywords())
192               
193                self.keywordsList = self.listify(self.keywords)
194               
195                if self.keywords == 'None':
196                        logging.warn("No parameter info for record!")
197                        self.parameters_text = ''
198                        self.parameters_tsvector = ''
199                else:           
200                        self.parametersOb = IsoIngestListUtilities(self.keywords,True)                   
201                        self.parameters_text = self.parametersOb.getDelimitedStringFromList(self.parametersOb.listVals)
202                        self.parameters_tsvector = self.parameters_text
203               
204               
205               
206                coords = self.getElementVal(self.isoModel.coordinates())
207                if coords == 'None':                   
208                        self.boundingBoxCoordinates = None
209                else:
210                        self.boundingBoxCoordinates = coords   
211               
212                #These are the MEDIN requested extra fields - make optional
213                try:
214                        self.originalFormatVersion = self.getElementVal(self.isoModel.originalFormatVersion())
215                except:
216                        self.originalFormatVersion = None
217               
218                try:
219                        self.publicationDate = self.getElementVal(self.isoModel.metadataPublicationDate())
220                except:
221                        self.publicationDate = None
222                       
223                try:
224                        self.resourceType = self.getElementVal(self.isoModel.resourceType())
225                        self.resourceType_text = self.resourceType
226                        self.resourceType_tsvector = self.resourceType
227                except:
228                        self.resourceType = None
229                        self.resourceType_text = None
230                        self.resourceType_tsvector = None
231               
232                try:
233                        self.topicCategory = self.getElementVal(self.isoModel.topicCategory())
234                        self.topicCategoryList = self.listify(self.topicCategory)
235                       
236                        if self.topicCategory == 'None':
237                                logging.warn("No topic category keywords available!")
238                                self.topicCategory_text = None
239                                self.topicCategory_tsvector = None
240                        else:
241                                self.topicCategoryOb = IsoIngestListUtilities(self.topicCategory,True)
242                                self.topicCategory_text = self.topicCategoryOb.getDelimitedStringFromList(self.topicCategoryOb.listVals)
243                                self.topicCategory_tsvector = self.topicCategory_text
244                               
245                        #self.topicCategory = self.getElementVal(self.isoModel.topicCategory())
246                        #self.topicCategory_text = self.topicCategory
247                        #self.topicCategory_tsvector = self.topicCategory
248                except:
249                        self.topicCategory = None
250                        self.topicCategory_text = None
251                        self.topicCategory_tsvector = None
252                       
253                '''
254                self.keywords = self.getElementVal(self.isoModel.keywords())
255               
256                self.keywordsList = self.listify(self.keywords)
257               
258                if self.keywords == 'None':
259                        logging.warn("No parameter info for record!")
260                        self.parameters_text = ''
261                        self.parameters_tsvector = ''
262                else:           
263                        self.parametersOb = IsoIngestListUtilities(self.keywords,True)                   
264                        self.parameters_text = self.parametersOb.getDelimitedStringFromList(self.parametersOb.listVals)
265                        self.parameters_tsvector = self.parameters_text
266                '''
267               
268                try:
269                        self.lineage = self.getElementVal(self.isoModel.lineage())
270                        self.lineage_text = self.lineage
271                        self.lineage_tsvector = self.lineage
272                except:
273                        self.lineage = None
274                        self.lineage_text = None
275                        self.lineage_tsvector = None
276               
277                try:
278                        self.publicAccessLimitations = self.getElementVal(self.isoModel.publicAccessLimitations())
279                        self.publicAccessLimitations_text = self.publicAccessLimitations
280                        self.publicAccessLimitations_tsvector = self.publicAccessLimitations
281                except:
282                        self.publicAccessLimitations = None
283                        self.publicAccessLimitations_text = None
284                        self.publicAccessLimitations_tsvector = None
285               
286               
287                try:
288                        self.dataOriginator= self.getElementVal(self.isoModel.dataOriginator())
289                                               
290                except:
291                        self.dataOriginator = None
292                        self.dataOriginator_text = None
293                        self.dataOriginator_tsvector = None
294                       
295                       
296                #check presence of originator - Mandatory!
297                if self.dataOriginator == 'None' or self.dataOriginator is None:
298                        self.processingMsg = 'No entry for Data Originator so record not valid and cannot/will not Ingest!'
299                        logging.error(self.processingMsg)
300                        self.validDoc = False
301               
302                else:                   
303                        self.dataOriginatorOb = IsoIngestListUtilities(self.dataOriginator,True)
304                       
305                        #add some extra specifications to deal with MEDIn requirement to have both searchable field (vector) and return as a text field
306                        self.dataOriginator_text = self.dataOriginatorOb.getDelimitedStringFromList(self.dataOriginatorOb.listVals)
307                        self.dataOriginator_tsvector = self.dataOriginator_text
308               
309                try:
310                        self.dataFormat = self.getElementVal(self.isoModel.originalFormat())
311                except:
312                        self.dataFormat = None
313               
314                try:
315                        self.metadataUpdateDate = self.getElementVal(self.isoModel.metadataUpdateDate())
316                except:
317                        self.metadataUpdateDate = None
318                       
319                       
320                try:
321                        self.originalFormatName = self.getElementVal(self.isoModel.originalFormat())
322                except:
323                        self.originalFormatName = None
324                       
325                try:
326                        self.originalFormatVersion = self.getElementVal(self.isoModel.originalFormatVersion())
327                except:
328                        self.originalFormatVersion = None
329                       
330               
331                logging.info("")
332                logging.info(" ****************** Completed rendering ISO xml into data structure! ****************** ")
333                logging.info("")
334               
335                return True
336       
337       
338        '''
339        Method to generate a simple list object from a compound list set
340        '''
341        def listify(self,listObject):
342               
343                list = []
344               
345                for outer in listObject:
346                        for inner in outer:                             
347                                list.append(inner)
348                                               
349                return list
350       
351        '''
352        Method to check date formats to ensure correct insertion into database - if doesnt match minimum YYYY-MM-DD format then is false,
353        so date should be set to None in calling method.  Will adjust self.processingMsg too for return info
354        '''
355        def checkDate(self,inputDate):
356               
357                if inputDate is None:
358                        return False
359               
360               
361                dateStrArr = inputDate.split('-')
362               
363                #check if long date string or short - if long, will have a T in! (should do..)
364                #just stick with date values now..
365                if 'T' in dateStrArr:
366                        logging.info("*********************************** LONG VALUE DATE STRING ALERT!")
367                        dateStrArr = dateStrArr[:3]
368                               
369                if len(dateStrArr)< 3:
370                        msg = "Invalid date format (%s) - insufficient length!\n" %inputDate
371                        logging.warn(msg)
372                        self.processingMsg = self.processingMsg + msg
373                        return False
374               
375                else:
376                        yearStr = dateStrArr[0]
377                        monthStr = dateStrArr[1]
378                        dayStr = dateStrArr[2]
379                               
380                #check days, months etc - Years can be anything... just do simple test for now
381                if int(monthStr) > 12 or int(monthStr) < 1:
382                        msg = "Invalid Month value (%s) \n" %monthStr
383                        logging.warn(msg)
384                        self.processingMsg = self.processingMsg + msg
385                        return False
386                               
387                elif monthStr in ['01','03','05','07','08','10','12']:
388                        if int(dayStr) > 31 or int(dayStr) < 1:
389                                msg = "(31 ck) Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
390                                logging.warn(msg)
391                                self.processingMsg = self.processingMsg + msg
392                                return False
393                        else:
394                                logging.info("(31 ck) Date value seems ok...(%s) " %inputDate)
395                                return True
396                       
397                elif monthStr in ['04','06','09','11']:
398                        if int(dayStr) > 30 or int(dayStr) < 1:
399                                msg = "(30 ck) Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
400                                logging.warn(msg)
401                                self.processingMsg = self.processingMsg + msg
402                                return False
403                        else:
404                                logging.info("(30 ck) Date value seems ok...(%s) " %inputDate)
405                                return True
406                                       
407                elif monthStr == '02':
408                        if int(yearStr) % 4 == 0:
409                                if int(dayStr) > 29 or int(dayStr) < 1:
410                                        msg = "Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
411                                        logging.warn(msg)
412                                        self.processingMsg = self.processingMsg + msg
413                                        return False
414                                else:
415                                        logging.info("(29 ck) Date value seems ok...(%s) " %inputDate)
416                                        return True
417                                                                       
418                        else:                           
419                                if int(dayStr) > 28 or int(dayStr) < 1:
420                                        msg = "Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
421                                        logging.warn(msg)
422                                        self.processingMsg = self.processingMsg + msg
423                                        return False
424                                else:
425                                        logging.info("(28 ck) Date value seems ok...(%s) " %inputDate)
426                                        return True                                                                     
427                               
428                else:
429                        msg = "Invalid month value (%s)" %monthStr
430                        logging.warn(msg)
431                        self.processingMsg = self.processingMsg + msg
432                        return False
433               
434        '''
435        Method to return start and end dates from a sequence of dates such as that found returned using the xpath defn from boundingDates
436        '''
437        def boundingDateRange(self,boundingDatesList):
438               
439                #generate a simple list of all dates
440                allDates = []
441                returnDates = {}
442               
443                       
444                #remember, need to sort out whether there was a start and end date in the first place, or just one so can copy values over
445                if len(boundingDatesList) == 1:
446                        logging.info("Only 1 set of start and stop dates, so setting range to this..")
447                        returnDates['start'] = boundingDatesList[0]['start']
448                        returnDates['end'] = boundingDatesList[0]['end']
449                       
450                       
451                       
452                       
453                        #check date validity
454                        for date in returnDates.keys():                                 
455                                if not self.checkDate(returnDates[date]):
456                                        returnDates[date] = 'None'
457                       
458                        #convert None to 'None'
459                        for date in returnDates.keys():
460                                if returnDates[date] is None:
461                                        returnDates[date] = 'None'
462                               
463                        return returnDates
464               
465                for outer in boundingDatesList:
466                        for inner in outer.keys():
467                                if outer[inner] is not None:
468                                        if outer[inner] != 'None':
469                                                #some other test here to ensure proper date object
470                                                allDates.append(outer[inner])
471                                               
472                               
473                #min and max seem to work for now (to get it working) - assuming proper dateformat (restrict to YYYY-MM-DD for now)
474                #TODO: use datetime library to do this properly
475               
476                #check correct formatting for dates - if invalid, set to None - use checkDate method           
477                for date in returnDates.keys():                 
478                        if not self.checkDate(returnDates[date]):
479                                returnDates[date] = 'None'
480                                       
481                returnDates['start'] = min(allDates)
482                returnDates['end'] = max(allDates)
483               
484               
485                return returnDates
486       
487       
488               
489        '''
490        Method to import specific ISO xpath definition class depending on the profile used
491        '''
492        def importISOxmlDefinition(self,format):
493               
494                '''
495                MEDIN Profile v2.3 (date? - MEDIN upgrade dev)
496                '''
497               
498                logging.info("Format chosen: " + format)
499               
500               
501               
502                #Dynamically import module of correct profile...
503                import_string = "from " + format + " import " + format + " as iso"
504               
505                try:
506                        exec import_string
507                except:
508                        logging.error("Could not import xpath class for: " + format)
509                        sys.exit()
510               
511                self.isoModel = iso()
512               
513               
514        '''
515        Method to govern the changing of an element value (in the first instance change a url to a NDG redirect URL
516       
517        NOTE: will only change Urls if changeUrl set to True - this method is also used to ready the xml for writing into db
518       
519        '''
520        def changeElementUrlVal(self,keyMethod,outputFile,changeUrl):
521               
522       
523                logging.info("******************************************************************************************")
524                logging.info("Extracting xpath information for data extraction type (to change URL..):" + keyMethod[0])
525                logging.info("******************************************************************************************")
526                               
527               
528                #first need to interpret the contents of the tuple we've been passed everything is a dictionary in the tuple apart from element 0:
529                #build a map of it - do this as a dictionary with the key as the position in the tuple and the value as the element val, be it str or dictionary key
530                dataStruct = self.interpretTuple(keyMethod)
531                cnt = 0
532               
533                #this is similar to getElementVal - but no interest in ordering or returning the actual value..
534               
535                #if changeUrl set to true, activate changes, just write xml as is if not
536                if changeUrl:           
537                        logging.info("changing urls now...")
538                        for i in dataStruct.keys()[1:]:
539                                               
540                                thisData = keyMethod[i][dataStruct[i]]
541                                                               
542                                #is dicionary a dependant value or straight xpath?
543                                if 'baseXpath' in thisData.keys():
544                                        logging.info("Extracting xml using COMPLEX dependant method for xpath def: " + str(cnt))                       
545                                        self.changeDependantURLElementVal(thisData['baseXpath'],thisData['elValXpath'],thisData['depValXpath'],thisData['depVal'])                             
546                                                               
547                                if 'xpath' in thisData.keys():         
548                                        logging.info("Extracting xml using SIMPLE method for xpath def: " + str(cnt))                                           
549                                        self.changeSimpleURLElementVal(thisData['xpath'])       
550                                       
551                else:
552                        logging.info("URL's not set to change!")
553                                                                               
554                #having returned the completed xml tree... write it to the OP file dictated..
555                try:
556                        #rewrite this back into the xml doc                             
557                        self.etree.write(outputFile)
558                        logging.info("Have successfully generated new ISO file with rewritten urls to NDG redirect format!")
559                       
560                        return True
561               
562                except:
563                        logging.error("Could not rewrite NDG url ISO xml to; " + outputFile)
564                       
565                        return False
566               
567               
568        '''
569        Method to change an ISO element val - simple xpath value
570        '''
571        def changeSimpleURLElementVal(self, xpath):
572               
573                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
574                xpathNS = self.appendNameSpace(xpath)           
575               
576                resElementVal = []
577               
578                try:   
579                        rootEl = self.root.findall(xpathNS)
580                       
581                except:
582                        logging.error("Could not find element for this xpath: " + xpath)                       
583                       
584                                               
585                #use a text 'None' rather than a None type                     
586                for elVal in rootEl:
587                        if elVal is None: 
588                                originalUrl = 'None' 
589                        else:
590                                try:                   
591                                        originalUrl = elVal.text       
592                                        redirectURL = self.redirectBaseURL + originalUrl + '&docID=' + urllib.quote(self.datasetID[0][0]) + '&docTitle=' + urllib.quote(self.datasetName[0][0])
593                               
594                                        #reassign...
595                                        elVal.text = redirectURL                                       
596                                        logging.info("Have successfuly rewritten ndg redirect URL: " + redirectURL)
597                                       
598                                except:
599                                        logging.error("Could not change original (simple) url!")
600               
601                       
602                #no need to return anything -if we rewrite the doc, any successful changes will be incorporated, otherwise will be ignored
603       
604        '''
605        Method to change an ISO element val - compound dependant xpath values
606        '''
607        def changeDependantURLElementVal(self,baseXpath,elXpath,depXpath,depValReqd):
608               
609                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
610                baseXpath = self.appendNameSpace(baseXpath)
611                               
612                #for path in baseXpaths:
613                try:                           
614                        rootEl = self.root.findall(baseXpath) #returns an elementree object of elements in a list                               
615                except:
616                        logging.error("Could not find element for this xpath: " + baseXpath)
617                        return 'None'
618                       
619                for el in rootEl:
620                        thisElXpth = self.appendNameSpace(elXpath)
621                                                               
622                        thisEl = self.doFindall(el,thisElXpth)
623                               
624                        #if there's a value for the actual xpath we want, go and get the dependant value
625                        if thisEl != 'None':
626                                       
627                                elVal = thisEl[0]
628                                elValTxt = elVal.text #NOTE take first one as we expect this to be the value we want
629                                                                       
630                                thisEldepXpth = self.appendNameSpace(depXpath)                                 
631                                thisDepEl = self.doFindall(el,thisEldepXpth)                                                           
632                                       
633                                if thisDepEl != 'None':
634                                                                               
635                                        depVal = thisDepEl[0].text
636                                                                       
637                                        if depVal == depValReqd:
638                                               
639                                                try:
640                                                        originalUrl = elValTxt                                         
641                                                        redirectURL = self.redirectBaseURL + originalUrl + '&docID=' + urllib.quote(self.datasetID[0][0]) + '&docTitle=' + urllib.quote(self.datasetName[0][0])
642                                                                                       
643                                                        #reassign...
644                                                        elVal.text = redirectURL                                                                                       
645                                                        logging.info("Have successfuly rewritten ndg redirect URL: " + redirectURL)
646                                                       
647                                                except:
648                                                        logging.error("Could not change original (complex) url!")
649                                       
650                                                                                                                       
651                #no need to return anything -if we rewrite the doc, any successful changes will be incorporated, otherwise will be ignored
652               
653       
654        '''
655        Method to initially interpret the xpath dictionary type passed
656        '''
657        def interpretTuple(self,keyMethod):
658               
659                dataStruct = {}
660                counter=0
661                for i in keyMethod:
662                        if type(i) is dict:
663                               
664                                #loop through the top level (remember its a dictionary of dictionaries!)
665                                for j in i.keys():                             
666                                        dataStruct[counter]=j
667                       
668                        #Only other data type present should be a string
669                        if type(i) is str:
670                                dataStruct[counter] = i
671                               
672                        counter = counter + 1
673                               
674                return dataStruct
675       
676               
677        '''
678        Method to aid passing and extraction info from complex xpath tuples
679       
680        Will work out whether complex or simple xpath extraction required depending on type of xpath method sent
681       
682        If 'order' key present then will return a dictionary of values with the extracted value using the specified key.
683       
684        '''
685        def getElementVal(self,keyMethod):
686               
687                #develop handler method to simplify
688                returnValList=[]                               
689                returnVal = 'None'
690               
691                logging.info("******************************************************************************************")
692                logging.info("Extracting xpath information for data extraction type:"  + keyMethod[0])
693                logging.info("******************************************************************************************")
694               
695                               
696                showValue = self.showXMLvalues
697               
698                #first need to interpret the contents of the tuple we've been passed everything is a dictionary in the tuple apart from element 0:
699                #build a map of it - do this as a dictionary with the key as the position in the tuple and the value as the element val, be it str or dictionary key
700                dataStruct = self.interpretTuple(keyMethod)
701                '''
702                dataStruct = {}
703                counter=0
704                for i in keyMethod:
705                        if type(i) is dict:
706                               
707                                #loop through the top level (remember its a dictionary of dictionaries!)
708                                for j in i.keys():                             
709                                        dataStruct[counter]=j
710                       
711                        #Only other data type present should be a string
712                        if type(i) is str:
713                                dataStruct[counter] = i
714                               
715                        counter = counter + 1
716                '''
717               
718                #now iterate through the key list in dataStruct but miss First one (as its a name val!)                 
719                valueList = []
720                ordering = False
721               
722                cnt = 1
723               
724                for i in dataStruct.keys()[1:]:
725                                               
726                        thisData = keyMethod[i][dataStruct[i]]
727                                                               
728                        #is dicionary a dependant value or straight xpath?
729                        if 'baseXpath' in thisData.keys():
730                                logging.info("Extracting xml using COMPLEX dependant method for xpath def: " + str(cnt))                       
731                                returnVal = self.returnDependantElementVal(thisData['baseXpath'],thisData['elValXpath'],thisData['depValXpath'],thisData['depVal'],showValue)                           
732                                valueList.append(returnVal)
733                                                               
734                        if 'xpath' in thisData.keys():         
735                                logging.info("Extracting xml using SIMPLE method for xpath def: " + str(cnt))                                           
736                                returnVal =  self.returnSimpleElementVal(thisData['xpath'],showValue)                                                           
737                                valueList.append(returnVal)
738                                                               
739                        #is there any ordering info required - if so, order the return list according to the ordering tuple
740                        if 'order' in keyMethod[i].keys():
741                                logging.info("Returning a dictionary of paired values as ordering requested for xpath (number of xpath defs MUST match ordering vals available")                       
742                                ordering = True                         
743                                orderingList = thisData
744                               
745                        else:
746                                logging.info("Returning a simple list of values for xpath def: " + str (cnt))
747                               
748                        cnt = cnt + 1
749                       
750                #check theres any data..
751                dataPresent=False
752                for values in valueList:
753                       
754                        #as valueList is a list of lists, length of valueList will be > 1 even if no data in sub-lists..
755                        #..so check through all subLists and if not data at all then can return None
756                        if len(values) > 0:
757                                dataPresent = True
758                               
759                if not dataPresent:                     
760                        logging.warning("No data to extract from the xml for %s" %keyMethod[0])
761                        return 'None'
762                       
763                #if ordering is required, create a corresponding dictionary of ordered values                                   
764                if ordering:
765                       
766                        #logging.INFO("Ordering data by specified order information!")
767                        logging.info("ordering information now!")
768                       
769                        '''FIRST need to group information together in correct order (i.e. elementtree groups all starts in one list, then all ends etc
770                        i.e. [[start1,start2],[end1,end2]] to [[start1,end1],[start2,end2]]
771                        '''
772                        #import pdb
773                        #pdb.set_trace()
774                        #check that number of number of elements in ordering list is not less than number of items in list to be ordered!
775                        #if multiple sets to be ordered, check numbers and return as dictionary
776                        checkCompLnth = len(valueList[0])
777                        for list in valueList:
778                                if len(list) != checkCompLnth:
779                                        logging.error("Sub component lists are of unequal length - CANNOT order!!")
780                                        return 'None'
781                               
782                        #outer loop should be the number of elements in the sublists..
783                        outer = []
784                        for localPos in range(0,checkCompLnth):
785                                inner=[]
786                                #inner loop should be the number of lists corresponding to repeating subelements                       
787                                for listPos in range(0,len(valueList)):
788                                       
789                                                inner.append(valueList[listPos][localPos])
790                                #append to outer loop
791                                outer.append(inner)                                                     
792                       
793                        for returnedList in outer:
794                               
795                                #create a disctionary for this round (will append it later to the return version)
796                                orderedValsSub = {}
797                                                                               
798                                if len(returnedList) != len(orderingList):
799                                        logging.error("Ordering List length does not match length of list to be ordered! (NOTE: can only order where lengths match!)")
800                                        returnValList.append('None')
801                               
802                                else:           
803                                        #go through valueList and extract values in correct position and build dictionary
804                                        for i in orderingList.keys():
805                                               
806                                                orderedValsSub[i] = returnedList[orderingList[i]-1] # remember there's an offset as '0' not used in these vals
807                               
808                               
809                                        returnValList.append(orderedValsSub)
810                       
811                        return returnValList
812                       
813                else:
814                        return valueList
815                       
816                       
817        '''
818        Method to return a list of values of an element value dependant on another element value held within the local tree
819        i.e. return a specific string val depending on a local code val in ISO
820       
821        if element exists and any dependant value present matches the expected value, the element value is returned as part of a list,
822        otherwise an empty list is returned
823        '''
824        def returnDependantElementVal(self,baseXpath,elXpath,depXpath,depValReqd,showValue=False):
825                       
826                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
827                baseXpath = self.appendNameSpace(baseXpath)
828               
829               
830                resDependantVal = []
831               
832                #for path in baseXpaths:
833                try:                           
834                        rootEl = self.root.findall(baseXpath) #returns an elementree object of elements in a list                               
835                except:
836                        logging.error("Could not find element for this xpath: " + baseXpath)
837                        return 'None'
838               
839               
840                for el in rootEl:
841                       
842                        thisElXpth = self.appendNameSpace(elXpath)                                             
843                        thisEl = self.doFindall(el,thisElXpth)
844                       
845                        #if there's a value for the actual xpath we want, go and get the dependant value
846                        if thisEl != 'None':
847                                       
848                                elVal = thisEl[0].text #NOTE take first one as we expect this to be the value we want
849                               
850                                #catch any nones here..
851                                #if elVal is None:
852                                #       import pdb
853                                #       pdb.set_trace()
854                                #       return 'None'
855                                                                       
856                                thisEldepXpth = self.appendNameSpace(depXpath)                                 
857                                thisDepEl = self.doFindall(el,thisEldepXpth)                                                           
858                               
859                                if thisDepEl != 'None':
860                                                                               
861                                        depVal = thisDepEl[0].text
862                                                                       
863                                        if depVal == depValReqd:
864                                               
865                                                if showValue is True:
866                                                        logging.info("")
867                                                        logging.info("          Element Value: " + elVal)
868                                                        logging.info("")
869                                               
870                                                resDependantVal.append(elVal)
871                                               
872                                                                                                                                               
873                return resDependantVal
874       
875        '''
876        Method to extract a value from the doc using a simple xpath.  If no element found or no data present then None is returned
877        '''
878        def returnSimpleElementVal(self,xpath,showValue=False):
879               
880                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
881                xpathNS = self.appendNameSpace(xpath)           
882               
883                resElementVal = []
884               
885                try:   
886                        rootEl = self.root.findall(xpathNS)
887                       
888                except:
889                        logging.error("Could not find element for this xpath: " + xpath)                       
890                        return ['None']
891                                               
892                #use a text 'None' rather than a None type
893               
894                for elVal in rootEl:
895                        if elVal is None:
896                                resElementVal.append('None')
897                        else:
898                                value = elVal.text
899                                if (showValue is True) and (value is not None):
900                                        logging.info("")
901                                        logging.info("          Element Value: " + value)
902                                        logging.info("")
903                                       
904                                resElementVal.append(value)
905                '''     
906                if rootEl[0].text is None:                                     
907                        resElementVal.append('None')
908                else:
909                        for el in rootEl:                                       
910                                resElementVal.append(el.text)   
911                               
912                                       
913                '''
914               
915                return resElementVal
916                               
917       
918        '''
919        Method to run an elementtree findall on ns appended path in the supplied element and return list
920        returns None if nothing found
921        '''
922        def doFindall(self,el,thisElXpth):
923                               
924                try:
925                        thisElXpthEl = el.findall(thisElXpth)
926                       
927                        if len(thisElXpthEl) == 0:
928                                thisElXpthEl = 'None'
929                except:
930                        thisElXpthEl = 'None'
931               
932               
933                return thisElXpthEl
934               
935               
936        '''
937        Method to extract actual value from xml doc - expects a list of namespace qualified xpaths and will return corresponding list of values
938        '''
939        def getXmlVal(self,paths):
940               
941                xmlVals = []
942                               
943                for path in paths:
944                        try:
945                                xmlVals.append(self.root.find(path).text)                       
946                        except:
947                                logging.error("Could not extract value for xpath: " + path)
948                                xmlVals.append('null')
949                               
950                return xmlVals                                 
951               
952        '''
953        Method to hold a dictionary linking namespace prefixes to URLs
954        '''
955        def isoNameSpaces(self):
956               
957                isoNs = {'gmd':'http://www.isotc211.org/2005/gmd','gco':'http://www.isotc211.org/2005/gco',
958                                'gmx':'http://www.isotc211.org/2005/gmx','gml':'http://www.opengis.net/gml/3.2',
959                                'none':'','gts':'http://www.isotc211.org/2005/gts','gsr':'http://www.isotc211.org/2005/gsr',
960                                'gss':'http://www.isotc211.org/2005/gss'
961                                }
962               
963                return isoNs
964       
965       
966        '''
967        Method to return DEFAULT namespace in elementtree format of ISO profile used (i.e. should be gmd)
968        (to deal with those elements with no prefix)
969        '''
970        def defaultIsoNamespace(self):
971                return 'gmd'
972               
973       
974       
975        '''
976        Method to extract root element of XML file specified using elementtree
977        '''     
978        def getIsoXML(self,file):
979               
980                logging.info("Getting xml root element")
981               
982                #parse the xml with elementtree
983                try:
984                        self.etree=ET.parse(file)
985                        root=self.etree.getroot() # should be the "gmd:MD_Metadata" element
986                except:
987                        logging.error("File %s appears to be bad XML!" %file)
988                        return None
989               
990                #avoid ns0 and ns1 etc etc namespaces when rewriting this ET
991                logging.info("Sorting out namespaces so can properly rewrite ISO xml..")
992                for ns in self.isoNameSpaces().keys():         
993                        if ns != 'None':                                               
994                                ET._namespace_map[self.isoNameSpaces()[ns]] = ns
995                               
996                #check root element is an ISO one - use elementtree appended namespace..
997                if root.tag != '{http://www.isotc211.org/2005/gmd}MD_Metadata':
998                        logging.error("XML document does not appear to be ISO (not gmd:MD_Metadata)!!")                 
999                        return None
1000       
1001                return root
1002               
1003               
1004        '''
1005        Method to convert the given xpath list to a namespace abbreviated version so elementtree can handle it (grrr.). 
1006        Will return a list of corresponding namespace qualified xpaths - if errors a 'null' will be placed.
1007        '''
1008        def appendNameSpaceList(self,paths):
1009               
1010                nameSpaceAppendedPaths = []
1011               
1012               
1013                for path in paths:             
1014                       
1015                        pathElements = path.split('/')
1016                       
1017                        #note due to crappy elementtree have to avoid initial "/"
1018                        count = 0
1019                        for element in pathElements:
1020                               
1021                                try:                                   
1022                                        if ':' in element:                                             
1023                                                splitElement = element.split(':')
1024                                                nsPrefix,nsElement = splitElement[0],splitElement[1]
1025                                        else:
1026                                                #use default namespace                                         
1027                                                nsPrefix = self.defaultIsoNamespace()
1028                                                nsElement = element
1029                                       
1030                                        if count == 0:
1031                                               
1032                                                #appendedPath = self.returnNS() + element
1033                                                appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1034                                        else:
1035                                               
1036                                                appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1037                                       
1038                                        count += 1
1039                                except:
1040                                        appendedPath = 'null'
1041                                        logging.error("Could not change to elementtree ns xpath")
1042                                                       
1043                        #clear up any blank namespace prefixes
1044                        appendedPath = appendedPath.replace('{}','')
1045                       
1046                        nameSpaceAppendedPaths.append(appendedPath)
1047
1048                return nameSpaceAppendedPaths
1049       
1050        '''
1051        Method to convert the given xpath to a namespace abbreviated version so elementtree can handle it (grrr.). 
1052        Will return an equivalent namespace qualified xpath - if errors a 'null' will be placed.
1053        '''
1054        def appendNameSpace(self,path):
1055               
1056                nameSpaceAppendedPath = ''
1057               
1058                pathElements = path.split('/')
1059                       
1060                #note due to crappy elementtree have to avoid initial "/"
1061                count = 0
1062                for element in pathElements:
1063                               
1064                        try:                                   
1065                                if ':' in element:                                             
1066                                        splitElement = element.split(':')
1067                                        nsPrefix,nsElement = splitElement[0],splitElement[1]
1068                                else:
1069                                        #use default namespace                                         
1070                                        nsPrefix = self.defaultIsoNamespace()
1071                                        nsElement = element
1072                                       
1073                                if count == 0:
1074                                               
1075                                        #appendedPath = self.returnNS() + element
1076                                        appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1077                                else:                                           
1078                                        appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1079                                       
1080                                count += 1
1081                        except:
1082                                appendedPath = 'null'
1083                                logging.error("Could not change to elementtree ns xpath")
1084                                                       
1085                #clear up any blank namespace prefixes
1086                nameSpaceAppendedPath = appendedPath.replace('{}','')
1087               
1088                return nameSpaceAppendedPath
Note: See TracBrowser for help on using the repository browser.