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

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

Tighten up atom data model to better handle online references + simplify
exist doc saving - don't return the updated doc as this will now be
retrieved by simply reloading the edit template.

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