source: ndgCommon/trunk/ndg/common/src/models/Atom.py @ 5170

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/ndgCommon/trunk/ndg/common/src/models/Atom.py@5170
Revision 5170, 53.0 KB checked in by cbyrom, 12 years ago (diff)

Adjust atom retrieval code to allow filtering of atoms in the 'working'
state - NB, when in browse mode, these atoms should not be available
for display.

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'''
6import sys, logging, re, datetime, urllib
7from xml.etree import cElementTree as ET
8import csml.parser as CsmlParser
9import ndg.common.src.clients.xmldb.eXist.dbconstants as dc
10from ndg.common.src.lib.ETxmlView import et2text
11import ndg.common.src.lib.utilities as utilities
12from ndg.common.src.models.vocabtermdata import VocabTermData as VTD
13from ndg.common.src.models import MolesEntity as ME
14from ndg.common.src.models import Deployment as Deployment
15from ndg.common.src.models import AtomState
16from ndg.common.src.models.ndgObject import ndgObject
17
18class AtomError(Exception):
19    """
20    Exception handling for Atom class.
21    """
22    def __init__(self, msg):
23        logging.error(msg)
24        Exception.__init__(self, msg)
25
26
27class Person(object):
28    '''
29    Class representing atom author type data - with name, uri and role attributes
30    @keyword personType: Type of person to create - specified using the Person.._Type
31    values.  Default is AUTHOR_TYPE.
32    @keyword namespace: a two value array of format, ['short_namespace_name', 'full_namespace_name']
33    - e.g. ['moles', 'http://ndg.nerc.ac.uk/schema/moles2beta']
34    '''
35    AUTHOR_TYPE = 0
36    CONTRIBUTOR_TYPE = 1
37    RESPONSIBLE_PARTY_TYPE = 2
38    ELEMENT_NAMES = ["author", "contributor", "responsibleParty"]
39   
40    def __init__(self, personType = AUTHOR_TYPE, namespace = None):
41        self.type = personType
42        if namespace:
43            self.ns_shortname = namespace[0]
44            self.ns_fullname = namespace[1]
45        else:
46            self.ns_shortname = ""
47            self.ns_fullname = ndgObject.ATOM_NS
48           
49        self.name = ""
50        self.uri = ""
51        self.role = ""
52       
53        # NB, the atom format specifies slightly different data contents
54        self.uriTagName = "email"
55        # NB, responsible party data is always stored in the moles section
56        if self.type == self.RESPONSIBLE_PARTY_TYPE:
57            self.ns_shortname = 'moles'
58            self.ns_fullname = ndgObject.MOLES_NS
59            self.uriTagName = "uri"
60
61    def __str__(self):
62        if self.name or self.uri or self.role:
63            return self.name + " | " + self.uri + " | " + self.role
64        return ""
65
66
67    def hasValue(self):
68        if self.name or self.uri or self.role:
69            return True
70        return False
71   
72    def fromString(self, personString):
73        (self.name, self.uri, self.role) = utilities.getTripleData(personString)
74       
75    def fromETElement(self, personTag):
76        self.name = personTag.findtext('{%s}name' %self.ns_fullname) or ""
77        self.role = personTag.findtext('{%s}role' %self.ns_fullname) or ""
78        self.uri = personTag.findtext('{%s}%s' %(self.ns_fullname, self.uriTagName)) or ""
79        logging.debug("Added name: '%s', role: '%s', %s: '%s'" \
80                      %(self.name, self.role, self.uriTagName, self.uri))
81
82    def toXML(self):
83        prefix = ""
84        if self.ns_shortname:
85            prefix = self.ns_shortname + ':'
86
87        author = ET.Element(prefix + self.ELEMENT_NAMES[self.type])
88
89        if self.name:
90            name = ET.SubElement(author, prefix + "name")
91            name.text = self.name
92       
93        if self.uri:
94            uri = ET.SubElement(author, prefix + self.uriTagName)
95            uri.text = self.uri
96       
97        if self.role:
98            role = ET.SubElement(author, prefix + "role")
99            role.text = self.role
100
101        return author
102   
103    def __cmp__(self, person1):
104        '''
105        Override comparison to allow proper object comparison when checking
106        if Person objects are in an array already - i.e. if person in personArray...
107        '''
108        if not person1:
109            return -1
110       
111        if self is person1:
112            return 0
113        elif self.uri == person1.uri and self.name == person1.name and \
114                self.role == person1.role and self.type == person1.type:
115            return 0
116        return 1
117
118
119class Link(object):
120    '''
121    Class representing an atom link - with href, title and rel attributes
122    '''
123
124    def __init__(self):
125        self.href = ""
126        self.title = ""
127        self.rel = ""
128
129    def fromString(self, linkString):
130        (self.href, self.title, self.rel) = utilities.getTripleData(linkString)
131       
132    def fromETElement(self, linkTag):
133        self.href = linkTag.attrib.get('href') or ""
134        self.rel = linkTag.attrib.get('rel') or ""
135        self.title = linkTag.attrib.get('title') or ""
136
137    def toXML(self):
138        link = ET.Element("link")
139        link.attrib["href"] = self.href
140        link.attrib["title"] = self.title
141        link.attrib["rel"] = self.rel
142        return link
143
144    def hasValue(self):
145        # NB, just a rel on its own is meaningless - so ignore
146        if self.href or self.title:
147            return True
148        return False
149   
150    def __str__(self):
151        if self.href or self.title or self.rel:
152            return self.href + " | " + self.title + " | " + self.rel
153        return ""
154   
155    def isChildAtom(self):
156        '''
157        Determines whether the link refers to another atom - e.g. a link to
158        a data granule
159        @return True, if so; False otherwise
160        '''
161        if self.rel.endswith(VTD.GRANULE_TERM) or \
162            self.rel.endswith(VTD.DEPLOYMENT_TERM) or \
163            self.rel.endswith(VTD.ACTIVITY_TERM) or \
164            self.rel.endswith(VTD.DPT_TERM) or \
165            self.rel.endswith(VTD.OBS_TERM):
166            return True
167       
168        return False
169   
170    def __cmp__(self, link1):
171        '''
172        Override comparison to allow proper object comparison when checking
173        if Link objects are in an array already - i.e. if link in linkArray...
174        '''
175        if not link1:
176            return -1
177       
178        if self is link1:
179            return 0
180        elif self.href == link1.href and self.title == link1.title and \
181                self.rel == link1.rel:
182            return 0
183        return 1
184
185
186class Category(object):
187    '''
188    Class representing an atom category - with term, scheme and label attributes
189    '''
190    def __init__(self):
191        self.term = ""
192        self.scheme = ""
193        self.label = ""
194
195    def fromString(self, linkString, escapeSpecialCharacters=True):
196        '''
197        Create Category from triple string of format, 'label | scheme | term'
198        @param linkString: triple string to create category with
199        @keyword escapeSpecialCharacters: if set to True, special characters in
200        triple string are escaped (default)
201        '''
202        (self.label, self.scheme, self.term) = utilities.getTripleData(linkString, \
203            doEscape=escapeSpecialCharacters)
204
205        # also replace any double quotes with single apostrophes - since this data
206        # is stored as an attribute - i.e. already surrounded by double quotes
207        self.label = self.label.replace("\"", "'")
208        self.scheme = self.scheme.replace("\"", "'")
209        self.term = self.term.replace("\"", "'")
210       
211       
212    def fromETElement(self, linkTag):
213        self.term = linkTag.attrib.get('term') or ""
214        self.label = linkTag.attrib.get('label') or ""
215        self.scheme = linkTag.attrib.get('scheme') or ""
216
217    def toXML(self):
218        link = ET.Element("category")
219        link.attrib["term"] = self.term
220        link.attrib["scheme"] = self.scheme
221        link.attrib["label"] = self.label
222        return link
223   
224    def hasValue(self):
225        if self.scheme or self.label or self.term:
226            return True
227        return False
228
229
230class Atom(object):
231
232    # labels for use with the atom categories
233    ATOM_TYPE = "ATOM_TYPE"
234    ATOM_SUBTYPE = "ATOM_SUBTYPE"
235
236    # labels for use with the templates to set/extract specific inputs
237    ONLINE_REF_LABEL = "online_ref"
238    PARAMETER_LABEL = "parameter"
239    ATOM_REF_LABEL = "atom_ref"
240    DELIMITER = "---"
241    REMOVE_LABEL = "remove"
242   
243    # format to use for t1-t2 date range
244    YEAR_FORMAT = '%Y-%m-%d'
245
246    # subtype name, when not defined
247    SUB_TYPE_NOT_DEFINED_NAME = "Not currently defined"
248
249    def __init__(self, atomType = None, vocabTermData = None, ndgObject = None, \
250                 xmlString = None, state = AtomState.WORKING_STATE, **inputs):
251        '''
252        Constructor - initialise the atom variables
253        @keyword atomType: type of atom to set up
254        @keyword vocabTermData: instance of VocabTermData object to use with atom
255        @keywork ndgObject: instance of ndgObject to use with atom
256        @keyword xmlString: XML representation of atom - will be parsed to populate
257        the atom data
258        @keyword state:  AtomState object representing the state of the atom
259        '''
260        logging.info("Initialising atom")
261        if atomType:
262            logging.info(" - of type '%s'" %atomType)
263        self.atomTypeID = atomType
264
265        # some data have further subtypes specified
266        self.subtypeID = None # this should be the termID
267        self.subtype = None # and this should be the fully formed vocab URL
268       
269        self.ndgObject = ndgObject
270
271        self.atomName = None
272        self.files = []
273        self.author = Person()
274        self.contributors = []
275        self.atomAuthors = []
276        self.parameters = []
277        self.spatialData = []
278        self.temporalData = []
279        self.relatedLinks = []
280        self.summary = []
281        self.content = []
282        # NB, this deployments data duplicates other atom data - and is only used for a
283        # convenient way to collect the info (by lookupAssociatedData()) for use in templates
284        self.deployments = []
285        # ditto for the following field
286        self.dataEntities = []
287           
288        self.csmlFile = None
289        self.cdmlFile = None
290        # general variable to use for setting the atom content - NB, if a csmlFile is specified
291        # (either directly or via a cdmlFile specification), this will be the content by default
292        # for this purpose
293        self.contentFile = None     
294        self.title = None
295        self.datasetID = None        # NB, the dataset id ends up in the atomName - <path><datasetID>.atom
296        self.atomID = None
297   
298        # boundary box info - to replace spatial/temporalData?
299        self.minX = None
300        self.minY = None
301        self.maxX = None
302        self.maxY = None
303        self.t1 = None
304        self.t2 = None
305
306        self.ME = ME.MolesEntity()
307       
308        # date when the atom was first ingested
309        self.publishedDate = None
310
311        # last update date
312        self.updatedDate = None
313
314        # assume atom in working state by default - this is used to define what collection
315        # in eXist the atom is stored in
316        self.state = state
317       
318        # additional, non standard atom data can be included in the molesExtra element
319        if vocabTermData:
320            self.VTD = vocabTermData
321        else:
322            self.VTD = VTD()
323       
324        if xmlString:
325            self.fromString(xmlString)
326
327        # if inputs passed in as dict, add these now
328        if inputs:
329            logging.info("Adding info to atom from input dict")
330            logging.debug(inputs)
331            self.__dict__.update(inputs)
332           
333            # NB, this doesn't trigger the Summary Property, so do this
334            # explicitly, if need be
335            if inputs.has_key('Summary'):
336                self.Summary = inputs.get('Summary')
337            if inputs.has_key('Content'):
338                self.Content = inputs.get('Content')
339            if inputs.has_key('author'):
340                name = inputs.get('author')
341                author = Person()
342                author.fromString(name)
343                self.author = author
344           
345            # also pass any moles data up to the moles entity object
346            if inputs.has_key('providerID'):
347                self.ME.providerID = inputs.get('providerID')
348               
349            if inputs.has_key('abbreviation'):
350                self.ME.abbreviation = inputs.get('abbreviation')
351
352        if self.atomTypeID:
353            self.atomTypeName = self.VTD.TERM_DATA[self.atomTypeID].title
354
355
356        self.deploymentsURL = ""
357        self.dataEntitiesURL = ""
358
359        logging.info("Atom initialised")
360
361
362    def addOnlineReferences(self, links):
363        '''
364        Add online reference data associated with the atom
365        - NB, care needs to be taken here since this data is stored in the atom
366        link elements and these are also used for the various atom associations
367        @param links: a Link or array of Links to add to the relatedLinks attribute
368        '''
369        logging.debug("Adding online references")
370        if not links:
371            return
372       
373        if type(links) is not list:
374            links = [links]
375       
376        # firstly clear out any online refs data from the existing related links
377        newLinks = []
378        for link in self.relatedLinks:
379            if link.isChildAtom():
380                newLinks.append(link)
381       
382        newLinks.extend(links)
383        for link in newLinks:
384           link.href = urllib.quote(link.href, '%')
385        self.relatedLinks = newLinks
386        logging.debug("Online references added")
387
388
389    def addUniqueRelatedLinks(self, links):
390        '''
391        Add links to relatedLinks array - if they are not already included
392        @param links: a Link or array of Links to add to the relatedLinks attribute
393        '''
394        self.addUniqueLinks(self.relatedLinks, links)
395       
396
397    def removeRelatedLinks(self, linksToDelete):
398        '''
399        Remove any links in the input list from the atom's related links list
400        @param linksToDelete: array of Link objects to remove from atom
401        '''
402        logging.debug("Removing related links from atom")
403        if not linksToDelete:
404            return
405       
406        if type(linksToDelete) is not list:
407            linksToDelete = [linksToDelete]
408       
409        updatedLinks = []
410        for link in self.relatedLinks:
411            if type(link) is not Link:
412                logging.warning("Link is not of 'Link' object type (type='%s') - skipping" %type(link))
413                continue
414            if link in linksToDelete:
415                logging.debug("- found link to remove")
416            else:
417                updatedLinks.append(link)
418
419        self.relatedLinks = updatedLinks
420        logging.debug("Links removed")
421
422    def getPublicationStatePath(self):
423        '''
424        Determine the correct publication state collection for the atom
425        @return collectionPath: collection path for the publication state of the atom
426        '''
427        logging.debug("Getting collection path for atom publication state")
428        collectionPath = dc.ATOM_COLLECTION_PATH + self.state.collectionPath
429        logging.debug("Returning publication state collection, '%s'" %collectionPath)
430        return collectionPath
431       
432
433    def getDefaultEntityCollectionPath(self):
434        '''
435        Determine the correct collection for the entity type of the atom
436        @return entityPath: collection path for the data type of the atom
437        '''
438        logging.debug("Getting collection path for atom entity type")
439        collectionPath = self.getPublicationStatePath()
440       
441        if self.atomTypeID == VTD.DE_TERM:
442            collectionPath += dc.DE_COLLECTION_PATH
443        elif self.atomTypeID == VTD.GRANULE_TERM:
444            collectionPath += dc.GRANULE_COLLECTION_PATH
445        elif self.atomTypeID == VTD.ACTIVITY_TERM and \
446            self.subtypeID == VTD.DEPLOYMENT_TERM:
447            collectionPath += dc.DEPLOYMENTS_COLLECTION_PATH
448        else:
449            collectionPath += dc.DEPLOYMENT_COLLECTION_PATH
450       
451        logging.debug("Returning entity collection, '%s'" %collectionPath)
452        return collectionPath
453       
454
455    def getDefaultCollectionPath(self):
456        '''
457        Determine the correct collection to use for the atom in eXist
458        '''
459        logging.debug("Getting default collection path for atom")
460        collectionPath = self.getDefaultEntityCollectionPath()
461        if not self.ME.providerID:
462            raise AtomError("Error: cannot determine atom collection path because " + \
463                            "the provider ID is not defined")
464           
465        collectionPath += self.ME.providerID + "/"
466        logging.debug("Returning collection, '%s'" %collectionPath)
467        return collectionPath
468
469
470    def __addAtomTypeDataXML(self, root):
471        '''
472        Add the atom type, and subtype data, if available, to atom categories
473        - and lookup and add the appropriate vocab term data
474        '''
475        if self.atomTypeID:
476            logging.info("Adding atom type info to XML output")
477            category = Category()
478            category.label = self.atomTypeID
479            # look up the appropriate vocab term data
480            category.scheme = self.VTD.getTermCurrentVocabURL(self.atomTypeID)
481            category.term = self.ATOM_TYPE
482            root.append(category.toXML())
483
484        if self.subtypeID:
485            logging.info("Adding atom subtype info to XML output")
486            # NB subtypes not all defined, so leave this out for the moment
487            category.label = self.subtypeID
488            # look up the appropriate vocab term data
489            category.scheme = self.VTD.getTermCurrentVocabURL(self.subtypeID)
490            category.term = self.ATOM_SUBTYPE
491            root.append(category.toXML())
492
493
494    def addMolesEntityData(self, abbreviation, provider_id, object_creation_time):
495        '''
496        Add data to include in the moles entity element
497        '''
498        logging.debug('Adding moles entity information')
499        self.ME.abbreviation = abbreviation
500        self.ME.providerID = provider_id
501        self.ME.createdDate = utilities.getISO8601Date(object_creation_time)
502        logging.debug('Moles entity information added')
503
504
505    def addAuthors(self, authors):
506        '''
507        Add author data appropriately to the atom
508        NB, these will overwrite any existing authors of the same type
509        @param authors: list of Person objects with the author data
510        '''
511        logging.debug('Adding authors data to Atom')
512        isFirstAuthor = {}
513        authorArray = None
514        for author in authors:
515            # NB, we're only allowed one atom author
516            if author.type == Person.AUTHOR_TYPE:
517                self.author = author
518                   
519                if isFirstAuthor.has_key(author.type):
520                    raise AtomError("Error: an atom can only have one author specified")
521                isFirstAuthor[author.type] = 1
522                continue
523            elif author.type == Person.CONTRIBUTOR_TYPE:
524                authorArray = self.contributors
525            elif author.type == Person.RESPONSIBLE_PARTY_TYPE:
526                authorArray = self.ME.responsibleParties
527               
528            # check if this is the first addition - if so, clear out the
529            # array in advance
530            if not isFirstAuthor.has_key(author.type):
531                logging.debug("Clearing out author array")
532                # NB, need to be careful to clear the array, not create a ref
533                # to a new array
534                del authorArray[:]
535                isFirstAuthor[author.type] = 1
536
537            if author.hasValue() and author not in authorArray:
538                logging.debug("Adding author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
539                              %(author.type, author.name, author.uri, author.role))
540                authorArray.append(author)
541
542        logging.debug('Finished adding authors data')
543
544
545    def _isNewParameter(self, param):
546        '''
547        Check if a parameter is already specified in the atom, return False if
548        so, otherwise return True
549        '''
550        for p in self.parameters:
551            if p.term == param.term and \
552                p.scheme == param.scheme and \
553                p.label == param.label:
554                return False
555        return True
556
557
558    def addRelatedLinks(self, linkVals):
559        '''
560        Add related links in string format - converting to Link objects
561        NB, only add the link if it is unique
562       
563        @param linkVals: string of format, 'uri | title | vocabServerURL'
564        '''
565        link = self.objectify(linkVals, 'relatedLinks')
566        if link not in self.relatedLinks:
567            self.relatedLinks.append(link)
568
569
570    def addParameters(self, params):
571        '''
572        Add a parameter to list - ensuring it is unique and has been formatted and tidied appropriately
573        @params param: parameter, as string array, to add to atom parameters collection
574        '''
575        # avoid strings being parsed character by character
576        if type(params) is str:
577            params = [params]
578           
579        for param in params:
580            # firstly tidy parameter
581            param = utilities.tidyUpParameters(param)
582            category = Category()
583            # NB, data already tidied up here, so set keyword to avoid this happening again
584            category.fromString(param, escapeSpecialCharacters=True)
585
586            # now check for uniqueness
587            if self._isNewParameter(category):
588                logging.debug("Adding new parameter: %s" %param)
589                self.parameters.append(category)
590   
591   
592    def _linksToXML(self, root):
593        '''
594        Add required links to the input element
595        @param root: element to add links to - NB, should be the root element of the atom
596        '''
597        selfLink = ET.SubElement(root, "link")
598        selfLink.attrib["href"] = self.atomBrowseURL
599        selfLink.attrib["rel"] = "self"
600       
601        for relatedLink in self.relatedLinks:
602            if relatedLink.hasValue():
603                root.append(relatedLink.toXML())
604   
605    def toXML(self):
606        '''
607        Convert the atom into XML representation and return this
608        @return: xml version of atom
609        '''
610        logging.info("Creating formatted XML version of Atom")
611        root = ET.Element("entry")
612        root.attrib["xmlns"] = ndgObject.ATOM_NS
613        root.attrib["xmlns:moles"] = ndgObject.MOLES_NS
614        root.attrib["xmlns:georss"] = ndgObject.GEOSS_NS
615        root.attrib["xmlns:gml"] = ndgObject.GML_NS
616        id = ET.SubElement(root, "id")
617        id.text = self.atomID
618        title = ET.SubElement(root, "title")
619        title.text = self.title
620        self._linksToXML(root)
621
622        if self.author and self.author.hasValue():
623            root.append(self.author.toXML())
624           
625        for contributor in self.contributors:
626            root.append(contributor.toXML())
627
628        # add parameters data
629        for param in self.parameters:
630            if param.hasValue():
631                root.append(param.toXML())
632
633        # add the type and subtype data
634        self.__addAtomTypeDataXML(root)
635                   
636        summary = ET.SubElement(root, "summary")
637        summary.text = self.Summary
638                   
639        # add link to content, if required - NB, can only have one content element in atom
640        # - and this is mandatory
641        content = ET.SubElement(root, "content")
642        contentFile = self.contentFile or self.csmlFile or self.cdmlFile
643        if contentFile:
644            content.attrib["type"] = "application/xml"
645            content.attrib["src"] = contentFile
646        else:
647            content.attrib["type"] = "xhtml"
648            div = ET.SubElement(content, 'div')
649            div.attrib["xmlns:xhtml"] = ndgObject.XHTML_NS
650            div.text = self.Content
651       
652        # if there's a published date already defined, assume we're doing an update now
653        # NB, update element is mandatory
654        currentDate = datetime.datetime.today().strftime("%Y-%m-%dT%H:%M:%SZ")
655        if not self.publishedDate:
656            self.publishedDate = currentDate
657
658        updated = ET.SubElement(root, "updated")
659        if not self.updatedDate:
660            self.updatedDate = currentDate
661        updated.text = self.updatedDate
662
663        published = ET.SubElement(root, "published")
664        published.text = self.publishedDate
665
666        # add the moles entity section, if it is required
667        if self.ME:
668            root.append(self.ME.toXML())
669
670        # add temporal range data, if available
671        temporalRange = ET.SubElement(root, "moles:temporalRange")
672        if self.t1:
673            temporalRange.text = self.t1
674            if self.t2:
675                temporalRange.text += "/" + self.t2
676
677        # add spatial range data, if available
678        self._addSpatialData(root)
679
680        tree = ET.ElementTree(root)
681        logging.info("XML version of Atom created")
682        return tree
683
684
685    def __getSummary(self):
686        logging.debug("Getting summary data")
687        summaryString = ""
688        for summary_line in self.summary:
689            summaryString += summary_line + "\n"
690
691        return summaryString
692
693    def __setSummary(self, summary):
694        logging.debug("Adding summary data")
695        self.summary = []
696        for summary_line in summary.split('\n'):
697            self.summary.append(summary_line)#utilities.escapeSpecialCharacters(summary_line))
698           
699    Summary = property(fset=__setSummary, fget=__getSummary, doc="Atom summary")
700
701
702    def __getContent(self):
703        logging.debug("Getting content data")
704        contentString = ""
705        # NB, there must be content specified in an atom
706        if not self.content:
707            return "Metadata document"
708       
709        for content_line in self.content:
710            contentString += content_line + "\n"
711
712        return contentString
713
714    def __setContent(self, content):
715        logging.debug("Adding content data")
716        self.content = []
717        if not content:
718            return
719       
720        for content_line in content.split('\n'):
721            self.content.append(content_line)
722           
723    Content = property(fset=__setContent, fget=__getContent, doc="Atom content")
724
725           
726    def fromString(self, xmlString):
727        '''
728        Initialise Atom object using an xmlString
729        @param xmlString: representation of atom as an XML string
730        '''
731        logging.info("Ingesting data from XML string")
732        logging.debug("Create elementtree instance with XML string")
733        tree = ET.fromstring(xmlString)
734        title = tree.findtext('{%s}title' %ndgObject.ATOM_NS)
735        if title:
736            logging.debug("Adding title data")
737            self.title = title
738
739        summary = tree.findtext('{%s}summary' %ndgObject.ATOM_NS)
740        if summary:
741            self.Summary = summary#.decode('unicode_escape')
742
743        authorElement = tree.find('{%s}author' %ndgObject.ATOM_NS)
744        if authorElement:
745            logging.debug("Adding author data")
746            author = Person()
747            author.fromETElement(authorElement)
748            self.author = author
749
750        contributorElements = tree.findall('{%s}contributor' %ndgObject.ATOM_NS)
751        for contributorElement in contributorElements:
752            logging.debug("Adding contributor data")
753            contributor = Person(personType = Person.CONTRIBUTOR_TYPE)
754            contributor.fromETElement(contributorElement)
755            self.contributors.append(contributor)
756
757        molesElement = tree.find('{%s}entity' %ndgObject.MOLES_NS)
758        if molesElement:
759            self.ME.fromET(molesElement)
760               
761        atomID = tree.findtext('{%s}id' %ndgObject.ATOM_NS)
762        self.__parseAtomID(atomID)
763       
764        self._parseCategoryData(tree.findall('{%s}category' %ndgObject.ATOM_NS))
765
766        self._parseLinksData(tree.findall('{%s}link' %ndgObject.ATOM_NS))
767           
768        contentTag = tree.find('{%s}content' %ndgObject.ATOM_NS)
769        if contentTag != None:
770            logging.debug("Found content tag - checking for CSML/CDML file data")
771            file = contentTag.attrib.get('src')
772            if file:
773                # NB, the path will reveal more reliably whether we're dealing with CSML and CDML files
774                if file.upper().find('CSML') > -1:
775                    logging.debug("Adding CSML file data")
776                    self.csmlFile = file
777                elif file.upper().find('CDML') > -1:
778                    logging.debug("Adding CDML file data")
779                    self.cdmlFile = file
780                self.contentFile = file
781            else:
782                logging.debug("No file data - adding contents of element instead")
783                div = contentTag.findtext('{%s}div'%ndgObject.ATOM_NS)#XHTML_NS)
784                self.Content = div
785       
786        range = tree.findtext('{%s}temporalRange' %ndgObject.MOLES_NS)
787        if range:
788            logging.debug("Adding temporal range data")
789            timeData = range.split('/')
790            self.t1 = timeData[0]
791            if len(timeData) > 1:
792                self.t2 = timeData[1]
793       
794        where = tree.find('{%s}where' %ndgObject.GEOSS_NS)
795        if where:
796            # NB, this parser won't mind if we're dealing with Envelope or EnvelopeWithTimePeriod
797            minBBox = where.findall('.//{%s}lowerCorner' %ndgObject.GML_NS)
798            if minBBox:
799                logging.debug("Adding min spatial range data")
800                minBBox = minBBox[0]
801                spatialData = minBBox.text.split()
802                self.minX = spatialData[0]
803                if len(spatialData) > 1:
804                    self.minY = spatialData[1]
805           
806            maxBBox = where.findall('.//{%s}upperCorner' %ndgObject.GML_NS)
807            if maxBBox:
808                maxBBox = maxBBox[0]
809                logging.debug("Adding max spatial range data")
810                spatialData = maxBBox.text.split()
811                self.maxX = spatialData[0]
812                if len(spatialData) > 1:
813                    self.maxY = spatialData[1]
814               
815        publishedDate = tree.findtext('{%s}published' %ndgObject.ATOM_NS)
816        if publishedDate:
817            logging.debug("Adding published date")
818            self.publishedDate = publishedDate
819               
820        updatedDate = tree.findtext('{%s}updated' %ndgObject.ATOM_NS)
821        if updatedDate:
822            logging.debug("Adding updated date")
823            self.updatedDate = updatedDate
824           
825        logging.info("Completed data ingest")
826   
827   
828    def _parseCategoryData(self, categories):
829        logging.debug("Adding category/parameters data")
830        for category in categories:
831            cat = Category()
832            cat.fromETElement(category)
833           
834            if cat.term == self.ATOM_TYPE:
835                logging.debug("Found atom type data")
836                self.atomTypeID = cat.label
837                self.atomTypeName = self.VTD.TERM_DATA[cat.label].title
838                continue
839            elif cat.term == self.ATOM_SUBTYPE:
840                logging.debug("Found atom subtype data")
841                self.subtypeID = cat.label
842                self.subtype = cat.scheme
843                continue
844
845            self.parameters.append(cat)
846
847
848    def __parseAtomID(self, atomID):
849        '''
850        Given an atom ID, extract the useful bits of info and set these on
851        the relevant atom attributes
852        @param atomID: an atom ID in the 'tag' format
853        '''
854        logging.debug("Extracting atom info from ID, '%s'" %atomID)
855        self.atomID = atomID
856        self.datasetID = atomID.split("__ATOM__")[-1]
857        self._generateAtomName(self.datasetID)
858        logging.debug("- all info extracted")
859   
860
861    def setDatasetID(self, datasetID):
862        '''
863        Set the dataset ID for the atom - and generate an appropriate atom name using this
864        @param datasetID: ID to set for the atom
865        '''
866        self.datasetID = datasetID
867        self._generateAtomName(datasetID) 
868        self.atomID = self.createAtomID(datasetID)
869
870
871    def createAtomID(self, datasetID):
872        '''
873        Create a unique ID, conforming to atom standards, for atom
874        NB, see http://diveintomark.org/archives/2004/05/28/howto-atom-id
875        @param datasetID: ID of atom's dataset
876        @return: unique ID
877        '''
878        logging.info("Creating unique ID for atom")
879        if not self.atomBrowseURL:
880            self._generateAtomName(datasetID)
881        urlBit = self.atomBrowseURL.split('://')[1]
882        urlBit = urlBit.replace('#', '')
883        urlBits = urlBit.split('/')
884        host = urlBits[0].split(':')[0] # avoid the port colon - as this breaks the ID format
885        dateBit = datetime.datetime.today().strftime("%Y-%m-%d")
886       
887        id = "tag:" + host + "," + dateBit + ":/" + "/".join(urlBits[1:])
888        logging.info("- unique ID created for atom")
889        logging.debug(" - '%s'" %id)
890        return id
891       
892       
893    def _generateAtomName(self, datasetID):
894        '''
895        Generate a consistent name for the atom - with full eXist doc path
896        @param datasetID: ID of atom's dataset
897        '''
898        self.atomName = datasetID + ".atom"
899        if not self.ME.providerID:
900            raise ValueError("Provider ID has not been specified for atom - please add this and retry")
901        self.ndgURI = self.ME.providerID + "__ATOM__" + datasetID
902        self.atomBrowseURL = VTD.BROWSE_ROOT_URL + self.ndgURI
903
904
905    def _parseLinksData(self, links):
906        '''
907        Extract links and atom data from array of link elements in the XML representation of the atom
908        @param links: an array of <link> elements
909        '''
910        # firstly, get all data to start with, so we can properly process it afterwards
911        linkData = {}
912        logging.debug("Getting link data")
913        for linkTag in links:
914            link = Link()
915            link.fromETElement(linkTag)
916
917            if not linkData.has_key(link.rel):
918                linkData[link.rel] = []
919           
920            linkData[link.rel].append(link)
921
922        # there should be one self referencing link - which will provide info on the atom itself
923        if not linkData.has_key('self'):
924            errorMessage = "Atom does not have self referencing link - " + \
925                "cannot ascertain datasetID without this - please fix"
926            logging.error(errorMessage)
927            raise ValueError(errorMessage)
928       
929        # this is the link describing the atom itself
930        self.atomBrowseURL = linkData['self'][0].href
931       
932        self.datasetID = self.atomBrowseURL.split("__ATOM__")[-1]
933        self.atomName = self.datasetID + ".atom"
934        # NB, only split on the stem, since the browse host may not be
935        # the same as that defined in VTD
936        self.ndgURI = self.atomBrowseURL.split(VTD.BROWSE_STEM_URL)[-1]
937       
938        # now remove this value and the associated moles doc link
939        del linkData['self']
940        molesDoc = self.atomBrowseURL.replace('ATOM', 'NDG-B1')
941        if linkData.has_key('related'):
942            relatedLinks = []
943            for link in linkData['related']:
944                if link.href != molesDoc:
945                    relatedLinks.append(link)
946           
947            linkData['related'] = relatedLinks
948               
949        # now add the remaining links to the atom
950        for key in linkData:
951            for link in linkData[key]:
952                logging.debug("Adding link data")
953                self.relatedLinks.append(link)
954       
955
956    def _addSpatialData(self, element):
957        '''
958        Add spatial coverage element to an input element
959        @param element: element to add coverage data to
960        '''
961        logging.info("Adding spatial data to Atom")
962        if not self.minX:
963            logging.info("No spatial data specified")
964            return
965        bbox = ET.SubElement(element, "georss:where")
966        envelope = ET.SubElement(bbox, "gml:Envelope")
967        lc = ET.SubElement(envelope, "gml:lowerCorner")
968        lc.text = str(self.minX) + " " + str(self.minY)
969        uc = ET.SubElement(envelope, "gml:upperCorner")
970        uc.text = str(self.maxX) + " " + str(self.maxY)
971
972       
973    def setAttribute(self, attributeName, attributeValue):
974        '''
975        Set the value of an atom attribute - and do some basic tidying up of the string content
976        - to escape any XML unfriendly characters
977        @param attributeName: name of the attribute whose value to set
978        @param attributeValue: value to set the attribute to 
979        '''
980        logging.debug("Setting attribute, %s, to %s" %(attributeName, attributeValue))
981        origValue = attributeValue
982       
983        # escape any special characters if a value has been specified
984        # NB, need to cope with both single values and arrays
985        if attributeValue:
986            if type(attributeValue) is list:
987                newVals = []
988                for val in attributeValue:
989                    newVals.append(self.objectify(utilities.escapeSpecialCharacters(val), attributeName))
990                attributeValue = newVals
991                   
992            else:
993                attributeValue = self.objectify(utilities.escapeSpecialCharacters(attributeValue), attributeName)
994
995        # handle the special case of authors; only one author is allowed per atom
996        # - the others should be treated as contributors
997        if attributeName == "authors":
998            setattr(self, "author", attributeValue[0])
999            if len(attributeValue) > 1:
1000                setattr(self, "contributors", attributeValue[1:])
1001        elif attributeName == "atomAuthors":
1002            if isinstance(attributeValue, list):
1003                for val in attributeValue:
1004                    self.ME.responsibleParties.append(val)
1005            else:
1006                self.ME.responsibleParties.append(attributeValue)
1007        elif attributeName == "files":
1008            self.addUniqueRelatedLinks(attributeValue)
1009        else:
1010            setattr(self, attributeName, attributeValue)
1011
1012
1013    def objectify(self, objectVals, attributeName):
1014        '''
1015        Some inputs are specified as strings but need to be converted into
1016        objects - do this here
1017        @param objectVals: a '|' delimited string of values
1018        @param attributeName: name of attribute the values belong to
1019        '''
1020        obj = None
1021        if type(objectVals) != str:
1022            return objectVals
1023       
1024        if attributeName == "relatedLinks":
1025            obj = Link()
1026        elif attributeName == "atomAuthors":
1027            obj = Person(personType = Person.RESPONSIBLE_PARTY_TYPE)
1028        elif attributeName == "authors":
1029            # NB, ensure there is only one author tag - extra authors are contributors
1030            authorType = Person.AUTHOR_TYPE
1031            if self.author and self.author.hasValue():
1032                authorType= Person.CONTRIBUTOR_TYPE
1033            obj = Person(personType = authorType)
1034        elif attributeName == 'files':
1035            obj = Link()
1036            objectVals = '%s|%s|%s' \
1037                %(self.VTD.getTermCurrentVocabURL(VTD.METADATA_SOURCE_TERM), objectVals, VTD.METADATA_SOURCE_TERM)
1038
1039        if obj:
1040            obj.fromString(objectVals)
1041            # NB, need to set it now, just in case we don't set it before coming back
1042            if attributeName == "authors" and (not self.author or not self.author.hasValue()):
1043                self.author = obj
1044            return obj
1045       
1046        return objectVals
1047
1048
1049    def toPrettyXML(self):
1050        '''
1051        Returns nicely formatted XML as string
1052        '''
1053        atomXML = self.toXML()
1054
1055        # create the string
1056        logging.debug("Converting the elementtree object into a string")
1057        prettyXML = et2text(atomXML.getroot())
1058
1059        # add XML version tag
1060        prettyXML = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + prettyXML
1061        logging.info("Created formatted version of XML object")
1062        return prettyXML
1063
1064
1065    def getLinksOfType(self, termID):
1066        '''
1067        Returns links in the atom related links attribute which match the specified
1068        term ID
1069        @param termID: the termID to look for in the related links - NB, this is
1070        matched to the end of the link.rel value
1071        @return links: array of Link objects with matching term type
1072        '''
1073        logging.debug("Getting atom links of type, '%s'" %termID)
1074        matchingLinks = []
1075        for link in self.relatedLinks:
1076            # firstly, handle special case where we only want the online ref type links
1077            # returned
1078            if termID == self.ONLINE_REF_LABEL:
1079                if not link.isChildAtom():
1080                    logging.debug("- found link with matching term type")
1081                    matchingLinks.append(link)
1082               
1083            elif link and link.rel and link.rel.lower().endswith(termID.lower()):
1084                logging.debug("- found link with matching term type")
1085                matchingLinks.append(link)
1086               
1087        logging.debug("Returning matched links")
1088        return matchingLinks
1089       
1090       
1091    def getLogos(self):
1092        '''
1093        Return related links that are logos
1094        @return: array of Links containing the logos for the atom
1095        '''
1096        logos = []
1097        for link in self.relatedLinks:
1098            if link.rel.lower().endswith(VTD.LOGO_TERM.lower()):
1099                logos.append(link)
1100               
1101        return logos
1102   
1103   
1104    def isGranule(self):
1105        if self.atomTypeID == VTD.GRANULE_TERM:
1106            return True
1107        return False
1108   
1109   
1110    def isDE(self):
1111        if self.atomTypeID == VTD.DE_TERM:
1112            return True
1113        return False
1114   
1115    def isDeployment(self):
1116        if self.subtypeID and self.subtypeID == VTD.DEPLOYMENT_TERM:
1117            return True
1118        return False
1119   
1120    def isDeployable(self):
1121        if (self.atomTypeID == VTD.ACTIVITY_TERM and self.subtypeID != VTD.DEPLOYMENT_TERM) or \
1122            self.atomTypeID == VTD.DPT_TERM or \
1123            self.atomTypeID == VTD.OBS_TERM:
1124            return True
1125        return False
1126   
1127    def isPublished(self):
1128        '''
1129        Check state of atom doc - if published or Published return True,
1130        otherwise return False
1131        '''
1132        return self.state.isPublishedState()
1133       
1134       
1135    def addCSMLData(self, csmlName, csmlContent, aggregateCoverage=False, useCSMLID=False):
1136        '''
1137        Parse CSML data and add extracted info to the atom
1138        @param csmlName: name of the csml file
1139        @param csmlContent: content of the csml file - NB, if this is set to None and the
1140        file, csmlName, is available locally, CsmlParser.Dataset will read in the file
1141        directly
1142        @keyword aggregateCoverage: if set to True, only coverage data that extends the
1143        atom coverage data will be added
1144        @keyword useCSMLID: if True, use the CSML doc ID as the dataset ID - NB,
1145        this should only be True if creating a new atom - e.g. from a granulite
1146        @return csmlDoc: the CsmlParser.Dataset object with the csml data in
1147        '''
1148        logging.info("Creating CSML data model")
1149        self.csmlFile = csmlName
1150        self.contentFile = csmlName
1151        content = csmlContent or csmlName
1152   
1153        csmlDoc = CsmlParser.Dataset(file=content)
1154       
1155        logging.info("Extracting info from CSML file")
1156        logging.debug("Got dataset ID: %s" %csmlDoc.id)
1157        if useCSMLID:
1158            logging.debug(" - using this ID for the atom")
1159            self.setDatasetID(VTD.GRANULE_TERM + '_' + csmlDoc.id)
1160       
1161        title = csmlDoc.name.CONTENT
1162        logging.debug("Got dataset name (title): '%s'" %title)
1163        # NB, if a title is specified (and not as the default value), it automatically is used in
1164        # place of anything in the granulite file
1165        if title and title != "NAME OF DATASET GOES HERE":
1166            logging.info("Title, '%s', extracted from CSML file" %title)
1167            if self.title:
1168                logging.info("- NB, this will override the title specified in the granulite file ('%s')" \
1169                             %self.title)
1170            self.title = title
1171               
1172        bbox1 = csmlDoc.getBoundingBox()
1173        bbox2 = csmlDoc.getCSMLBoundingBox()
1174
1175        time = None
1176        if bbox2:
1177            time = bbox2.getTimeLimits()
1178   
1179        # now check for other parameters to add to granule
1180        # Firstly, extract the bounding envelope
1181        if bbox1:
1182            w, e = utilities.normaliseLongitude(bbox1[0],bbox1[2])
1183            n, s = (bbox1[3], bbox1[1])
1184   
1185            if not aggregateCoverage or (not self.maxY or float(n) > float(self.maxY)):
1186                self.maxY = n
1187               
1188            if not aggregateCoverage or (not self.minY or float(s) < float(self.minY)):
1189                self.minY = s
1190           
1191            if not aggregateCoverage or (not self.minX or float(w) < float(self.minX)):
1192                self.minX = w
1193   
1194            if not aggregateCoverage or (not self.maxX or float(e) > float(self.maxX)):
1195                self.maxX = e
1196           
1197            logging.debug("Got bounding box data from file: (%s, %s) , (%s, %s)" \
1198                          %(w, s, e, n))
1199           
1200            logging.debug("Updated atom bounding box data: (%s, %s) , (%s, %s)" \
1201                          %(self.minX, self.minY, self.maxX, self.maxY))
1202        else:
1203            logging.debug("No valid bounding box data found")
1204   
1205        if time:
1206            t1 = utilities.formatDateYYYYMMDD(time[0])
1207            if not aggregateCoverage or \
1208                (not self.t1 or datetime.datetime.strptime(t1, YEAR_FORMAT) < \
1209                    datetime.datetime.strptime(self.t1, YEAR_FORMAT)):
1210                self.t1 = t1
1211   
1212            t2 = time[1]
1213            if t2 and t2 != 'None':
1214                t2 = utilities.formatDateYYYYMMDD(t2)
1215                if not aggregateCoverage or \
1216                    (not self.t2 or datetime.datetime.strptime(t2, YEAR_FORMAT) > \
1217                        datetime.datetime.strptime(self.t2, YEAR_FORMAT)):
1218                    self.t2 = t2
1219           
1220            logging.debug("Got time range: %s -> %s" %(self.t1, self.t2))
1221        else:
1222            logging.debug("No valid time range data found")
1223   
1224        #create parameter summaries:
1225        #set up list to hold the parameters data
1226        parameters = []
1227        for feature in csmlDoc.featureCollection.featureMembers:
1228            if hasattr(feature.parameter, 'href'):
1229                paramTriple = ""
1230                if hasattr(feature, 'description'):
1231                    paramTriple = feature.description.CONTENT
1232                    paramTriple += " | " + feature.parameter.href
1233                   
1234                    term = ""
1235                    if hasattr(feature, 'name'):
1236                        term = feature.name.CONTENT
1237   
1238                    paramTriple += " | " + term
1239                   
1240                    logging.debug("Got parameter info: %s" %paramTriple)
1241                    parameters.append(paramTriple)
1242       
1243        # update the atom with the extracted parameters
1244        logging.info("Adding CSML parameters to granule atom")
1245        self.addParameters(parameters)
1246        logging.info("Finished adding CSML data")
1247        return csmlDoc
1248
1249
1250    def lookupAssociatedData(self, type, searchClient, lookupIndirectReferences=False):
1251        '''
1252        Check through the atom links and retrieve any associated data of the
1253        specified type
1254        @param type: type of associated data to lookup - currently VTD.DEPLOYMENT_TERM
1255        or VTD.DE_TERM
1256        @param searchClient: Client implementing the AbstractSearchXMLDBClient class
1257        @keyword lookupIndirectReferences: if True, the atom ID is used to search
1258        defined deployments to find those which reference it, otherwise only
1259        deployments data featured in the atom related links are processed
1260        '''
1261        logging.info("Looking up %s info" %type)
1262        self.allActivities = []
1263        self.allObs = []
1264        self.allDpts = []
1265
1266        if type != VTD.DE_TERM and type != VTD.DEPLOYMENT_TERM:
1267            raise ValueError('Unrecognised associated data type: %s' %type)
1268       
1269        # avoid duplicating lookup effort
1270        if (type == VTD.DEPLOYMENT_TERM and self.deployments) or \
1271            (type == VTD.DE_TERM and self.dataEntities):
1272            logging.info("- this info has already been looked up - returning")
1273            return
1274
1275        # firstly, collect all the references to the info required
1276        if lookupIndirectReferences:
1277            logging.info("Looking up indirect references")
1278           
1279            # if we're looking up DE data for deployments data, need to have the
1280            # deployments info looked up first
1281            if type == VTD.DE_TERM and self.isDeployable() and not self.deployments:
1282                self.lookupAssociatedData(VTD.DEPLOYMENT_TERM, searchClient, 
1283                                          lookupIndirectReferences = lookupIndirectReferences)
1284           
1285            logging.info("Looking up references to this atom from other %s" %type)
1286           
1287            # NB, if we're looking up deployments info, we only look up references
1288            # to this atom - if we're looking up DEs, we need to look up references
1289            # to the deployments referenced by this atom
1290            urls = [self.atomBrowseURL]
1291           
1292            if type == VTD.DE_TERM and self.isDeployable():
1293                urls = []
1294                for dep in self.deployments:
1295                    urls.append(dep.browseURL)
1296                   
1297            links = []
1298            for url in urls:
1299                doc = searchClient.getNDGDoc(type, ndgObject.ASSOCIATED_ATOM_DOC_TYPE, url,
1300                                             targetCollection = dc.ATOM_COLLECTION_PATH)
1301                # now need to turn this results set into actual atoms
1302                tree = ET.fromstring(doc)
1303                for atom in tree:
1304                    logging.debug("- found reference in %s" %type)
1305                    links.append(ET.tostring(atom))
1306                   
1307            logging.info("Finished looking up indirect references")
1308        else:
1309            links = self.getLinksOfType(self.VTD.DEPLOYMENT_TERM)
1310
1311        # now retrieve the references and extract the required data
1312        logging.info("Retrieving info from %s references" %type)
1313        if type == VTD.DEPLOYMENT_TERM:
1314            logging.info("Extracting links data to deployment entitites")
1315            self.deployments = []
1316            for link in links:
1317                if lookupIndirectReferences:
1318                    deploymentAtom = link
1319                else:
1320                    localID = link.href.split("__ATOM__")[-1]
1321                    deploymentAtom = searchClient.getNDGDoc('', 
1322                                                            'ATOM', localID, 
1323                                                            targetCollection = dc.ATOM_COLLECTION_PATH)
1324   
1325                deployment = Deployment.Deployment(Atom(xmlString=str(deploymentAtom)))
1326                self.deployments.append(deployment)
1327               
1328                self.addUniqueLinks(self.allActivities, deployment.activities)
1329                self.addUniqueLinks(self.allObs, deployment.obs)
1330                self.addUniqueLinks(self.allDpts, deployment.dpts)
1331        else:
1332            # for DE data, just store the title + link in a Link object
1333            self.dataEntities = []
1334            logging.info("Extracting links data to data entitites")
1335            for data in links:
1336                atom = Atom(xmlString=str(data))
1337                link = Link()
1338                link.title = atom.title
1339                link.href = atom.atomBrowseURL
1340                link.rel = atom.datasetID
1341               
1342                # NB, different deployments may be used by the same DE - so
1343                # avoid duplication
1344                self.addUniqueLinks(self.dataEntities, link)
1345           
1346        logging.info("Finished looking up %s info" %type)
1347
1348
1349    def addUniqueLinks(self, dataArray, links):
1350        '''
1351        Add links to specified array - if they are not already included
1352        @param dataArray: a list, potentially arlready containing links
1353        @param links: a Link or array of Links to add to the dataArray
1354        '''
1355        logging.debug("Adding new links")
1356        if not links:
1357            return
1358       
1359        if type(links) is not list:
1360            links = [links]
1361       
1362        for link in links:
1363            if type(link) is not Link:
1364                logging.warning("Link is not of 'Link' object type (type='%s') - skipping" %type(link))
1365                continue
1366            if link not in dataArray:
1367                logging.debug("- adding unique link")
1368                dataArray.append(link)
1369        logging.debug("Finished adding links")
1370
1371       
1372    def getFullPath(self):
1373        '''
1374        Return full path to atom in eXist, if it exists, or None, otherwise
1375        @return fullPath: string - collection + filename of atom in eXist
1376        '''
1377        # NB, name assigned when atom created in eXist - so if not defined, not
1378        # in eXist
1379        logging.debug("Getting full path to atom")
1380        if self.atomName:
1381            logging.debug("Return full path to atom in eXist")
1382            return self.getDefaultCollectionPath() + self.atomName
1383        logging.debug("Atom doesn't currently exist in eXist - return 'None'")
1384        return None
1385   
1386   
1387    def getSubTypePrettyName(self):
1388        '''
1389        Return the subtype of the atom in a human readable form
1390        @return: sub type of atom as a verbose string
1391        '''
1392        logging.debug("Getting human readable version of atom subtype")
1393        subType = self.SUB_TYPE_NOT_DEFINED_NAME
1394        if self.subtypeID:
1395           subType = self.VTD.tidySubTypeTitle(self.subtypeID)
1396           
1397        logging.debug("- returning subtype: '%s'" %subType)
1398        return subType
Note: See TracBrowser for help on using the repository browser.