source: TI01-discovery-Ingest/trunk/v4.3.0/ingestAutomation-upgrade/OAIBatch/ExtractISO.py @ 8026

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

Various updates to improve reporting and utf-8 handling - note requires ElementTree 1.3 or higher

Line 
1
2#from xml.etree import ElementTree as ET - SJD - not using the standard python ET anymore - use latest version!
3from elementtree import ElementTree as ET
4import logging,urllib,os,sys,inspect,string
5from Utilities import IsoIngestListUtilities # so can use those useful getSingleVal and getMultipleVal methods for discerning lists etc (esp useful when using the isoVal to dbColumn methods..)
6import ndg.common.src.lib.utilities as ndgUtilities
7import random
8import ndg.common.src.lib.fileutilities as FileUtilities
9
10
11'''
12ExtractISO handler class designed to deal with NERC DMS series of ISO profile metadata (for NERC Revitilisation work)
13'''
14error=''
15message=''     
16
17class ExtractISO:
18
19        def __init__(self,filenameIP,isoFormat):                               
20                               
21                self.inputXMLfile = filenameIP
22                self.isoFormatToExtract = isoFormat
23               
24                #show actual values extracted from the XML in logging.info
25                self.showXMLvalues = True
26               
27                logging.info("Have initialised ISO XML Extractor")
28               
29                #self.createISOdataStructure()
30               
31               
32        '''
33        Method to control re-writing of ISO xml with NDG redirect URL's
34        '''
35        def generateNDGredirectURL(self, ndgRedirectURL, xmlOutputFile, changeUrl):
36               
37                logging.info("Generating XML copy with URLS converted to NDG redirect URL's")
38               
39                self.redirectBaseURL = ndgRedirectURL
40               
41                rewriteIsoXml = self.changeElementUrlVal(self.isoModel.urlsToChange(), xmlOutputFile, changeUrl)
42                               
43                return rewriteIsoXml
44       
45       
46       
47        '''
48        Method to map ISO variables to Discovery Database columns - use in PostgresDAO.py with getattr!
49       
50        returns a dictionary where key is
51    '''
52        def mapIsoFieldsToDBcolumns(self):
53               
54                return {'originalFormat':'original_format_name', \
55        'originalFormatVersion':'original_format_version',\
56        'datasetAbstract':'dataset_abstract', \
57        'resourceType_text':'resource_type', \
58        'resourceType_tsvector':'resource_type_ts_vector', \
59        'topicCategory_text':'topic_category', \
60        'topicCategory_tsvector':'topic_category_ts_vector', \
61        'lineage_text':'lineage', \
62        'lineage_tsvector':'lineage_ts_vector', \
63        'publicAccessLimitations_text':'limitations_public_access', \
64        'publicAccessLimitations_tsvector':'limitations_public_access_ts_vector', \
65        'dataOriginator_text':'data_originator',\
66        'dataOriginator_tsvector':'data_originator_tsvector',\
67        'metadataUpdateDate':'dataset_metadata_update_date', \
68        'dataFormat':'original_format_name',
69        'createDate':'dataset_metadata_creation_date',
70        'publicationDate':'dataset_metadata_publication_date',
71        'authors_text':'authors','parameters_text':'parameters',
72        'dataFormats_text':'data_formats','dataFormats_ts_vector':'data_formats_tsvector', \
73        'resourceLocatorBool':'resource_locator','inspireThemes_text':'inspire_themes','inspireThemes_tsvector':'inspire_themes_ts_vector', \
74        'verticalExtent_text':'vertical_extent','verticalExtent_tsvector':'vertical_extent_ts_vector'
75        }
76               
77               
78        '''
79                Method to match the columns found in mapIsoFieldsToDBcolumns to the postgres data types
80        '''
81        def mapDBcolumnsToIsoDataTypes(self):
82               
83                return {'original_format_name':'text', \
84        'original_format_version':'text',\
85        'dataset_abstract':'text', \
86        'resource_type':'text', \
87        'resource_type_ts_vector':'tsvector', \
88        'topic_category':'text', \
89        'topic_category_ts_vector':'tsvector', \
90        'lineage':'text', \
91        'limitations_public_access':'text', \
92        'lineage_ts_vector':'tsvector', \
93        'limitations_public_access_ts_vector':'tsvector', \
94        'data_originator':'text',\
95        'data_originator_tsvector':'tsvector',\
96        'dataset_metadata_update_date':'timestamp', \
97        'original_format_name':'text',
98        'dataset_metadata_creation_date':'timestamp',
99        'dataset_metadata_publication_date':'timestamp',
100        'authors':'text','parameters':'text',
101        'data_formats':'text','data_formats_tsvector':'tsvector', \
102        'resource_locator':'boolean','inspire_themes':'text','inspire_themes_ts_vector':'tsvector', \
103        'vertical_extent':'text','vertical_extent_ts_vector':'tsvector'
104        }
105       
106               
107        '''
108        Simple method to allow return information on success of extraction ( __init__ method cannot return anything so use this method)
109        '''
110        def createISOdataStructure(self):
111               
112                logging.info("")
113                logging.info("********************************************************************************************************************************************")
114                logging.info("****************** Creating ISO data structure from " + self.inputXMLfile + "****************** ")
115                logging.info("********************************************************************************************************************************************")
116                logging.info("")
117               
118                #set processing message and processing docs
119                self.processingMsg = ''
120                self.validDoc = True
121               
122                self.stopHere = False # aid debuging!
123               
124                self.root = self.getIsoXML(self.inputXMLfile)
125               
126                self.isoFileLocation = self.inputXMLfile       
127               
128                #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')
129               
130                if self.root is None:
131                        logging.error("Detected possible problem with input xml - cannot parse it!")
132                        self.processingMsg = 'Problem with the XML structure!'
133                        self.validDoc = False
134                        return False
135               
136                #choose the ISO xpath definition class depending on format profile required
137                self.importISOxmlDefinition(self.isoFormatToExtract)
138                                                       
139                #get all the required information from the chosen xpath definition class
140               
141                #first - work out what type of resource we are ingesting .. assume dataset             
142                try:                   
143                        self.resourceType = self.getElementVal(self.isoModel.resourceType())[0][0]                     
144                        self.resourceType_text = self.resourceType
145                        self.resourceType_tsvector = self.resourceType
146                except:
147                        self.resourceType = None
148                        self.resourceType_text = None
149                        self.resourceType_tsvector = None
150               
151                #if dataset is a dataset type get id from usual place, if service use fileIdentifier..
152                self.datasetID = 'None'
153                if self.resourceType == 'dataset' or self.resourceType=='series':               
154                        self.datasetID = self.getElementVal(self.isoModel.datasetID()) 
155                elif self.resourceType == 'service':
156                        logging.info("Ingesting a service metadata record!")
157                        #self.datasetID = self.getElementVal(self.isoModel.fileIdentifier())
158                       
159                        #NERC not yet accepting service docs so filter them out here!
160                        self.processingMsg = 'ResourceType is SERVICE- not currently accepted into the NERC DWS!'
161                        logging.error(self.processingMsg)
162                        self.validDoc = False
163               
164                if self.datasetID == 'None':
165                        self.processingMsg = 'No entry for Dataset ID so record not valid and cannot/will not Ingest!'
166                        logging.error(self.processingMsg)
167                       
168                        #to aid proper filename reporting must give it a quasi id as will give "None" filename!
169                        id = [str(random.randrange(1000,20000,3)) + '_ingestIempID'] # remember must be in that double list format!
170                        logging.info("Using random integer: %s for ID to complete reporting process..." %id)
171                        self.datasetID = [id]
172                        self.validDoc = False
173               
174                else:
175                       
176                        #noticed some MEDIN centres allowing space characters in a unique id.. change to single underscore! (remember only 1 allowed!)
177                        self.datasetID[self.findTheListData(self.datasetID)][0] = self.datasetID[self.findTheListData(self.datasetID)][0].replace(" ", "_")
178               
179                #self.stopHere = True
180                self.datasetName = self.getElementVal(self.isoModel.dataSetName())
181                if self.datasetName == 'None':
182                        self.processingMsg = 'No entry for Dataset Name so record not valid and cannot/will not Ingest!'
183                        logging.error(self.processingMsg)
184                        self.validDoc = False
185               
186                self.datasetAbstract = self.getElementVal(self.isoModel.dataSetAbstract())             
187               
188                self.revisionDate = self.getElementVal(self.isoModel.metadataRevisionDate())
189               
190                #BODC using timestamp, so must isolate whatever value is available             
191                self.singleRevisionDate = self.revisionDate[self.findTheListData(self.revisionDate)][0]
192               
193                self.createDate = self.getElementVal(self.isoModel.metadataCreationDate())
194                               
195                self.datasetName = self.getElementVal(self.isoModel.dataSetName())
196                                               
197                self.boundingDates = self.getElementVal(self.isoModel.boundingDates())
198               
199                self.boundingDatesRange = self.boundingDateRange(self.boundingDates)           
200                                       
201                self.originalFormat = self.getElementVal(self.isoModel.originalFormat())
202                               
203                try:
204                        self.dataFormats = self.getElementVal(self.isoModel.dataFormats())
205                except:
206                        self.dataFormats = 'None'
207                               
208                if self.dataFormats != 'None':
209                       
210                        self.dataFormatsOb = IsoIngestListUtilities(self.dataFormats,True)
211                        self.dataFormats_text = self.dataFormatsOb.getDelimitedStringFromList(self.dataFormatsOb.listVals)
212                       
213                        #will be nothing in there due to way we've caught NoneTypes in Utilities.py - so set to 'None' here so can be nulled in sql
214                        if len(self.dataFormats_text) == 0:
215                                self.dataFormats_text = 'None'
216                       
217                        self.dataFormats_ts_vector = self.dataFormats_text
218                                               
219                else:
220                        self.dataFormats_text = 'None'
221                        self.dataFormats_ts_vector = 'None'
222                       
223                self.authors = self.getElementVal(self.isoModel.authors())
224               
225                if self.authors == 'None':
226                        self.processingMsg = 'No entry for Authors so record not valid and cannot/will not Ingest!'
227                        logging.error(self.processingMsg)
228                        self.validDoc = False
229               
230                else:
231                        #need to provide a list for updating relevant db column - keep distinct from other authors based vars just in case of problems..               
232                        self.authorOb = IsoIngestListUtilities(self.authors,True)               
233                        self.authors_text = self.authorOb.getDelimitedStringFromList(self.authorOb.listVals)           
234                        self.authors_ts_vector = self.authors_text
235                #import pdb; pdb.set_trace()   
236                self.datacentreName = self.getElementVal(self.isoModel.dataCentreName())
237               
238                if self.datacentreName == 'None':
239                        self.processingMsg = 'No entry for Data Centre name so record not valid and cannot/will not Ingest!'
240                        logging.error(self.processingMsg)
241                        self.validDoc = False
242               
243               
244                self.keywords = self.getElementVal(self.isoModel.keywords())
245               
246                self.keywordsList = self.listify(self.keywords)
247               
248                #Does the record have a specific NERC VocabList identifier?
249               
250                #NOTE: this value not currently used in all NERC DC records in this method..
251               
252                scopeValsPresent = self.listify(self.getElementVal(self.isoModel.scopeCode(self.isoModel.ingestKeywordIdentifier())))
253               
254                if len(scopeValsPresent) == 0:
255                        logging.error("No Scope value present!")
256                        self.docInScope = False
257               
258                else:
259                        self.docInScope = True
260                       
261                        logging.info("Scope value detected (%s)" %self.isoModel.ingestKeywordIdentifier() )
262                       
263                        #as a fudge until all providers agree on how they are putting in their scope vals...
264                        #pull the actual list ID and stick it in the keyword list so current method of adding scope works..
265                        self.keywordsList.append(self.isoModel.ingestKeywordIdentifier().split('/')[-1])
266                       
267                if self.keywords == 'None':
268                        logging.warn("No parameter info for record!")
269                        self.parameters_text = ''
270                        self.parameters_tsvector = ''
271                else:           
272                        self.parametersOb = IsoIngestListUtilities(self.keywords,True)                   
273                        self.parameters_text = self.parametersOb.getDelimitedStringFromList(self.parametersOb.listVals)
274                        self.parameters_tsvector = self.parameters_text
275                                               
276                coords = self.getElementVal(self.isoModel.coordinates())
277                if coords == 'None':                   
278                        self.boundingBoxCoordinates = None
279                else:
280                        self.boundingBoxCoordinates = coords   
281               
282                #These are the MEDIN requested extra fields - make optional
283                try:
284                        self.originalFormatVersion = self.getElementVal(self.isoModel.originalFormatVersion())
285                except:
286                        self.originalFormatVersion = None
287               
288                try:
289                        self.publicationDate = self.getElementVal(self.isoModel.metadataPublicationDate())
290                except:
291                        self.publicationDate = None
292                                       
293                try:
294                       
295                        self.topicCategory = self.getElementVal(self.isoModel.topicCategory())
296                        self.topicCategoryList = self.listify(self.topicCategory)
297                       
298                        if self.topicCategory == 'None':
299                                logging.warn("No topic category keywords available!")
300                                self.topicCategory_text = None
301                                self.topicCategory_tsvector = None
302                        else:
303                                self.topicCategoryOb = IsoIngestListUtilities(self.topicCategory,True)
304                                self.topicCategory_text = self.topicCategoryOb.getDelimitedStringFromList(self.topicCategoryOb.listVals)
305                                self.topicCategory_tsvector = self.topicCategory_text
306                               
307                        #self.topicCategory = self.getElementVal(self.isoModel.topicCategory())
308                        #self.topicCategory_text = self.topicCategory
309                        #self.topicCategory_tsvector = self.topicCategory
310                except:
311                        self.topicCategory = None
312                        self.topicCategory_text = None
313                        self.topicCategory_tsvector = None
314                       
315                #updated for NERC API v4.3.0
316               
317                #resource locator               
318                try:
319                        self.resourceLocator = self.getElementVal(self.isoModel.resourceLocator())
320                        self.resourceLocator_text = self.resourceLocator
321                        self.resourceLocator_tsvector = self.resourceLocator
322                except:
323                        self.resourceLocator = None
324                        self.resourceLocator_text = None
325                        self.resourceLocator_tsvector = None
326               
327                       
328                #resourceLocator in the db is a boolean field - it either has a data resource or it doesnt             
329                if self.resourceLocator == 'None' or self.resourceLocator =='':
330                        self.resourceLocatorBool = False
331                else:
332                        self.resourceLocatorBool = True
333               
334                #INSPIRE themes (sub class of keywords)
335               
336                try:   
337                        self.inspireThemes = self.getElementVal(self.isoModel.INSPIREthemes()) 
338                        self.inspireThemesList = self.listify(self.inspireThemes)
339                except:
340                        self.inspireThemes = None
341               
342                if self.inspireThemes == 'None' or self.inspireThemes is None:
343                        logging.warn("No INSPIRE themes info for record!")
344                        self.inspireThemes_text = '' 
345                        self.inspireThemes_tsvector = ''
346                else:           
347                        self.inspireThemesOb = IsoIngestListUtilities(self.inspireThemes,True)                   
348                        self.inspireThemes_text = self.inspireThemesOb.getDelimitedStringFromList(self.inspireThemesOb.listVals)
349                        self.inspireThemes_tsvector = self.inspireThemes_text
350               
351                #vertical extent (sub class of keywords)        OPTIONAL for NERC DMS                   
352                try:
353                        self.verticalExtent = self.getElementVal(self.isoModel.verticalExtentKeyword()) 
354                        self.verticalExtentList = self.listify(self.verticalExtent)
355                except:
356                        self.verticalExtent = None
357               
358                if self.verticalExtent == 'None' or self.verticalExtent is None:
359                        logging.warn("No Vertical Extent keywords info for record!")
360                        self.verticalExtent_text = ''
361                        self.verticalExtent_tsvector = ''
362                else:           
363                        self.verticalExtentOb = IsoIngestListUtilities(self.verticalExtent,True)                         
364                        self.verticalExtent_text = self.verticalExtentOb.getDelimitedStringFromList(self.verticalExtentOb.listVals)
365                        self.verticalExtent_tsvector = self.verticalExtent_text
366                                               
367                       
368                '''
369                self.keywords = self.getElementVal(self.isoModel.keywords())
370               
371                self.keywordsList = self.listify(self.keywords)
372               
373                if self.keywords == 'None':
374                        logging.warn("No parameter info for record!")
375                        self.parameters_text = ''
376                        self.parameters_tsvector = ''
377                else:           
378                        self.parametersOb = IsoIngestListUtilities(self.keywords,True)                   
379                        self.parameters_text = self.parametersOb.getDelimitedStringFromList(self.parametersOb.listVals)
380                        self.parameters_tsvector = self.parameters_text
381                '''
382               
383                try:
384                        self.lineage = self.getElementVal(self.isoModel.lineage())
385                        self.lineage_text = self.lineage
386                        self.lineage_tsvector = self.lineage
387                except:
388                        self.lineage = None
389                        self.lineage_text = None
390                        self.lineage_tsvector = None
391                '''
392                try:
393                        self.publicAccessLimitations = self.getElementVal(self.isoModel.publicAccessLimitations())
394                        self.publicAccessLimitations_text = self.publicAccessLimitations
395                        self.publicAccessLimitations_tsvector = self.publicAccessLimitations
396                        self.publicAccessLimitationsList = self.listify(self.publicAccessLimitations_text)
397                except:
398                        self.publicAccessLimitations = None
399                        self.publicAccessLimitations_text = None
400                        self.publicAccessLimitations_tsvector = None
401                        self.publicAccessLimitationsList = None
402                '''     
403               
404                try:   
405                        self.publicAccessLimitations= self.getElementVal(self.isoModel.publicAccessLimitations())       
406                        self.publicAccessLimitationsList = self.listify(self.publicAccessLimitations)
407                except:
408                        self.publicAccessLimitations = None
409               
410                if self.publicAccessLimitations == 'None' or self.publicAccessLimitations is None:
411                        logging.warn("No Public access limitation info for record!")
412                        self.publicAccessLimitations_text = '' 
413                        self.publicAccessLimitations_tsvector = ''
414                else:           
415                        self.publicAccessLimitationsOb = IsoIngestListUtilities(self.publicAccessLimitations,True)                       
416                        self.publicAccessLimitations_text = self.publicAccessLimitationsOb.getDelimitedStringFromList(self.publicAccessLimitationsOb.listVals)
417                        self.publicAccessLimitations_tsvector = self.inspireThemes_text         
418                               
419                try:
420                        self.dataOriginator= self.getElementVal(self.isoModel.dataOriginator())
421                                               
422                except:
423                        self.dataOriginator = None
424                        self.dataOriginator_text = None
425                        self.dataOriginator_tsvector = None
426                       
427               
428                #check presence of originator - Mandatory!
429                if self.dataOriginator == 'None' or self.dataOriginator is None:
430                        #self.processingMsg = 'No entry for Data Originator!'
431                        #logging.error(self.processingMsg)
432                        #self.validDoc = False
433                        self.dataOriginator_text = 'null'
434                        self.dataOriginator_tsvector = 'null'
435               
436                else:                   
437                        self.dataOriginatorOb = IsoIngestListUtilities(self.dataOriginator,True)
438                       
439                        #add some extra specifications to deal with MEDIn requirement to have both searchable field (vector) and return as a text field
440                        self.dataOriginator_text = self.dataOriginatorOb.getDelimitedStringFromList(self.dataOriginatorOb.listVals)
441                        self.dataOriginator_tsvector = self.dataOriginator_text
442               
443                try:
444                        self.dataFormat = self.getElementVal(self.isoModel.originalFormat())
445                except:
446                        self.dataFormat = None
447               
448                try:
449                        self.metadataUpdateDate = self.getElementVal(self.isoModel.metadataUpdateDate())
450                except:
451                        self.metadataUpdateDate = None
452                       
453                       
454                try:
455                        self.originalFormatName = self.getElementVal(self.isoModel.originalFormat())
456                except:
457                        self.originalFormatName = None
458                       
459                try:
460                        self.originalFormatVersion = self.getElementVal(self.isoModel.originalFormatVersion())
461                except:
462                        self.originalFormatVersion = None
463                       
464               
465                logging.info("")
466                logging.info(" ****************** Completed rendering ISO xml into data structure! ****************** ")
467                logging.info("")
468               
469                return True
470       
471       
472        '''
473        Method to generate a simple list object from a compound list set
474        '''
475        def listify(self,listObject):
476               
477                list = []
478               
479                for outer in listObject:
480                        for inner in outer:                             
481                                list.append(inner)
482                                               
483                return list
484       
485        '''
486        Method to pad out a date required for proper updating postgres timestamps if only have a stump of a date (i.e. YYYY or YYYY-MM)
487       
488        NOTE: only pads out dates if it needs to! (checkDate checks the values!) - use checkDate for validation- if bad date just returns input but with warning
489       
490        Uses template YYYY-MM-DDTHH:MM:SS if anything other then pad to minimum YYYY-MM-DD
491       
492        '''
493        def padDate(self,inputDate,rangeStart=None):
494               
495                if inputDate is None:
496                        #just pass it on through
497                        return None
498                               
499                if rangeStart is None:
500                        padMax = True
501                else:
502                        padMax = rangeStart
503                       
504                inputDate=inputDate.strip()
505                                       
506                dateStrArr = inputDate.split('-')
507                               
508                #check if long date string or short - if long, will have a T in! (should do..)
509                #just stick with date values now..
510                if 'T' in dateStrArr:
511                        logging.info("*********************************** LONG VALUE DATE STRING ALERT!")
512                        dateStrArr = dateStrArr[:3]
513                        timeStrArr = dateStrArr[4:]
514                                                       
515                if len(dateStrArr)< 3:
516                       
517                        #acceptible to have YYYY, or YYYY-MM as well now...
518                        if len(dateStrArr)==0:
519                                logging.warn("Invalid Date! (%s)" %inputdate)
520                                return inputDate
521                       
522                        elif len(dateStrArr) == 1:
523                                logging.info("looks like just a year supplied..")
524                                yearStr = dateStrArr[0]
525                               
526                                if len(yearStr) != 4:
527                                        logging.warn("Invalid Date! (%s)" %inputDate)
528                                        return inputDate
529                                #temp fudge
530                                logging.info("No Month or day data- pad for postgres!")
531                               
532                                #pad values as appropriate
533                                if padMax:                             
534                                        monthStr = "01"
535                                        dayStr = "01"
536                                else:
537                                        monthStr = "12"
538                                        dayStr = "31"
539                                       
540                                outputDate = "%s-%s-%s" %(yearStr,monthStr,dayStr)
541                                logging.info("Have padded input of %s to %s" %(inputDate,outputDate))
542                               
543                                return outputDate
544                               
545                        elif len(dateStrArr)==2:
546                                msg = "checking what could be a year:%s month:%s" %(dateStrArr[0],dateStrArr[1])
547                                yearStr = dateStrArr[0]
548                                monthStr = dateStrArr[1]
549                               
550                                if len(yearStr) != 4:
551                                        logging.warn("Bad year value...(%s)" %yearStr)
552                                        return False
553                               
554                                #pad values as appropriate
555                                if padMax:     
556                                        dayStr = "01"
557                                else:
558                                        if monthStr in ['01','03','05','07','08','10','12']:
559                                                dayStr = "31"
560                                        elif monthStr in ['04','06','09','11']:
561                                                dayStr = "30"
562                                        else:
563                                                dayStr = '28'
564                                               
565                                outputDate = "%s-%s-%s" %(yearStr,monthStr,dayStr)
566                                logging.info("Have padded input of %s to %s" %(inputDate,outputDate))
567                               
568                                return outputDate
569                               
570                else:
571                        logging.info("No padding required (%s)" %inputDate)
572                        return inputDate
573                       
574       
575        '''
576        Method to check date values to ensure correct insertion into database - if doesnt match minimum YYYY-MM-DD format then is false,
577        so date should be set to None in calling method.  Will adjust self.processingMsg too for return info
578       
579        NOTE: update to just check values - padDate method must be called first to ensure date format is minimum of YYYY-MM-DD
580       
581        '''
582        def checkDate(self,inputDate):
583               
584                #rangeStartOrEnd - to aid in padding out dates - will choose minimum pad values if true, maximum if false
585                #returnValue - if had to pad values, and want a changed value back thgis must be true           
586               
587                #strip any leading or trailing spaces
588                inputDate = inputDate.strip()
589               
590                if 'T' in inputDate:
591                        if len(inputDate.split('T'))==2:
592                                dateStrArr = inputDate.split('T')[0]
593                                timeStrArr = inputDate.split('T')[0]
594                               
595                                dateStrArr = dateStrArr.split('-')
596                                if len(dateStrArr)!=3:
597                                        msg = "Invalid input format date (%s)" %inputDate
598                                        return False
599                       
600                                else:                                   
601                                        yearStr = dateStrArr[0]
602                                        monthStr = dateStrArr[1]
603                                        dayStr = dateStrArr[2]
604                               
605                        else:
606                                #still need to do some basic date structure checking as padDate only fills out where it can, if still bad
607                                #this method should ultimately say whether good or bad date
608                                msg = "Invalid input format date (%s)" %inputDate
609                                return False
610               
611                elif ' ' in inputDate:
612                        if len(inputDate.split(' '))==2:
613                                dateStrArr = inputDate.split(' ')[0]
614                                timeStrArr = inputDate.split(' ')[0]
615                               
616                                dateStrArr = dateStrArr.split('-')
617                                if len(dateStrArr)!=3:
618                                        msg = "Invalid input format date (%s)" %inputDate
619                                        return False
620                       
621                                else:                                   
622                                        yearStr = dateStrArr[0]
623                                        monthStr = dateStrArr[1]
624                                        dayStr = dateStrArr[2]
625                               
626                        else:
627                                #still need to do some basic date structure checking as padDate only fills out where it can, if still bad
628                                #this method should ultimately say whether good or bad date
629                                msg = "Invalid input format date (%s)" %inputDate
630                                return False
631       
632                else:
633                        #should now have YYYY-MM-DD if padDate used properly
634                        dateStrArr = inputDate.split('-')
635                       
636                        if len(dateStrArr)!=3:
637                                        msg = "Invalid input format date (%s)" %inputDate
638                                        return False
639                       
640                        else:                                   
641                                yearStr = dateStrArr[0]
642                                monthStr = dateStrArr[1]
643                                dayStr = dateStrArr[2]
644               
645                #check days, months etc - Years can be anything... just do simple test for now         
646                if int(monthStr) > 12 or int(monthStr) < 1:
647                        msg = "Invalid Month value (%s) \n" %monthStr
648                        logging.warn(msg)
649                        self.processingMsg = self.processingMsg + msg
650                        return False
651                               
652                elif monthStr in ['01','03','05','07','08','10','12']:
653                        if int(dayStr) > 31 or int(dayStr) < 1:
654                                msg = "(31 ck) Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
655                                logging.warn(msg)
656                                self.processingMsg = self.processingMsg + msg
657                                return False
658                        else:
659                                logging.info("(31 ck) Date value seems ok...(%s) " %inputDate)
660                                return True
661                       
662                elif monthStr in ['04','06','09','11']:
663                        if int(dayStr) > 30 or int(dayStr) < 1:
664                                msg = "(30 ck) Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
665                                logging.warn(msg)
666                                self.processingMsg = self.processingMsg + msg
667                                return False
668                        else:
669                                logging.info("(30 ck) Date value seems ok...(%s) " %inputDate)
670                                return True
671                                       
672                elif monthStr == '02':
673                        if int(yearStr) % 4 == 0:
674                                if int(dayStr) > 29 or int(dayStr) < 1:
675                                        msg = "Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
676                                        logging.warn(msg)
677                                        self.processingMsg = self.processingMsg + msg
678                                        return False
679                                else:
680                                        logging.info("(29 ck) Date value seems ok...(%s) " %inputDate)
681                                        return True
682                                                                       
683                        else:                           
684                                if int(dayStr) > 28 or int(dayStr) < 1:
685                                        msg = "Invalid Day value (%s) for month (%s)\n" %(dayStr,monthStr)
686                                        logging.warn(msg)
687                                        self.processingMsg = self.processingMsg + msg
688                                        return False
689                                else:
690                                        logging.info("(28 ck) Date value seems ok...(%s) " %inputDate)
691                                        return True                                                                     
692                               
693                else:
694                        msg = "Invalid month value (%s)" %monthStr
695                        logging.warn(msg)
696                        self.processingMsg = self.processingMsg + msg
697                        return False
698               
699        '''
700        Method to return start and end dates from a sequence of dates such as that found returned using the xpath defn from boundingDates
701        '''
702        def boundingDateRange(self,boundingDatesList):
703               
704                #generate a simple list of all dates
705                allDates = []
706                returnDates = {}
707               
708                #check that there's something in it...
709                if boundingDatesList == 'None':
710                        return 'None'
711                                       
712                #remember, need to sort out whether there was a start and end date in the first place, or just one so can copy values over
713                if len(boundingDatesList) == 1:
714                        logging.info("Only 1 set of start and stop dates, so setting range to this..")
715                        returnDates['start'] = boundingDatesList[0]['start']
716                        returnDates['end'] = boundingDatesList[0]['end']
717                                               
718                        #check date validity & convert None to 'None' as necessary
719                        for date in returnDates.keys():
720                                if date=='start':                       
721                                        if returnDates[date] is not None:
722                                               
723                                                #pad if possible..
724                                                returnDates[date] = self.padDate(returnDates[date],rangeStart=True)
725                                                                                                                       
726                                                if not self.checkDate(returnDates[date]):
727                                                        returnDates[date] = 'None'
728                                        else:
729                                                returnDates[date] = 'None'
730                                                       
731                                elif date=='end':
732                                       
733                                        if returnDates[date] is not None:
734                                               
735                                                #pad if possible..
736                                                returnDates[date] = self.padDate(returnDates[date],rangeStart=False)
737                                                                                                                       
738                                                if not self.checkDate(returnDates[date]):
739                                                        returnDates[date] = 'None'
740                                        else:
741                                                returnDates[date] = 'None'
742                                                                               
743                        return returnDates
744               
745                for outer in boundingDatesList:
746                        for inner in outer.keys():
747                                if outer[inner] is not None:
748                                        if outer[inner] != 'None':
749                                                #some other test here to ensure proper date object
750                                                allDates.append(outer[inner])
751                                               
752                               
753                #min and max seem to work for now (to get it working) - assuming proper dateformat (restrict to YYYY-MM-DD for now)
754                #TODO: use datetime library to do this properly
755               
756                #check correct formatting for dates - if invalid, set to None - use checkDate method           
757                for date in returnDates.keys():                 
758                        if not self.checkDate(returnDates[date]):
759                                returnDates[date] = 'None'
760                                       
761                returnDates['start'] = min(allDates)
762                returnDates['end'] = max(allDates)
763               
764               
765                return returnDates
766       
767       
768               
769        '''
770        Method to import specific ISO xpath definition class depending on the profile used
771        '''
772        def importISOxmlDefinition(self,format):
773               
774                '''
775                ISO Profile version (date? - MEDIN upgrade dev)
776                '''
777               
778                logging.info("Format chosen: " + format)
779               
780                #Dynamically import module of correct profile...
781                import_string = "from " + format + " import " + format + " as iso"
782               
783                try:
784                        exec import_string
785                except:
786                        logging.error("Could not import xpath class for: " + format)
787                        sys.exit()
788               
789                self.isoModel = iso()
790               
791               
792        '''
793        Method to govern the changing of an element value (in the first instance change a url to a NDG redirect URL
794       
795        NOTE: will only change Urls if changeUrl set to True - this method is also used to ready the xml for writing into db
796       
797        '''
798        def changeElementUrlVal(self,keyMethod,outputFile,changeUrl):
799               
800       
801                logging.info("******************************************************************************************")
802                logging.info("Extracting xpath information for data extraction type (to change URL..):" + keyMethod[0])
803                logging.info("******************************************************************************************")
804                               
805               
806                #first need to interpret the contents of the tuple we've been passed everything is a dictionary in the tuple apart from element 0:
807                #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
808                dataStruct = self.interpretTuple(keyMethod)
809                cnt = 0
810               
811                #this is similar to getElementVal - but no interest in ordering or returning the actual value..
812               
813                #if changeUrl set to true, activate changes, just write xml as is if not
814                if changeUrl:           
815                        logging.info("changing urls now...")
816                        for i in dataStruct.keys()[1:]:
817                                               
818                                thisData = keyMethod[i][dataStruct[i]]
819                                                               
820                                #is dicionary a dependant value or straight xpath?
821                                if 'baseXpath' in thisData.keys():
822                                        logging.info("Extracting xml using COMPLEX dependant method for xpath def: " + str(cnt))                       
823                                        self.changeDependantURLElementVal(thisData['baseXpath'],thisData['elValXpath'],thisData['depValXpath'],thisData['depVal'])                             
824                                                               
825                                if 'xpath' in thisData.keys():         
826                                        logging.info("Extracting xml using SIMPLE method for xpath def: " + str(cnt))                                           
827                                        self.changeSimpleURLElementVal(thisData['xpath'])       
828                                       
829                        try:
830                                #rewrite this back into the xml doc                             
831                                self.etree.write(outputFile)
832                                logging.info("Have successfully generated new ISO file with rewritten urls to NDG redirect format!")
833                       
834                                return True
835               
836                        except:
837                                logging.error("Could not rewrite NDG url ISO xml to; " + outputFile)                   
838                                return False
839                                       
840                else:
841                        logging.info("URL's not set to change- therefore copy original file to discovery dir")
842
843                        try:
844                                FileUtilities.copyFileToDir(self.originalXMLfileName,outputFile)
845                                return True
846                        except:
847                                logging.error("Couldnt create a direct copy of original file in discovery dir!")
848                                return False
849               
850               
851        '''
852        Method to change an ISO element val - simple xpath value
853        '''
854        def changeSimpleURLElementVal(self, xpath):
855               
856                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
857                xpathNS = self.appendNameSpace(xpath)           
858               
859                resElementVal = []
860               
861                try:   
862                        rootEl = self.root.findall(xpathNS)
863                       
864                except:
865                        logging.error("Could not find element for this xpath: " + xpath)                       
866                       
867                                               
868                #use a text 'None' rather than a None type                     
869                for elVal in rootEl:
870                        if elVal is None: 
871                                originalUrl = 'None' 
872                        else:
873                                try:                   
874                                        originalUrl = elVal.text       
875                                        redirectURL = self.redirectBaseURL + originalUrl + '&docID=' + urllib.quote(self.datasetID[0][0]) + '&docTitle=' + urllib.quote(self.datasetName[0][0])
876                               
877                                        #reassign...
878                                        elVal.text = redirectURL                                       
879                                        logging.info("Have successfuly rewritten ndg redirect URL: " + redirectURL)
880                                       
881                                except:
882                                        logging.error("Could not change original (simple) url!")
883               
884                       
885                #no need to return anything -if we rewrite the doc, any successful changes will be incorporated, otherwise will be ignored
886       
887        '''
888        Method to change an ISO element val - compound dependant xpath values
889        '''
890        def changeDependantURLElementVal(self,baseXpath,elXpath,depXpath,depValReqd):
891               
892                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
893                baseXpath = self.appendNameSpace(baseXpath)
894                               
895                #for path in baseXpaths:
896                try:                           
897                        rootEl = self.root.findall(baseXpath) #returns an elementree object of elements in a list                               
898                except:
899                        logging.error("Could not find element for this xpath: " + baseXpath)
900                        return 'None'
901                       
902                for el in rootEl:
903                        thisElXpth = self.appendNameSpace(elXpath)
904                                                               
905                        thisEl = self.doFindall(el,thisElXpth)
906                               
907                        #if there's a value for the actual xpath we want, go and get the dependant value
908                        if thisEl != 'None':
909                                       
910                                elVal = thisEl[0]
911                                elValTxt = elVal.text #NOTE take first one as we expect this to be the value we want
912                                                                       
913                                thisEldepXpth = self.appendNameSpace(depXpath)                                 
914                                thisDepEl = self.doFindall(el,thisEldepXpth)                                                           
915                                       
916                                if thisDepEl != 'None':
917                                                                               
918                                        depVal = thisDepEl[0].text
919                                                                       
920                                        if depVal == depValReqd:
921                                               
922                                                try:
923                                                        originalUrl = elValTxt                                         
924                                                        redirectURL = self.redirectBaseURL + originalUrl + '&docID=' + urllib.quote(self.datasetID[0][0]) + '&docTitle=' + urllib.quote(self.datasetName[0][0])
925                                                                                       
926                                                        #reassign...
927                                                        elVal.text = redirectURL                                                                                       
928                                                        logging.info("Have successfuly rewritten ndg redirect URL: " + redirectURL)
929                                                       
930                                                except:
931                                                        logging.error("Could not change original (complex) url!")
932                                       
933                                                                                                                       
934                #no need to return anything -if we rewrite the doc, any successful changes will be incorporated, otherwise will be ignored
935               
936       
937        '''
938        Method to initially interpret the xpath dictionary type passed
939        '''
940        def interpretTuple(self,keyMethod):
941               
942                dataStruct = {}
943                counter=0
944                for i in keyMethod:
945                        if type(i) is dict:
946                               
947                                #loop through the top level (remember its a dictionary of dictionaries!)
948                                for j in i.keys():                             
949                                        dataStruct[counter]=j
950                       
951                        #Only other data type present should be a string
952                        if type(i) is str:
953                                dataStruct[counter] = i
954                               
955                        counter = counter + 1
956                               
957                return dataStruct
958       
959               
960        '''
961        Method to aid passing and extraction info from complex xpath tuples
962       
963        Will work out whether complex or simple xpath extraction required depending on type of xpath method sent
964       
965        If 'order' key present then will return a dictionary of values with the extracted value using the specified key.
966       
967        '''
968        def getElementVal(self,keyMethod):
969               
970                #develop handler method to simplify
971                returnValList=[]                               
972                returnVal = 'None'
973               
974                if self.showXMLvalues:
975                        logging.info("******************************************************************************************")
976                        logging.info("Extracting xpath information for data extraction type:"  + keyMethod[0])
977                        logging.info("******************************************************************************************")
978               
979                               
980                showValue = self.showXMLvalues
981               
982                #first need to interpret the contents of the tuple we've been passed everything is a dictionary in the tuple apart from element 0:
983                #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
984                dataStruct = self.interpretTuple(keyMethod)
985                '''
986                dataStruct = {}
987                counter=0
988                for i in keyMethod:
989                        if type(i) is dict:
990                               
991                                #loop through the top level (remember its a dictionary of dictionaries!)
992                                for j in i.keys():                             
993                                        dataStruct[counter]=j
994                       
995                        #Only other data type present should be a string
996                        if type(i) is str:
997                                dataStruct[counter] = i
998                               
999                        counter = counter + 1
1000                '''
1001               
1002                #now iterate through the key list in dataStruct but miss First one (as its a name val!)                 
1003                valueList = []
1004                ordering = False
1005               
1006                cnt = 1
1007               
1008                for i in dataStruct.keys()[1:]:
1009                                               
1010                        thisData = keyMethod[i][dataStruct[i]]
1011                                                               
1012                        #is dicionary a dependant value or straight xpath?
1013                        if 'baseXpath' in thisData.keys():
1014                                logging.info("Extracting xml using COMPLEX dependant method for xpath def: " + str(cnt))                       
1015                                returnVal = self.returnDependantElementVal(thisData['baseXpath'],thisData['elValXpath'],thisData['depValXpath'],thisData['depVal'],showValue)                           
1016                                valueList.append(returnVal)
1017                                                               
1018                        if 'xpath' in thisData.keys():         
1019                                logging.info("Extracting xml using SIMPLE method for xpath def: " + str(cnt))                                           
1020                                returnVal =  self.returnSimpleElementVal(thisData['xpath'],showValue)                                                           
1021                                valueList.append(returnVal)
1022                                                               
1023                        #is there any ordering info required - if so, order the return list according to the ordering tuple
1024                        if 'order' in keyMethod[i].keys():
1025                                logging.info("Returning a dictionary of paired values as ordering requested for xpath (number of xpath defs MUST match ordering vals available")                       
1026                                ordering = True                         
1027                                orderingList = thisData
1028                               
1029                        else:
1030                                logging.info("Returning a simple list of values for xpath def: " + str (cnt))
1031                               
1032                        cnt = cnt + 1
1033                       
1034                #check theres any data..
1035                dataPresent=False
1036                for values in valueList:
1037                       
1038                        #as valueList is a list of lists, length of valueList will be > 1 even if no data in sub-lists..
1039                        #..so check through all subLists and if not data at all then can return None
1040                        if len(values) > 0:
1041                                dataPresent = True
1042                               
1043                if not dataPresent:                     
1044                        logging.warning("No data to extract from the xml for %s" %keyMethod[0])
1045                        return 'None'
1046                       
1047                #if ordering is required, create a corresponding dictionary of ordered values                                   
1048                if ordering:
1049                       
1050                        #logging.INFO("Ordering data by specified order information!")
1051                        logging.info("ordering information now!")
1052                       
1053                        '''FIRST need to group information together in correct order (i.e. elementtree groups all starts in one list, then all ends etc
1054                        i.e. [[start1,start2],[end1,end2]] to [[start1,end1],[start2,end2]]
1055                        '''
1056                        #import pdb
1057                        #pdb.set_trace()
1058                        #check that number of number of elements in ordering list is not less than number of items in list to be ordered!
1059                        #if multiple sets to be ordered, check numbers and return as dictionary
1060                        checkCompLnth = len(valueList[0])
1061                        for list in valueList:
1062                                if len(list) != checkCompLnth:
1063                                        logging.error("Sub component lists are of unequal length - CANNOT order!!")
1064                                        return 'None'
1065                               
1066                        #outer loop should be the number of elements in the sublists..
1067                        outer = []
1068                        for localPos in range(0,checkCompLnth):
1069                                inner=[]
1070                                #inner loop should be the number of lists corresponding to repeating subelements                       
1071                                for listPos in range(0,len(valueList)):
1072                                       
1073                                                inner.append(valueList[listPos][localPos])
1074                                #append to outer loop
1075                                outer.append(inner)                                                     
1076                       
1077                        for returnedList in outer:
1078                               
1079                                #create a disctionary for this round (will append it later to the return version)
1080                                orderedValsSub = {}
1081                                                                               
1082                                if len(returnedList) != len(orderingList):
1083                                        logging.error("Ordering List length does not match length of list to be ordered! (NOTE: can only order where lengths match!)")
1084                                        returnValList.append('None')
1085                               
1086                                else:           
1087                                        #go through valueList and extract values in correct position and build dictionary
1088                                        for i in orderingList.keys():
1089                                               
1090                                                orderedValsSub[i] = returnedList[orderingList[i]-1] # remember there's an offset as '0' not used in these vals
1091                               
1092                               
1093                                        returnValList.append(orderedValsSub)
1094                       
1095                        return returnValList
1096                       
1097                else:
1098                        return valueList
1099                       
1100                       
1101        '''
1102        Method to return a list of values of an element value dependant on another element value held within the local tree
1103        i.e. return a specific string val depending on a local code val in ISO
1104       
1105        if element exists and any dependant value present matches the expected value, the element value is returned as part of a list,
1106        otherwise an empty list is returned
1107        '''
1108        def returnDependantElementVal(self,baseXpath,elXpath,depXpathOrig,depValReqd,showValue=False):
1109                       
1110                #Note using single path namespath appender rather than list version.. (keeps it simpler)       
1111                baseXpath = self.appendNameSpace(baseXpath)
1112               
1113                resDependantVal = []
1114               
1115                #for path in baseXpaths:
1116                try:                           
1117                        rootEl = self.root.findall(baseXpath) #returns an elementree object of elements in a list                               
1118                except:
1119                        logging.error("Could not find element for this xpath: " + baseXpath)
1120                        return 'None'
1121               
1122               
1123                for el in rootEl:
1124                       
1125                        thisElXpth = self.appendNameSpace(elXpath)                                             
1126                        thisEl = self.doFindall(el,thisElXpth)
1127                       
1128                        attrFlag = False
1129                       
1130                        #if there's a value for the actual xpath we want, go and get the dependant value
1131                        if thisEl != 'None':
1132                               
1133                                #update to handle multiple values dependant on a single element value.. (based on bodc having multiple keywords in a single MD_keywords for Inspire themes                                     
1134                                #elVal = thisEl[0].text #NOTE take first one as we expect this to be the value we want
1135                                                                       
1136                                for elValIt in thisEl:
1137                                       
1138                                        #elVal = elValIt.text
1139                                        #ensure correct encoding...
1140                                        elVal = elValIt.text.encode('utf-8')
1141                                                                               
1142                                        if '@' in depXpathOrig:
1143                                               
1144                                                #if there is an attribute in there, strip if off (should ALWAYS be last value anyway..)
1145                                                thisAttribute = depXpathOrig.split('@')[-1]
1146                                               
1147                                                #does the attribute have a namespace (i.e. xlink;href)
1148                                                if ":" in thisAttribute:
1149                                                        thisAttribute = self.appendNameSpace(thisAttribute)
1150                                               
1151                                                if depXpathOrig.split('@')[0][-1] == '/':
1152                                                        depXpath = depXpathOrig.split('@')[0][:-1]
1153                                                else:
1154                                                        depXpath = depXpathOrig.split('@')[0]
1155                                                       
1156                                                attrFlag = True                                         
1157                                               
1158                                        else:
1159                                                depXpath = depXpathOrig
1160                                               
1161                                        thisEldepXpth = self.appendNameSpace(depXpath)                                 
1162                                        thisDepEl = self.doFindall(el,thisEldepXpth)                                                                                                   
1163                                       
1164                                        if thisDepEl != 'None':
1165                                                                                                                       
1166                                                if attrFlag:
1167                                                       
1168                                                        if thisDepEl[0].attrib.get(thisAttribute) == depValReqd:
1169                                                                resDependantVal.append(elVal)
1170                                                               
1171                                                       
1172                                                else:
1173                                                        #not an attribute but an element value                                 
1174                                                        depVal = thisDepEl[0].text # still need a single dependant value
1175                                                               
1176                                                        if depVal == depValReqd:
1177                                               
1178                                                                if showValue is True:
1179                                                                        logging.info("")
1180                                                                        logging.info("          Element Value: " + elVal)
1181                                                                        logging.info("")
1182                                                                                                               
1183                                                                resDependantVal.append(elVal)
1184               
1185                if self.stopHere:
1186                        import pdb; pdb.set_trace()             
1187                                                                                                                                               
1188                return resDependantVal
1189       
1190        '''
1191        Method to extract a value from the doc using a simple xpath.  If no element found or no data present then None is returned
1192        '''
1193        def returnSimpleElementVal(self,xpath,showValue=False):
1194                               
1195                attrib=False
1196               
1197                #are there any attributes required i.e. xpath uses "@" to get attribute name, elementtree uses the "attrib" method
1198                if '@' in xpath:
1199                        logging.info("Requires an attribute value...")
1200                       
1201                        attrib = True
1202                       
1203                        #get the attribute name to find(assume its the last term after the "@")
1204                        attribVal = xpath.split('@')[1:][0]
1205                        attribXpath = xpath.split('@')[0:][0]
1206                       
1207                        #Note using single path namespath appender rather than list version.. (keeps it simpler)       
1208                        xpathNS = self.appendNameSpace(attribXpath)
1209                       
1210                else:
1211                        xpathNS = self.appendNameSpace(xpath)
1212                               
1213                resElementVal = []
1214                               
1215                try:                   
1216                        rootEl = self.root.findall(xpathNS)
1217                       
1218                except:
1219                        logging.error("Could not find element for this xpath: " + xpath)                       
1220                        return ['None']
1221                                               
1222                #use a text 'None' rather than a None type
1223               
1224                for elVal in rootEl:
1225                        if elVal is None:
1226                                resElementVal.append('None')
1227                        else:
1228                                if attrib:
1229                                        value = elVal.attrib[attribVal]
1230                                else:
1231                                        #ensure correct encoding...
1232                                        value = elVal.text.encode('utf-8')
1233                                       
1234                                if (showValue is True) and (value is not None):
1235                                        logging.info("")
1236                                        logging.info("          Element Value: " + value)
1237                                        logging.info("")
1238                                       
1239                                        resElementVal.append(value)
1240                                       
1241                if self.stopHere:
1242                        import pdb; pdb.set_trace()
1243                '''     
1244                if rootEl[0].text is None:                                     
1245                        resElementVal.append('None')
1246                else:
1247                        for el in rootEl:                                       
1248                                resElementVal.append(el.text)   
1249                               
1250                                       
1251                '''
1252               
1253                return resElementVal
1254                               
1255       
1256        '''
1257        Method to run an elementtree findall on ns appended path in the supplied element and return list
1258        returns None if nothing found
1259        '''
1260        def doFindall(self,el,thisElXpth):
1261                               
1262                try:
1263                        thisElXpthEl = el.findall(thisElXpth)
1264                       
1265                        if len(thisElXpthEl) == 0:
1266                                thisElXpthEl = 'None'
1267                except:
1268                        thisElXpthEl = 'None'
1269               
1270               
1271                return thisElXpthEl
1272               
1273               
1274        '''
1275        Method to extract actual value from xml doc - expects a list of namespace qualified xpaths and will return corresponding list of values
1276        '''
1277        def getXmlVal(self,paths):
1278               
1279                xmlVals = []
1280                               
1281                for path in paths:
1282                        try:
1283                                xmlVals.append(self.root.find(path).text)                       
1284                        except:
1285                                logging.error("Could not extract value for xpath: " + path)
1286                                xmlVals.append('null')
1287                               
1288                return xmlVals                                 
1289               
1290        '''
1291        Method to hold a dictionary linking namespace prefixes to URLs
1292        '''
1293        def isoNameSpaces(self):
1294               
1295                isoNs = {'gmd':'http://www.isotc211.org/2005/gmd',
1296                                'gco':'http://www.isotc211.org/2005/gco',
1297                                'gmx':'http://www.isotc211.org/2005/gmx',
1298                                'gml':'http://www.opengis.net/gml/3.2',
1299                                'none':'','gts':'http://www.isotc211.org/2005/gts',
1300                                'gsr':'http://www.isotc211.org/2005/gsr',
1301                                'gss':'http://www.isotc211.org/2005/gss',
1302                                'srv':'http://www.isotc211.org/2005/srv',
1303                                'geonet':'http://www.fao.org/geonetwork',
1304                                'xlink':'http://www.w3.org/1999/xlink'
1305                                }
1306               
1307                return isoNs
1308       
1309       
1310        '''
1311        Method to return DEFAULT namespace in elementtree format of ISO profile used (i.e. should be gmd)
1312        (to deal with those elements with no prefix)
1313        '''
1314        def defaultIsoNamespace(self):
1315                return 'gmd'
1316               
1317       
1318       
1319        '''
1320        Method to extract root element of XML file specified using elementtree
1321        '''     
1322        def getIsoXML(self,file):
1323               
1324                logging.info("Getting xml root element")
1325               
1326                self.originalXMLfileName = file         
1327               
1328                #parse the xml with elementtree
1329                try:
1330                        #explicity code everything as UTF-8 (ensure UTF-8 compliance Dec 2011) - Use Et 1.3+ (now explicitly included in buildout recipe)                       
1331                        parser = ET.XMLParser(encoding="utf-8")
1332                        self.etree=ET.parse(file,parser=parser)
1333                        root=self.etree.getroot() # should be the "gmd:MD_Metadata" element
1334                except:
1335                        logging.error("File %s appears to be bad XML!" %file)
1336                        return None
1337               
1338                #avoid ns0 and ns1 etc etc namespaces when rewriting this ET
1339                logging.info("Sorting out namespaces so can properly rewrite ISO xml..")
1340                for ns in self.isoNameSpaces().keys():         
1341                        if ns != 'None':                                               
1342                                ET._namespace_map[self.isoNameSpaces()[ns]] = ns
1343                               
1344                #check root element is an ISO one - use elementtree appended namespace..
1345                if root.tag != '{http://www.isotc211.org/2005/gmd}MD_Metadata':
1346                        logging.error("XML document does not appear to be ISO (not gmd:MD_Metadata)!!")                 
1347                        return None
1348       
1349                return root
1350               
1351               
1352        '''
1353        Method to convert the given xpath list to a namespace abbreviated version so elementtree can handle it (grrr.). 
1354        Will return a list of corresponding namespace qualified xpaths - if errors a 'null' will be placed.
1355        '''
1356        def appendNameSpaceList(self,paths):
1357               
1358                nameSpaceAppendedPaths = []
1359               
1360               
1361                for path in paths:             
1362                       
1363                        pathElements = path.split('/')
1364                       
1365                        #note due to crappy elementtree have to avoid initial "/"
1366                        count = 0
1367                        for element in pathElements:
1368                               
1369                                try:                                   
1370                                        if ':' in element:                                             
1371                                                splitElement = element.split(':')
1372                                                nsPrefix,nsElement = splitElement[0],splitElement[1]
1373                                        else:
1374                                                #use default namespace                                         
1375                                                nsPrefix = self.defaultIsoNamespace()
1376                                                nsElement = element
1377                                       
1378                                        if count == 0:
1379                                               
1380                                                #appendedPath = self.returnNS() + element
1381                                                appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1382                                        else:
1383                                               
1384                                                appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1385                                       
1386                                        count += 1
1387                                except:
1388                                        appendedPath = 'null'
1389                                        logging.error("Could not change to elementtree ns xpath")
1390                                                       
1391                        #clear up any blank namespace prefixes
1392                        appendedPath = appendedPath.replace('{}','')
1393                       
1394                        nameSpaceAppendedPaths.append(appendedPath)
1395
1396                return nameSpaceAppendedPaths
1397       
1398        '''
1399        Method to convert the given xpath to a namespace abbreviated version so elementtree can handle it (grrr.). 
1400        Will return an equivalent namespace qualified xpath - if errors a 'null' will be placed.
1401        '''
1402        def appendNameSpace(self,path):
1403               
1404                nameSpaceAppendedPath = ''
1405               
1406                pathElements = path.split('/')
1407                       
1408                #note due to crappy elementtree have to avoid initial "/"
1409                count = 0
1410                for element in pathElements:
1411                               
1412                        try:                                   
1413                                if ':' in element:                                             
1414                                        splitElement = element.split(':')
1415                                        nsPrefix,nsElement = splitElement[0],splitElement[1]
1416                                else:
1417                                        #use default namespace                                         
1418                                        nsPrefix = self.defaultIsoNamespace()
1419                                        nsElement = element
1420                                       
1421                                if count == 0:
1422                                               
1423                                        #appendedPath = self.returnNS() + element
1424                                        appendedPath = '{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1425                                else:                                           
1426                                        appendedPath = appendedPath + '/{' + self.isoNameSpaces()[nsPrefix] +'}' + nsElement
1427                                       
1428                                count += 1
1429                        except:
1430                                appendedPath = 'null'
1431                                logging.error("Could not change to elementtree ns xpath")
1432                                                       
1433                #clear up any blank namespace prefixes
1434                nameSpaceAppendedPath = appendedPath.replace('{}','')
1435               
1436                return nameSpaceAppendedPath
1437       
1438       
1439        '''
1440        Method to find which of the lists actually has data in it
1441        '''
1442        def findTheListData(self,xpathExtraction):
1443               
1444                cnt = 0
1445                mapping = []
1446               
1447                       
1448                for subList in xpathExtraction:
1449                        subListLen = len(subList)
1450                        if subListLen > 0:
1451                                #found where the data is, return sublist                               
1452                                mapping.append(cnt)     
1453                        cnt += 1                       
1454                                                               
1455                #how many values are there
1456                if len(mapping) == 1:
1457                        return mapping[0]
1458                else:
1459                        logging.warn("More than 1 value available - for now, have taken the first!")
1460                        return mapping[0]
1461               
1462               
1463       
1464       
1465       
Note: See TracBrowser for help on using the repository browser.