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

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

Initial classes for creating a data object to aid ingestion of ISO metadata records. difConvertedtoISO19139.py is the xpath definition class for the results of dif2iso xqueries. testIso.py is a test class to check output

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