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

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

Add code to allow input/edit of online references + fix author/contributor
data to conform to the Atom 'email' convention - i.e. using an email
attribute instead of a uri one.

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