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

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

Updated keyword handling to allow proper detection and use of vocab list entered keywords. Note that only BODC and CEDA are currently using this so still need to keep old methods for other DCs

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