source: exist/trunk/python/ndgUtils/models/Atom.py @ 4286

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/exist/trunk/python/ndgUtils/models/Atom.py@4286
Revision 4286, 29.8 KB checked in by cbyrom, 11 years ago (diff)

Add new db structure for deployment atoms + add subtype term ID to atom.

Line 
1'''
2 Class representing data in  atom format - allowing moles data to be stored and accessed in a web feed compatible way
3 
4 @author: C Byrom, Tessella Jun 2008
5'''
6try: #python 2.5
7    from xml.etree import cElementTree as ET
8except ImportError:
9    try:
10        # if you've installed it yourself it comes this way
11        import cElementTree as ET
12    except ImportError:
13        # if you've egged it this is the way it comes
14        from ndgUtils.elementtree import cElementTree as ET
15import sys, logging, re, datetime
16from ndgUtils.eXistConnector import eXistConnector
17from ndgUtils.ETxmlView import et2text
18from utilities import getTripleData, escapeSpecialCharacters, \
19    tidyUpParameters, getISO8601Date
20from ndgUtils.vocabtermdata import VocabTermData as VTD
21from ndgUtils.models import MolesEntity as ME
22
23
24class AtomError(Exception):
25    """
26    Exception handling for Atom class.
27    """
28    def __init__(self, msg):
29        logging.error(msg)
30        Exception.__init__(self, msg)
31
32
33class Person(object):
34    '''
35    Class representing atom author type data - with name, uri and role attributes
36    '''
37    AUTHOR_TYPE = 0
38    CONTRIBUTOR_TYPE = 1
39    RESPONSIBLE_PARTY_TYPE = 2
40    ELEMENT_NAMES = ["author", "contributor", "responsibleParty"]
41   
42    def __init__(self, personType = AUTHOR_TYPE, namespace = None):
43        self.type = personType
44        self.ns = namespace
45        self.name = ""
46        self.uri = ""
47        self.role = ""
48       
49        # NB, the atom format specifies slightly different data contents
50        self.uriTagName = "email"
51        # NB, responsible party data is always stored in the moles section
52        if self.type == self.RESPONSIBLE_PARTY_TYPE:
53            self.ns = 'moles'
54            self.uriTagName = "uri"
55
56    def __str__(self):
57        if self.name or self.uri or self.role:
58            return self.name + " | " + self.uri + " | " + self.role
59        return ""
60   
61    def fromString(self, personString):
62        (self.name, self.uri, self.role) = getTripleData(personString)
63       
64    def fromETElement(self, personTag):
65        self.name = personTag.findtext('name') or ""
66        self.role = personTag.findtext('role') or ""
67        self.uri = personTag.findtext(self.uriTagName) or ""
68        logging.debug("Added name: '%s', role: '%s', %s: '%s'" \
69                      %(self.name, self.role, self.uriTagName, self.uri))
70
71    def toXML(self):
72        prefix = ""
73        if self.ns:
74            prefix = self.ns + ':'
75
76        author = ET.Element(prefix + self.ELEMENT_NAMES[self.type])
77
78        if self.name:
79            name = ET.SubElement(author, prefix + "name")
80            name.text = self.name
81       
82        if self.uri:
83            uri = ET.SubElement(author, prefix + self.uriTagName)
84            uri.text = self.uri
85       
86        if self.role:
87            role = ET.SubElement(author, prefix + "role")
88            role.text = self.role
89
90        return author
91   
92    def __cmp__(self, person1):
93        '''
94        Override comparison to allow proper object comparison when checking
95        if Person objects are in an array already - i.e. if person in personArray...
96        '''
97        if self is person1:
98            return 0
99        elif self.uri == person1.uri and self.name == person1.name and \
100                self.role == person1.role and self.type == person1.type:
101            return 0
102        return 1
103
104class Link(object):
105    '''
106    Class representing an atom link - with href, title and rel attributes
107    '''
108
109    def __init__(self):
110        self.href = ""
111        self.title = ""
112        self.rel = ""
113
114    def fromString(self, linkString):
115        (self.href, self.title, self.rel) = getTripleData(linkString)
116       
117    def fromETElement(self, linkTag):
118        self.href = linkTag.attrib.get('href') or ""
119        self.rel = linkTag.attrib.get('rel') or ""
120        self.title = linkTag.attrib.get('title') or ""
121
122    def toXML(self):
123        link = ET.Element("link")
124        link.attrib["href"] = self.href
125        link.attrib["title"] = self.title
126        link.attrib["rel"] = self.rel
127        return link
128
129    def hasValue(self):
130        # NB, just a rel on its own is meaningless - so ignore
131        if self.href or self.title:
132            return True
133        return False
134   
135
136    def __str__(self):
137        if self.href or self.title or self.rel:
138            return self.href + " | " + self.title + " | " + self.rel
139        return ""
140
141
142class Category(object):
143    '''
144    Class representing an atom category - with term, scheme and label attributes
145    '''
146    def __init__(self):
147        self.term = ""
148        self.scheme = ""
149        self.label = ""
150
151    def fromString(self, linkString):
152        (self.label, self.scheme, self.term) = getTripleData(linkString)
153       
154    def fromETElement(self, linkTag):
155        self.term = linkTag.attrib.get('term') or ""
156        self.label = linkTag.attrib.get('label') or ""
157        self.scheme = linkTag.attrib.get('scheme') or ""
158
159    def toXML(self):
160        link = ET.Element("category")
161        link.attrib["term"] = self.term
162        link.attrib["scheme"] = self.scheme
163        link.attrib["label"] = self.label
164        return link
165
166
167class Atom(object):
168
169    # labels for use with the atom categories
170    ATOM_TYPE = "ATOM_TYPE"
171    ATOM_SUBTYPE = "ATOM_SUBTYPE"
172
173    # labels for use with the templates to set/extract specific inputs
174    ONLINE_REF_LABEL = "online_ref"
175    ATOM_REF_LABEL = "atom_ref"
176
177
178    def __init__(self, atomType = None, vocabTermData = None, ndgObject = None, \
179                 xmlString = None, state = eXistConnector.WORKING_COLLECTION_PATH, **inputs):
180        '''
181        Constructor - initialise the atom variables
182        '''
183        logging.info("Initialising atom")
184        if atomType:
185            logging.info(" - of type '%s'" %atomType)
186        self.atomTypeID = atomType
187
188        # some data have further subtypes specified
189        self.subtypeID = None # this should be the termID
190        self.subtype = None # and this should be the fully formed vocab URL
191       
192        self.ndgObject = ndgObject
193
194        self.atomName = None
195        self.files = []
196        self.author = None
197        self.contributors = []
198        self.atomAuthors = []
199        self.parameters = []
200        self.spatialData = []
201        self.temporalData = []
202        self.relatedLinks = []
203        self.summary = []
204        self.csmlFile = None
205        self.cdmlFile = None
206        # general variable to use for setting the atom content - NB, if a csmlFile is specified
207        # (either directly or via a cdmlFile specification), this will be the content by default
208        # for this purpose
209        self.contentFile = None     
210        self.logos = []
211        self.title = None
212        self.datasetID = None        # NB, the dataset id ends up in the atomName - <path><datasetID>.atom
213        self.atomID = None
214   
215        # boundary box info - to replace spatial/temporalData?
216        self.minX = None
217        self.minY = None
218        self.maxX = None
219        self.maxY = None
220        self.t1 = None
221        self.t2 = None
222
223        self.ME = ME.MolesEntity()
224       
225        # date when the atom was first ingested
226        self.publishedDate = None
227
228        # last update date
229        self.updatedDate = None
230
231        # assume atom in working state by default - this is used to define what collection
232        # in eXist the atom is stored in
233        self.state = state
234       
235        # additional, non standard atom data can be included in the molesExtra element
236        if vocabTermData:
237            self.VTD = vocabTermData
238        else:
239            self.VTD = VTD()
240       
241        if xmlString:
242            self.fromString(xmlString)
243
244        # if inputs passed in as dict, add these now
245        if inputs:
246            logging.info("Adding info to atom from input dict")
247            logging.debug(inputs)
248            self.__dict__.update(inputs)
249           
250            # NB, this doesn't trigger the Summary Property, so do this
251            # explicitly, if need be
252            if inputs.has_key('Summary'):
253                self.Summary = inputs.get('Summary')
254           
255            # also pass any moles data up to the moles entity object
256            if inputs.get('providerID'):
257                self.ME.providerID = inputs.get('providerID')
258
259        if self.atomTypeID:
260            self.atomTypeName = self.VTD.TERM_DATA[self.atomTypeID].title
261
262        logging.info("Atom initialised")
263
264
265    def getDefaultCollectionPath(self):
266        '''
267        Determine the correct collection to use for the atom in eXist
268        '''
269        collectionPath = eXistConnector.BASE_COLLECTION_PATH + self.state
270       
271        if self.atomTypeID == VTD.DE_TERM:
272            collectionPath += eXistConnector.DE_COLLECTION_PATH
273        elif self.atomTypeID == VTD.GRANULE_TERM:
274            collectionPath += eXistConnector.GRANULE_COLLECTION_PATH
275        elif self.atomTypeID == VTD.ACTIVITY_DEPLOYMENT_TERM:
276            collectionPath += eXistConnector.DEPLOYMENTS_COLLECTION_PATH
277        else:
278            collectionPath += eXistConnector.DEPLOYMENT_COLLECTION_PATH
279       
280        if not self.ME.providerID:
281            raise AtomError("Error: cannot determine atom collection path because " + \
282                            "the provider ID is not defined")
283           
284        collectionPath += self.ME.providerID + "/"
285        return collectionPath
286           
287
288    def __addAtomTypeDataXML(self, root):
289        '''
290        Add the atom type, and subtype data, if available, to atom categories
291        - and lookup and add the appropriate vocab term data
292        '''
293        if self.atomTypeID:
294            logging.info("Adding atom type info to XML output")
295            category = Category()
296            category.label = self.atomTypeID
297            # look up the appropriate vocab term data
298            category.scheme = self.VTD.getTermCurrentVocabURL(self.atomTypeID)
299            category.term = self.ATOM_TYPE
300            root.append(category.toXML())
301
302        if self.subtypeID:
303            logging.info("Adding atom subtype info to XML output")
304            # NB subtypes not all defined, so leave this out for the moment
305            category.label = self.subtypeID
306            # look up the appropriate vocab term data
307            category.scheme = self.VTD.getTermCurrentVocabURL(self.subtypeID)
308            category.term = self.ATOM_SUBTYPE
309            root.append(category.toXML())
310
311
312    def addMolesEntityData(self, abbreviation, provider_id, object_creation_time):
313        '''
314        Add data to include in the moles entity element
315        '''
316        logging.debug('Adding moles entity information')
317        self.ME.abbreviation = abbreviation
318        self.ME.providerID = provider_id
319        self.ME.createdDate = getISO8601Date(object_creation_time)
320        logging.debug('Moles entity information added')
321
322
323    def addAuthors(self, authors):
324        '''
325        Add author data appropriately to the atom
326        NB, these will overwrite any existing authors of the same type
327        @param authors: list of Person objects with the author data
328        '''
329        logging.debug('Adding authors data to Atom')
330        isFirstAuthor = {}
331        authorArray = None
332        for author in authors:
333            # NB, we're only allowed one atom author
334            if author.type == Person.AUTHOR_TYPE:
335                self.author = author
336                if isFirstAuthor.has_key(author.type):
337                    raise AtomError("Error: an atom can only have one author specified")
338                isFirstAuthor[author.type] = 1
339                continue
340            elif author.type == Person.CONTRIBUTOR_TYPE:
341                authorArray = self.contributors
342            elif author.type == Person.RESPONSIBLE_PARTY_TYPE:
343                authorArray = self.ME.responsibleParties
344               
345            # check if this is the first addition - if so, clear out the
346            # array in advance
347            if not isFirstAuthor.has_key(author.type):
348                logging.debug("Clearing out author array")
349                # NB, need to be careful to clear the array, not create a ref
350                # to a new array
351                del authorArray[:]
352                isFirstAuthor[author.type] = 1
353
354            if str(author) != "" and author not in authorArray:
355                logging.debug("Adding author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
356                              %(author.type, author.name, author.uri, author.role))
357                authorArray.append(author)
358
359        logging.debug('Finished adding authors data')
360
361
362    def _isNewParameter(self, param):
363        '''
364        Check if a parameter is already specified in the atom, return False if
365        so, otherwise return True
366        '''
367        for p in self.parameters:
368            if p.term == param.term and \
369                p.scheme == param.scheme and \
370                p.label == param.label:
371                return False
372        return True
373
374
375    def addRelatedLinks(self, linkVals):
376        '''
377        Add related links in string format - converting to Link objects
378        @param linkVals: string of format, 'uri | title | vocabServerURL'
379        '''
380        self.relatedLinks.append(self.objectify(linkVals, 'relatedLinks'))
381
382
383    def addLogos(self, logoVals):
384        '''
385        Add related logos in string format - converting to Link objects
386        @param linkVals: string of format, 'uri | title | vocabServerURL'
387        '''
388        self.relatedLinks.append(self.objectify(logoVals, 'logo'))
389
390
391    def addParameters(self, params):
392        '''
393        Add a parameter to list - ensuring it is unique and has been formatted and tidied appropriately
394        @params param: parameter, as string array, to add to atom parameters collection
395        '''
396        # avoid strings being parsed character by character
397        if type(params) is str:
398            params = [params]
399           
400        for param in params:
401            # firstly tidy parameter
402            param = tidyUpParameters(param)
403            category = Category()
404            category.fromString(param)
405
406            # now check for uniqueness
407            if self._isNewParameter(category):
408                logging.debug("Adding new parameter: %s" %param)
409                self.parameters.append(category)
410   
411   
412    def _linksToXML(self, root):
413        '''
414        Add required links to the input element
415        @param root: element to add links to - NB, should be the root element of the atom
416        '''
417        selfLink = ET.SubElement(root, "link")
418        selfLink.attrib["href"] = self.atomBrowseURL
419        selfLink.attrib["rel"] = "self"
420        molesLink = ET.SubElement(root, "link")
421        molesDoc = re.sub('ATOM','NDG-B1', self.atomBrowseURL)
422        molesLink.attrib["href"] = molesDoc
423        molesLink.attrib["rel"] = "related"
424       
425        for relatedLink in self.relatedLinks:
426            if relatedLink.hasValue():
427                root.append(relatedLink.toXML())
428       
429        for logo in self.logos:
430            if logo.hasValue():
431                root.append(logo.toXML())
432   
433    def toXML(self):
434        '''
435        Convert the atom into XML representation and return this
436        @return: xml version of atom
437        '''
438        logging.info("Creating formatted XML version of Atom")
439        root = ET.Element("entry")
440        root.attrib["xmlns"] = "http://www.w3.org/2005/Atom"
441        root.attrib["xmlns:moles"] = "http://ndg.nerc.ac.uk/schema/moles2alpha"
442        root.attrib["xmlns:georss"] = "http://www.georss.org/georss"
443        root.attrib["xmlns:gml"] = "http://www.opengis.net/gml"
444        id = ET.SubElement(root, "id")
445        id.text = self.atomID
446        title = ET.SubElement(root, "title")
447        title.text = self.title
448        self._linksToXML(root)
449
450        # NB, the author tag is mandatory for atoms - so if an explicit
451        # author has not been set, just take the author to be the provider
452        if not self.author:
453            author = Person()
454            author.name = self.ME.providerID
455            author.uri = self.ME.providerID
456            self.author = author
457
458        root.append(self.author.toXML())
459           
460        for contributor in self.contributors:
461            root.append(contributor.toXML())
462
463        # add the moles entity section, if it is required
464        if self.ME:
465            root.append(self.ME.toXML())
466
467        # add parameters data
468        for param in self.parameters:
469            root.append(param.toXML())
470
471        # add the type and subtype data
472        self.__addAtomTypeDataXML(root)
473                   
474        summary = ET.SubElement(root, "summary")
475        summary.text = escapeSpecialCharacters(self.Summary)
476
477        # add link to content, if required - NB, can only have one content element in atom
478        # - and this is mandatory
479        content = ET.SubElement(root, "content")
480        if self.contentFile:
481            content.attrib["type"] = "application/xml"
482            content.attrib["src"] = self.contentFile
483        else:
484            content.text = "Metadata document"
485       
486        # if there's a published date already defined, assume we're doing an update now
487        # NB, update element is mandatory
488        currentDate = datetime.datetime.today().strftime("%Y-%m-%dT%H:%M:%SZ")
489        if not self.publishedDate:
490            self.publishedDate = currentDate
491
492        updated = ET.SubElement(root, "updated")
493        if not self.updatedDate:
494            self.updatedDate = currentDate
495        updated.text = self.updatedDate
496
497        published = ET.SubElement(root, "published")
498        published.text = self.publishedDate
499
500        # add temporal range data, if available
501        temporalRange = ET.SubElement(root, "moles:temporalRange")
502        if self.t1:
503            temporalRange.text = self.t1
504            if self.t2:
505                temporalRange.text += "/" + self.t2
506
507        # add spatial range data, if available
508        self._addSpatialData(root)
509
510        tree = ET.ElementTree(root)
511        logging.info("XML version of Atom created")
512        return tree
513
514
515    def __getSummary(self):
516        logging.debug("Getting summary data")
517        summaryString = ""
518        for summary_line in self.summary:
519            summaryString += summary_line + "\n"
520
521        return summaryString
522
523    def __setSummary(self, summary):
524        logging.debug("Adding summary data")
525        self.summary = []
526        for summary_line in summary.split('\n'):
527            self.summary.append(summary_line)
528           
529    Summary = property(fset=__setSummary, fget=__getSummary, doc="Atom summary")
530
531           
532    def fromString(self, xmlString):
533        '''
534        Initialise Atom object using an xmlString
535        @param xmlString: representation of atom as an XML string
536        '''
537        logging.info("Ingesting data from XML string")
538       
539        # firstly, remove any namespaces used - to avoid problems with elementtree
540        logging.debug("Stripping moles namespace from string to allow easy handling with elementtree")
541        xmlString = xmlString.replace('moles:', '')
542        xmlString = xmlString.replace('georss:', '')
543        xmlString = xmlString.replace('gml:', '')
544        xmlString = xmlString.replace('xmlns="http://www.w3.org/2005/Atom"', '')
545
546        # now create elementtree with the XML string
547        logging.debug("Create elementtree instance with XML string")
548        tree = ET.fromstring(xmlString)
549       
550        title = tree.findtext('title')
551        if title:
552            logging.debug("Adding title data")
553            self.title = title
554
555        summary = tree.findtext('summary')
556        if summary:
557            self.Summary = summary
558
559        authorElement = tree.find('author')
560        logging.debug("Adding author data")
561        author = Person()
562        author.fromETElement(authorElement)
563        self.author = author
564
565        contributorElements = tree.findall('contributor')
566        for contributorElement in contributorElements:
567            logging.debug("Adding contributor data")
568            contributor = Person(personType = Person.CONTRIBUTOR_TYPE)
569            contributor.fromETElement(contributorElement)
570            self.contributors.append(contributor)
571
572        molesElement = tree.find('entity')
573        if molesElement:
574            self.ME.fromET(molesElement)
575               
576        self.atomID = tree.findtext('id')
577
578        self._parseCategoryData(tree.findall('category'))
579
580        self._parseLinksData(tree.findall('link'))
581           
582        contentTag = tree.find('content')
583        if contentTag != None:
584            logging.debug("Found content tag - checking for CSML/CDML file data")
585            file = contentTag.attrib.get('src')
586            if file:
587                # NB, the path will reveal more reliably whether we're dealing with CSML and CDML files
588                if file.upper().find('CSML') > -1:
589                    logging.debug("Adding CSML file data")
590                    self.csmlFile = file
591                elif file.upper().find('CDML') > -1:
592                    logging.debug("Adding CDML file data")
593                    self.cdmlFile = file
594                self.contentFile = file
595       
596        range = tree.findtext('temporalRange')
597        if range:
598            logging.debug("Adding temporal range data")
599            timeData = range.split('/')
600            self.t1 = timeData[0]
601            if len(timeData) > 1:
602                self.t2 = timeData[1]
603       
604        # NB, this parser won't mind if we're dealing with Envelope or EnvelopeWithTimePeriod
605        minBBox = tree.findall('.//lowerCorner')
606        if minBBox:
607            logging.debug("Adding min spatial range data")
608            minBBox = minBBox[0]
609            spatialData = minBBox.text.split()
610            self.minX = spatialData[0]
611            if len(spatialData) > 1:
612                self.minY = spatialData[1]
613       
614        maxBBox = tree.findall('.//upperCorner')
615        if maxBBox:
616            maxBBox = maxBBox[0]
617            logging.debug("Adding max spatial range data")
618            spatialData = maxBBox.text.split()
619            self.maxX = spatialData[0]
620            if len(spatialData) > 1:
621                self.maxY = spatialData[1]
622               
623        publishedDate = tree.findtext('published')
624        if publishedDate:
625            logging.debug("Adding published date")
626            self.publishedDate = publishedDate
627               
628        updatedDate = tree.findtext('updated')
629        if updatedDate:
630            logging.debug("Adding updated date")
631            self.updatedDate = updatedDate
632           
633        logging.info("Completed data ingest")
634   
635   
636    def _parseCategoryData(self, categories):
637        logging.debug("Adding category/parameters data")
638        for category in categories:
639            cat = Category()
640            cat.fromETElement(category)
641           
642            if cat.term == self.ATOM_TYPE:
643                logging.debug("Found atom type data")
644                self.atomTypeID = cat.label
645                self.atomTypeName = self.VTD.TERM_DATA[cat.label].title
646                continue
647            elif cat.term == self.ATOM_SUBTYPE:
648                logging.debug("Found atom subtype data")
649                self.subtypeID = cat.label
650                self.subtype = cat.scheme
651                continue
652
653            self.parameters.append(cat)
654   
655
656    def setDatasetID(self, datasetID):
657        '''
658        Set the dataset ID for the atom - and generate an appropriate atom name using this
659        @param datasetID: ID to set for the atom
660        '''
661        self.datasetID = datasetID
662        self._generateAtomName(datasetID) 
663        self.atomID = self.createAtomID(datasetID)
664
665
666    def createAtomID(self, datasetID):
667        '''
668        Create a unique ID, conforming to atom standards, for atom
669        NB, see http://diveintomark.org/archives/2004/05/28/howto-atom-id
670        @param datasetID: ID of atom's dataset
671        @return: unique ID
672        '''
673        logging.info("Creating unique ID for atom")
674        if not self.atomBrowseURL:
675            self._generateAtomName(datasetID)
676        urlBit = self.atomBrowseURL.split('://')[1]
677        urlBit = urlBit.replace('#', '')
678        urlBits = urlBit.split('/')
679        dateBit = datetime.datetime.today().strftime("%Y-%m-%d")
680       
681        id = "tag:" + urlBits[0] + "," + dateBit + ":/" + "/".join(urlBits[1:])
682        logging.info("- unique ID created for atom")
683        logging.debug(" - '%s'" %id)
684        return id
685       
686       
687    def _generateAtomName(self, datasetID):
688        '''
689        Generate a consistent name for the atom - with full eXist doc path
690        @param datasetID: ID of atom's dataset
691        '''
692        self.atomName = datasetID + ".atom"
693        self.ndgURI = self.ME.providerID + "__ATOM__" + datasetID
694        self.atomBrowseURL = VTD.BROWSE_ROOT_URL + self.ndgURI
695
696
697    def _parseLinksData(self, links):
698        '''
699        Extract links and atom data from array of link elements in the XML representation of the atom
700        @param links: an array of <link> elements
701        '''
702        # firstly, get all data to start with, so we can properly process it afterwards
703        linkData = {}
704        logging.debug("Getting link data")
705        for linkTag in links:
706            link = Link()
707            link.fromETElement(linkTag)
708
709            if not linkData.has_key(link.rel):
710                linkData[link.rel] = []
711               
712            if link.title == VTD.TERM_DATA[VTD.LOGO_TERM].title:
713                self.logos.append(link)
714            else:
715                linkData[link.rel].append(link)
716
717        # there should be one self referencing link - which will provide info on the atom itself
718        if not linkData.has_key('self'):
719            errorMessage = "Atom does not have self referencing link - " + \
720                "cannot ascertain datasetID without this - please fix"
721            logging.error(errorMessage)
722            raise ValueError(errorMessage)
723       
724        # this is the link describing the atom itself
725        self.atomBrowseURL = linkData['self'][0].href
726       
727        self.datasetID = self.atomBrowseURL.split("__ATOM__")[-1]
728        self.atomName = self.datasetID + ".atom"
729        self.ndgURI = self.atomBrowseURL.split(VTD.BROWSE_ROOT_URL)[1]
730       
731        # now remove this value and the associated moles doc link
732        del linkData['self']
733        molesDoc = self.atomBrowseURL.replace('ATOM', 'NDG-B1')
734        if linkData.has_key('related'):
735            relatedLinks = []
736            for link in linkData['related']:
737                if link.href != molesDoc:
738                    relatedLinks.append(link)
739           
740            linkData['related'] = relatedLinks
741               
742        # now add the remaining links to the atom
743        for key in linkData:
744            for link in linkData[key]:
745                logging.debug("Adding link data")
746                self.relatedLinks.append(link)
747       
748
749    def _addSpatialData(self, element):
750        '''
751        Add spatial coverage element to an input element
752        @param element: element to add coverage data to
753        '''
754        logging.info("Adding spatial data to Atom")
755        bbox = ET.SubElement(element, "georss:where")
756        if not self.minX:
757            logging.info("No spatial data specified")
758            return
759       
760        envelope = ET.SubElement(bbox, "gml:Envelope")
761        lc = ET.SubElement(envelope, "gml:lowerCorner")
762        lc.text = self.minX + " " + self.minY
763        uc = ET.SubElement(envelope, "gml:upperCorner")
764        uc.text = self.maxX + " " + self.maxY
765
766       
767    def setAttribute(self, attributeName, attributeValue):
768        '''
769        Set the value of an atom attribute - and do some basic tidying up of the string content
770        - to escape any XML unfriendly characters
771        @param attributeName: name of the attribute whose value to set
772        @param attributeValue: value to set the attribute to 
773        '''
774        logging.debug("Setting attribute, %s, to %s" %(attributeName, attributeValue))
775        origValue = attributeValue
776       
777        # escape any special characters if a value has been specified
778        # NB, need to cope with both single values and arrays
779        if attributeValue:
780            if type(attributeValue) is list:
781                newVals = []
782                for val in attributeValue:
783                    newVals.append(objectify(escapeSpecialCharacters(val)), attributeName)
784                attributeValue = newVals
785                   
786            else:
787                attributeValue = objectify(escapeSpecialCharacters(attributeValue), attributeName)
788
789        # handle the special case of authors; only one author is allowed per atom
790        # - the others should be treated as contributors
791        if attributeName == "authors":
792            setattr(self, "author", attributeValue[0])
793            if len(attributeValue) > 1:
794                setattr(self, "contributors", attributeValue[1:])
795        elif attributeName == "atomAuthors":
796            self.ME.responsibleParties.append(attributeValue)
797        else:
798            setattr(self, attributeName, attributeValue)
799
800
801    def objectify(self, objectVals, attributeName):
802        '''
803        Some inputs are specified as strings but need to be converted into
804        objects - do this here
805        @param objectVals: a '|' delimited string of values
806        @param attributeName: name of attribute the values belong to
807        '''
808        obj = None
809        if type(objectVals) != str:
810            return objectVals
811       
812        if attributeName == "relatedLinks" or attributeName == "logo":
813            obj = Link()
814        elif attributeName == "atomAuthors" or attributeName == "authors":
815            obj = Person()
816
817        if obj:
818            obj.fromString(objectVals)
819            return obj
820       
821        return objectVals
822
823
824    def toPrettyXML(self):
825        '''
826        Returns nicely formatted XML as string
827        '''
828        atomXML = self.toXML()
829
830        # create the string
831        logging.debug("Converting the elementtree object into a string")
832        prettyXML = et2text(atomXML.getroot())
833
834        # add XML version tag
835        prettyXML = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + prettyXML
836        logging.info("Created formatted version of XML object")
837        return prettyXML
Note: See TracBrowser for help on using the repository browser.