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

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

Upgrade a set of bug fixes to deal with authors, upgrade stats, reporting and deletion of records. Also includes update to Utilities.py for methods to provide string representation of the ISO data object doubledictionary

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