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

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

Added check so will not allow service records in

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