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

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

Updated so can handle multiple ordered elements - testIso.py updated to show all information

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