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

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

added method to extract datasetID ..doh

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