source: TI01-discovery-Ingest/trunk/v4n_MEDIN/ingestAutomation-upgrade/OAIBatch/ExtractISO.py @ 6904

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

further debug updates to check dates

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