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

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

simple bug fixes

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