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

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

Couple of small fixes.

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