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

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

Fixed some content bugs and handling of keywords/parameters as well as improve reporting

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