source: TI01-discovery/branches/ingestion-MEDIN/ingestAutomation-upgrade/OAIBatch/ExtractISO.py @ 6363

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI01-discovery/branches/ingestion-MEDIN/ingestAutomation-upgrade/OAIBatch/ExtractISO.py@6363
Revision 6363, 14.8 KB checked in by sdonegan, 12 years ago (diff)

added method to return start and end dates - useful if multiple date elements used

Line 
1
2from xml.etree import ElementTree as ET
3import logging,urllib,os,sys
4
5class ExtractISO:
6
7        def __init__(self,filenameIP,isoFormat):                               
8                               
9                self.inputXMLfile = filenameIP
10                self.isoFormatToExtract = isoFormat
11               
12                logging.info("Have initialised ISO XML Extractor")
13               
14                #self.createISOdataStructure()
15               
16        '''
17        Simple method to allow return information on success of extraction ( __init__ method cannot return anything so use this method)
18        '''
19        def createISOdataStructure(self):
20               
21                logging.info("")
22                logging.info("****************** Creating ISO data structure from " + self.inputXMLfile + "****************** ")
23                logging.info("")
24               
25                self.root = self.getIsoXML(self.inputXMLfile)           
26               
27                #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')
28               
29                if self.root is None:
30                        logging.error("Detected possible problem with input xml - cannot parse it!")
31                        return False
32               
33                #choose the ISO xpath definition class depending on format profile required
34                self.importISOxmlDefinition(self.isoFormatToExtract)
35                                       
36                #get all the required information from the chosen xpath definition class
37                self.datasetID = self.getElementVal(self.isoModel.datasetID())
38               
39                self.revisionDate = self.getElementVal(self.isoModel.metadataRevisionDate())
40               
41                self.createDate = self.getElementVal(self.isoModel.metadataCreationDate())
42               
43                self.datasetName = self.getElementVal(self.isoModel.dataSetName())             
44               
45                self.boundingDates = self.getElementVal(self.isoModel.boundingDates())
46               
47                self.boundingDatesRange = self.boundingDateRange(self.boundingDates)   
48               
49                self.originalFormat = self.getElementVal(self.isoModel.originalFormat())
50               
51                self.authors = self.getElementVal(self.isoModel.authors())
52                               
53                self.datacentreName = self.getElementVal(self.isoModel.dataCentreName())
54               
55                self.parameters = self.getElementVal(self.isoModel.parameters()) 
56               
57                self.keywords = self.getElementVal(self.isoModel.keywords())
58               
59                self.boundingBoxCoordinates = self.getElementVal(self.isoModel.coordinates())
60               
61                logging.info("")
62                logging.info(" ****************** Completed rendering ISO xml into data structure! ****************** ")
63                logging.info("")
64               
65                return True
66       
67               
68        '''
69        Method to return start and end dates from a sequence of dates such as that found returned using the xpath defn from boundingDates
70        '''
71        def boundingDateRange(self,boundingDatesList):
72               
73                #generate a simple list of all dates
74                allDates = []
75                returnDates = {}
76               
77                for outer in boundingDatesList:
78                        for inner in outer.keys():
79                                if outer[inner] is not None:
80                                        if outer[inner] != 'None':
81                                                #some other test here to ensure proper date object
82                                                allDates.append(outer[inner])
83
84                #min and max seem to work for now (to get it working) - assuming proper dateformat (restrict to YYYY-MM-DD for now)
85                #TODO: use datetime library to do this properly         
86                returnDates['start'] = min(allDates)
87                returnDates['end'] = max(allDates)
88               
89                return returnDates
90       
91       
92               
93        '''
94        Method to import specific ISO xpath definition class depending on the profile used
95        '''
96        def importISOxmlDefinition(self,format):
97               
98                '''
99                MEDIN Profile v2.3 (date? - MEDIN upgrade dev)
100                '''
101               
102                logging.info("Format chosen: " + format)
103               
104                if format == 'MEDINv2.3':
105                       
106                        from difConvertedto_ISO19139 import difConvertedto_ISO19139 as dif2iso
107                       
108                '''
109                "Stub" iso profile based on MEDIN profile - non complete ISO from GCMD DIF metadata converted via NDG xquery (v?)
110                '''     
111                if format == 'dif2stubIso':
112                       
113                        from difConvertedto_ISO19139 import difConvertedto_ISO19139 as dif2iso
114                       
115                '''
116                Other ISO profiles to support: NERC Discovery; CEH ISO etc etc, WPCC
117                '''
118
119                self.isoModel = dif2iso()
120               
121               
122               
123        '''
124        Method to aid passing and extraction info from complex xpath tuples
125       
126        Will work out whether complex or simple xpath extraction required depending on type of xpath method sent
127       
128        If 'order' key present then will return a dictionary of values with the extracted value using the specified key.
129       
130        '''
131        def getElementVal(self,keyMethod):
132               
133                #develop handler method to simplify
134                returnValList=[]                               
135                returnVal = 'None'
136               
137                logging.info("******************************************************************************************")
138                logging.info("Extracting xpath information for data extraction type:" + keyMethod[0])
139                logging.info("******************************************************************************************")
140                               
141               
142                #first need to interpret the contents of the tuple we've been passed everything is a dictionary in the tuple apart from element 0:
143                #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
144                dataStruct = {}
145                counter=0
146                for i in keyMethod:
147                        if type(i) is dict:
148                               
149                                #loop through the top level (remember its a dictionary of dictionaries!)
150                                for j in i.keys():                             
151                                        dataStruct[counter]=j
152                       
153                        #Only other data type present should be a string
154                        if type(i) is str:
155                                dataStruct[counter] = i
156                               
157                        counter = counter + 1
158               
159               
160                #now iterate through the key list in dataStruct but miss First one (as its a name val!)                 
161                valueList = []
162                ordering = False
163                cnt = 1
164               
165                for i in dataStruct.keys()[1:]:
166                                               
167                        thisData = keyMethod[i][dataStruct[i]]
168                                                               
169                        #is dicionary a dependant value or straight xpath?
170                        if 'baseXpath' in thisData.keys():
171                                logging.info("Extracting xml using COMPLEX dependant method for xpath def: " + str(cnt))                       
172                                returnVal = self.returnDependantElementVal(thisData['baseXpath'],thisData['elValXpath'],thisData['depValXpath'],thisData['depVal'])                             
173                                valueList.append(returnVal)
174                                                               
175                        if 'xpath' in thisData.keys():         
176                                logging.info("Extracting xml using SIMPLE method for xpath def: " + str(cnt))                                           
177                                returnVal =  self.returnSimpleElementVal(thisData['xpath'])                                                             
178                                valueList.append(returnVal)
179                                                               
180                        #is there any ordering info required - if so, order the return list according to the ordering tuple
181                        if 'order' in keyMethod[i].keys():
182                                logging.info("Returning a dictionary of paired values as ordering requested for xpath (number of xpath defs MUST match ordering vals available")                       
183                                ordering = True                         
184                                orderingList = thisData
185                               
186                        else:
187                                logging.info("Returning a simple list of values for xpath def: " + str (cnt))
188                               
189                        cnt = cnt + 1
190                       
191                #if ordering is required, create a corresponding dictionary of ordered values                                   
192                if ordering:
193                       
194                        #logging.INFO("Ordering data by specified order information!")
195                        logging.info("ordering information now!")
196                       
197                        '''FIRST need to group information together in correct order (i.e. elementtree groups all starts in one list, then all ends etc
198                        i.e. [[start1,start2],[end1,end2]] to [[start1,end1],[start2,end2]]
199                        '''
200                       
201                        #check that number of number of elements in ordering list is not less than number of items in list to be ordered!
202                        #if multiple sets to be ordered, check numbers and return as dictionary
203                        checkCompLnth = len(valueList[0])
204                        for list in valueList:
205                                if len(list) != checkCompLnth:
206                                        logging.error("Sub component lists are of unequal length - CANNOT order!!")
207                                        return 'None'
208                               
209                        #outer loop should be the number of elements in the sublists..
210                        outer = []
211                        for localPos in range(0,checkCompLnth):
212                                inner=[]
213                                #inner loop should be the number of lists corresponding to repeating subelements                       
214                                for listPos in range(0,len(valueList)):
215                                       
216                                                inner.append(valueList[listPos][localPos])
217                                #append to outer loop
218                                outer.append(inner)                                                     
219                       
220                        for returnedList in outer:
221                               
222                                #create a disctionary for this round (will append it later to the return version)
223                                orderedValsSub = {}
224                                                                               
225                                if len(returnedList) != len(orderingList):
226                                        logging.error("Ordering List length does not match length of list to be ordered! (NOTE: can only order where lengths match!)")
227                                        returnValList.append('None')
228                               
229                                else:           
230                                        #go through valueList and extract values in correct position and build dictionary
231                                        for i in orderingList.keys():
232                                               
233                                                orderedValsSub[i] = returnedList[orderingList[i]-1] # remember there's an offset as '0' not used in these vals
234                               
235                               
236                                        returnValList.append(orderedValsSub)
237                       
238                        return returnValList
239                       
240                else:
241                        return valueList
242                       
243                       
244        '''
245        Method to return a list of values of an element value dependant on another element value held within the local tree
246        i.e. return a specific string val depending on a local code val in ISO
247       
248        if element exists and any dependant value present matches the expected value, the element value is returned as part of a list,
249        otherwise an empty list is returned
250        '''
251        def returnDependantElementVal(self,baseXpath,elXpath,depXpath,depValReqd):
252                       
253                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
254                baseXpath = self.appendNameSpace(baseXpath)
255               
256                resDependantVal = []
257               
258                #for path in baseXpaths:
259                try:                           
260                        rootEl = self.root.findall(baseXpath) #returns an elementree object of elements in a list                               
261                except:
262                        logging.error("Could not find element for this xpath: " + baseXpath)
263                        return 'None'
264                       
265                for el in rootEl:
266                        thisElXpth = self.appendNameSpace(elXpath)
267                                                               
268                        thisEl = self.doFindall(el,thisElXpth)
269                               
270                        #if there's a value for the actual xpath we want, go and get the dependant value
271                        if thisEl != 'None':
272                                       
273                                elVal = thisEl[0].text #NOTE take first one as we expect this to be the value we want
274                                                                       
275                                thisEldepXpth = self.appendNameSpace(depXpath)                                 
276                                thisDepEl = self.doFindall(el,thisEldepXpth)                                                           
277                                       
278                                if thisDepEl != 'None':
279                                                                               
280                                        depVal = thisDepEl[0].text
281                                                                       
282                                        if depVal == depValReqd:
283                                                resDependantVal.append(elVal)
284                                                                                                                                                       
285                return resDependantVal
286       
287        '''
288        Method to extract a value from the doc using a simple xpath.  If no element found or no data present then None is returned
289        '''
290        def returnSimpleElementVal(self,xpath):
291               
292                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
293                xpathNS = self.appendNameSpace(xpath)           
294               
295
296                resElementVal = []
297               
298                try:   
299                        rootEl = self.root.findall(xpathNS)
300                       
301                except:
302                        logging.error("Could not find element for this xpath: " + xpath)                       
303                        return ['None']
304                                               
305                #use a text 'None' rather than a None type
306               
307                for elVal in rootEl:
308                        if elVal is None:
309                                resElementVal.append('None')
310                        else:
311                                resElementVal.append(elVal.text)
312                '''     
313                if rootEl[0].text is None:                                     
314                        resElementVal.append('None')
315                else:
316                        for el in rootEl:                                       
317                                resElementVal.append(el.text)           
318                '''
319                return resElementVal
320                               
321       
322        '''
323        Method to run an elementtree findall on ns appended path in the supplied element and return list
324        returns None if nothing found
325        '''
326        def doFindall(self,el,thisElXpth):
327                               
328                try:
329                        thisElXpthEl = el.findall(thisElXpth)
330                       
331                        if len(thisElXpthEl) == 0:
332                                thisElXpthEl = 'None'
333                except:
334                        thisElXpthEl = 'None'
335               
336               
337                return thisElXpthEl
338               
339               
340        '''
341        Method to extract actual value from xml doc - expects a list of namespace qualified xpaths and will return corresponding list of values
342        '''
343        def getXmlVal(self,paths):
344               
345                xmlVals = []
346                               
347                for path in paths:
348                        try:
349                                xmlVals.append(self.root.find(path).text)                       
350                        except:
351                                logging.error("Could not extract value for xpath: " + path)
352                                xmlVals.append('null')
353                               
354                return xmlVals                                 
355               
356        '''
357        Method to hold a dictionary linking namespace prefixes to URLs
358        '''
359        def isoNameSpaces(self):
360               
361                isoNs = {'gmd':'http://www.isotc211.org/2005/gmd','gco':'http://www.isotc211.org/2005/gco',
362                                'gmx':'http://www.isotc211.org/2005/gmx','gml':'http://www.opengis.net/gml/3.2',
363                                'none':''
364                                }
365               
366                return isoNs
367       
368       
369        '''
370        Method to return DEFAULT namespace in elementtree format of ISO profile used (i.e. should be gmd)
371        (to deal with those elements with no prefix)
372        '''
373        def defaultIsoNamespace(self):
374                return 'gmd'
375               
376       
377       
378        '''
379        Method to extract root element of XML file specified using elementtree
380        '''     
381        def getIsoXML(self,file):
382               
383                logging.info("Getting xml root element")
384               
385                #parse the xml with elementtree
386                etree=ET.parse(file)
387                root=etree.getroot() # should be the "gmd:MD_Metadata" element
388               
389                #check root element is an ISO one - use elementtree appended namespace..
390                if root.tag != '{http://www.isotc211.org/2005/gmd}MD_Metadata':
391                        logging.error("XML document does not appear to be ISO (not gmd:MD_Metadata)!!")                 
392                        return None
393                               
394                return root
395               
396               
397        '''
398        Method to convert the given xpath list to a namespace abbreviated version so elementtree can handle it (grrr.). 
399        Will return a list of corresponding namespace qualified xpaths - if errors a 'null' will be placed.
400        '''
401        def appendNameSpaceList(self,paths):
402               
403                nameSpaceAppendedPaths = []
404               
405               
406                for path in paths:             
407                       
408                        pathElements = path.split('/')
409                       
410                        #note due to crappy elementtree have to avoid initial "/"
411                        count = 0
412                        for element in pathElements:
413                               
414                                try:                                   
415                                        if ':' in element:                                             
416                                                splitElement = element.split(':')
417                                                nsPrefix,nsElement = splitElement[0],splitElement[1]
418                                        else:
419                                                #use default namespace                                         
420                                                nsPrefix = self.defaultIsoNamespace()
421                                                nsElement = element
422                                       
423                                        if count == 0:
424                                               
425                                                #appendedPath = self.returnNS() + element
426                                                appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
427                                        else:
428                                               
429                                                appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
430                                       
431                                        count += 1
432                                except:
433                                        appendedPath = 'null'
434                                        logging.error("Could not change to elementtree ns xpath")
435                                                       
436                        #clear up any blank namespace prefixes
437                        appendedPath = appendedPath.replace('{}','')
438                       
439                        nameSpaceAppendedPaths.append(appendedPath)
440
441                return nameSpaceAppendedPaths
442       
443        '''
444        Method to convert the given xpath to a namespace abbreviated version so elementtree can handle it (grrr.). 
445        Will return an equivalent namespace qualified xpath - if errors a 'null' will be placed.
446        '''
447        def appendNameSpace(self,path):
448               
449                nameSpaceAppendedPath = ''
450               
451                pathElements = path.split('/')
452                       
453                #note due to crappy elementtree have to avoid initial "/"
454                count = 0
455                for element in pathElements:
456                               
457                        try:                                   
458                                if ':' in element:                                             
459                                        splitElement = element.split(':')
460                                        nsPrefix,nsElement = splitElement[0],splitElement[1]
461                                else:
462                                        #use default namespace                                         
463                                        nsPrefix = self.defaultIsoNamespace()
464                                        nsElement = element
465                                       
466                                if count == 0:
467                                               
468                                        #appendedPath = self.returnNS() + element
469                                        appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
470                                else:                                           
471                                        appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
472                                       
473                                count += 1
474                        except:
475                                appendedPath = 'null'
476                                logging.error("Could not change to elementtree ns xpath")
477                                                       
478                #clear up any blank namespace prefixes
479                nameSpaceAppendedPath = appendedPath.replace('{}','')
480               
481                return nameSpaceAppendedPath
Note: See TracBrowser for help on using the repository browser.