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

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

Make handling of parameters common - so granulite + form input both
use same route + add extra code to deal with input of double quotes
here (cannot have this in the data since the data is an XML attribute)
+ more tidying of editor/browse links.

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
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        self.relatedLinks = newLinks
384        logging.debug("Online references added")
385
386
387    def addUniqueRelatedLinks(self, links):
388        '''
389        Add links to relatedLinks array - if they are not already included
390        @param links: a Link or array of Links to add to the relatedLinks attribute
391        '''
392        self.addUniqueLinks(self.relatedLinks, links)
393       
394
395    def removeRelatedLinks(self, linksToDelete):
396        '''
397        Remove any links in the input list from the atom's related links list
398        @param linksToDelete: array of Link objects to remove from atom
399        '''
400        logging.debug("Removing related links from atom")
401        if not linksToDelete:
402            return
403       
404        if type(linksToDelete) is not list:
405            linksToDelete = [linksToDelete]
406       
407        updatedLinks = []
408        for link in self.relatedLinks:
409            if type(link) is not Link:
410                logging.warning("Link is not of 'Link' object type (type='%s') - skipping" %type(link))
411                continue
412            if link in linksToDelete:
413                logging.debug("- found link to remove")
414            else:
415                updatedLinks.append(link)
416
417        self.relatedLinks = updatedLinks
418        logging.debug("Links removed")
419
420    def getPublicationStatePath(self):
421        '''
422        Determine the correct publication state collection for the atom
423        @return collectionPath: collection path for the publication state of the atom
424        '''
425        logging.debug("Getting collection path for atom publication state")
426        collectionPath = dc.ATOM_COLLECTION_PATH + self.state.collectionPath
427        logging.debug("Returning publication state collection, '%s'" %collectionPath)
428        return collectionPath
429       
430
431    def getDefaultEntityCollectionPath(self):
432        '''
433        Determine the correct collection for the entity type of the atom
434        @return entityPath: collection path for the data type of the atom
435        '''
436        logging.debug("Getting collection path for atom entity type")
437        collectionPath = self.getPublicationStatePath()
438       
439        if self.atomTypeID == VTD.DE_TERM:
440            collectionPath += dc.DE_COLLECTION_PATH
441        elif self.atomTypeID == VTD.GRANULE_TERM:
442            collectionPath += dc.GRANULE_COLLECTION_PATH
443        elif self.atomTypeID == VTD.ACTIVITY_TERM and \
444            self.subtypeID == VTD.DEPLOYMENT_TERM:
445            collectionPath += dc.DEPLOYMENTS_COLLECTION_PATH
446        else:
447            collectionPath += dc.DEPLOYMENT_COLLECTION_PATH
448       
449        logging.debug("Returning entity collection, '%s'" %collectionPath)
450        return collectionPath
451       
452
453    def getDefaultCollectionPath(self):
454        '''
455        Determine the correct collection to use for the atom in eXist
456        '''
457        logging.debug("Getting default collection path for atom")
458        collectionPath = self.getDefaultEntityCollectionPath()
459        if not self.ME.providerID:
460            raise AtomError("Error: cannot determine atom collection path because " + \
461                            "the provider ID is not defined")
462           
463        collectionPath += self.ME.providerID + "/"
464        logging.debug("Returning collection, '%s'" %collectionPath)
465        return collectionPath
466
467
468    def __addAtomTypeDataXML(self, root):
469        '''
470        Add the atom type, and subtype data, if available, to atom categories
471        - and lookup and add the appropriate vocab term data
472        '''
473        if self.atomTypeID:
474            logging.info("Adding atom type info to XML output")
475            category = Category()
476            category.label = self.atomTypeID
477            # look up the appropriate vocab term data
478            category.scheme = self.VTD.getTermCurrentVocabURL(self.atomTypeID)
479            category.term = self.ATOM_TYPE
480            root.append(category.toXML())
481
482        if self.subtypeID:
483            logging.info("Adding atom subtype info to XML output")
484            # NB subtypes not all defined, so leave this out for the moment
485            category.label = self.subtypeID
486            # look up the appropriate vocab term data
487            category.scheme = self.VTD.getTermCurrentVocabURL(self.subtypeID)
488            category.term = self.ATOM_SUBTYPE
489            root.append(category.toXML())
490
491
492    def addMolesEntityData(self, abbreviation, provider_id, object_creation_time):
493        '''
494        Add data to include in the moles entity element
495        '''
496        logging.debug('Adding moles entity information')
497        self.ME.abbreviation = abbreviation
498        self.ME.providerID = provider_id
499        self.ME.createdDate = utilities.getISO8601Date(object_creation_time)
500        logging.debug('Moles entity information added')
501
502
503    def addAuthors(self, authors):
504        '''
505        Add author data appropriately to the atom
506        NB, these will overwrite any existing authors of the same type
507        @param authors: list of Person objects with the author data
508        '''
509        logging.debug('Adding authors data to Atom')
510        isFirstAuthor = {}
511        authorArray = None
512        for author in authors:
513            # NB, we're only allowed one atom author
514            if author.type == Person.AUTHOR_TYPE:
515                self.author = author
516                   
517                if isFirstAuthor.has_key(author.type):
518                    raise AtomError("Error: an atom can only have one author specified")
519                isFirstAuthor[author.type] = 1
520                continue
521            elif author.type == Person.CONTRIBUTOR_TYPE:
522                authorArray = self.contributors
523            elif author.type == Person.RESPONSIBLE_PARTY_TYPE:
524                authorArray = self.ME.responsibleParties
525               
526            # check if this is the first addition - if so, clear out the
527            # array in advance
528            if not isFirstAuthor.has_key(author.type):
529                logging.debug("Clearing out author array")
530                # NB, need to be careful to clear the array, not create a ref
531                # to a new array
532                del authorArray[:]
533                isFirstAuthor[author.type] = 1
534
535            if author.hasValue() and author not in authorArray:
536                logging.debug("Adding author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
537                              %(author.type, author.name, author.uri, author.role))
538                authorArray.append(author)
539
540        logging.debug('Finished adding authors data')
541
542
543    def _isNewParameter(self, param):
544        '''
545        Check if a parameter is already specified in the atom, return False if
546        so, otherwise return True
547        '''
548        for p in self.parameters:
549            if p.term == param.term and \
550                p.scheme == param.scheme and \
551                p.label == param.label:
552                return False
553        return True
554
555
556    def addRelatedLinks(self, linkVals):
557        '''
558        Add related links in string format - converting to Link objects
559        NB, only add the link if it is unique
560       
561        @param linkVals: string of format, 'uri | title | vocabServerURL'
562        '''
563        link = self.objectify(linkVals, 'relatedLinks')
564        if link not in self.relatedLinks:
565            self.relatedLinks.append(link)
566
567
568    def addParameters(self, params):
569        '''
570        Add a parameter to list - ensuring it is unique and has been formatted and tidied appropriately
571        @params param: parameter, as string array, to add to atom parameters collection
572        '''
573        # avoid strings being parsed character by character
574        if type(params) is str:
575            params = [params]
576           
577        for param in params:
578            # firstly tidy parameter
579            param = utilities.tidyUpParameters(param)
580            category = Category()
581            # NB, data already tidied up here, so set keyword to avoid this happening again
582            category.fromString(param, escapeSpecialCharacters=True)
583
584            # now check for uniqueness
585            if self._isNewParameter(category):
586                logging.debug("Adding new parameter: %s" %param)
587                self.parameters.append(category)
588   
589   
590    def _linksToXML(self, root):
591        '''
592        Add required links to the input element
593        @param root: element to add links to - NB, should be the root element of the atom
594        '''
595        selfLink = ET.SubElement(root, "link")
596        selfLink.attrib["href"] = self.atomBrowseURL
597        selfLink.attrib["rel"] = "self"
598       
599        for relatedLink in self.relatedLinks:
600            if relatedLink.hasValue():
601                root.append(relatedLink.toXML())
602   
603    def toXML(self):
604        '''
605        Convert the atom into XML representation and return this
606        @return: xml version of atom
607        '''
608        logging.info("Creating formatted XML version of Atom")
609        root = ET.Element("entry")
610        root.attrib["xmlns"] = ndgObject.ATOM_NS
611        root.attrib["xmlns:moles"] = ndgObject.MOLES_NS
612        root.attrib["xmlns:georss"] = ndgObject.GEOSS_NS
613        root.attrib["xmlns:gml"] = ndgObject.GML_NS
614        id = ET.SubElement(root, "id")
615        id.text = self.atomID
616        title = ET.SubElement(root, "title")
617        title.text = self.title
618        self._linksToXML(root)
619
620        if self.author and self.author.hasValue():
621            root.append(self.author.toXML())
622           
623        for contributor in self.contributors:
624            root.append(contributor.toXML())
625
626        # add parameters data
627        for param in self.parameters:
628            if param.hasValue():
629                root.append(param.toXML())
630
631        # add the type and subtype data
632        self.__addAtomTypeDataXML(root)
633                   
634        summary = ET.SubElement(root, "summary")
635        summary.text = self.Summary
636                   
637        # add link to content, if required - NB, can only have one content element in atom
638        # - and this is mandatory
639        content = ET.SubElement(root, "content")
640        contentFile = self.contentFile or self.csmlFile or self.cdmlFile
641        if contentFile:
642            content.attrib["type"] = "application/xml"
643            content.attrib["src"] = contentFile
644        else:
645            content.attrib["type"] = "xhtml"
646            div = ET.SubElement(content, 'div')
647            div.attrib["xmlns:xhtml"] = ndgObject.XHTML_NS
648            div.text = self.Content
649       
650        # if there's a published date already defined, assume we're doing an update now
651        # NB, update element is mandatory
652        currentDate = datetime.datetime.today().strftime("%Y-%m-%dT%H:%M:%SZ")
653        if not self.publishedDate:
654            self.publishedDate = currentDate
655
656        updated = ET.SubElement(root, "updated")
657        if not self.updatedDate:
658            self.updatedDate = currentDate
659        updated.text = self.updatedDate
660
661        published = ET.SubElement(root, "published")
662        published.text = self.publishedDate
663
664        # add the moles entity section, if it is required
665        if self.ME:
666            root.append(self.ME.toXML())
667
668        # add temporal range data, if available
669        temporalRange = ET.SubElement(root, "moles:temporalRange")
670        if self.t1:
671            temporalRange.text = self.t1
672            if self.t2:
673                temporalRange.text += "/" + self.t2
674
675        # add spatial range data, if available
676        self._addSpatialData(root)
677
678        tree = ET.ElementTree(root)
679        logging.info("XML version of Atom created")
680        return tree
681
682
683    def __getSummary(self):
684        logging.debug("Getting summary data")
685        summaryString = ""
686        for summary_line in self.summary:
687            summaryString += summary_line + "\n"
688
689        return summaryString
690
691    def __setSummary(self, summary):
692        logging.debug("Adding summary data")
693        self.summary = []
694        for summary_line in summary.split('\n'):
695            self.summary.append(summary_line)#utilities.escapeSpecialCharacters(summary_line))
696           
697    Summary = property(fset=__setSummary, fget=__getSummary, doc="Atom summary")
698
699
700    def __getContent(self):
701        logging.debug("Getting content data")
702        contentString = ""
703        # NB, there must be content specified in an atom
704        if not self.content:
705            return "Metadata document"
706       
707        for content_line in self.content:
708            contentString += content_line + "\n"
709
710        return contentString
711
712    def __setContent(self, content):
713        logging.debug("Adding content data")
714        self.content = []
715        if not content:
716            return
717       
718        for content_line in content.split('\n'):
719            self.content.append(content_line)
720           
721    Content = property(fset=__setContent, fget=__getContent, doc="Atom content")
722
723           
724    def fromString(self, xmlString):
725        '''
726        Initialise Atom object using an xmlString
727        @param xmlString: representation of atom as an XML string
728        '''
729        logging.info("Ingesting data from XML string")
730        logging.debug("Create elementtree instance with XML string")
731        tree = ET.fromstring(xmlString)
732        title = tree.findtext('{%s}title' %ndgObject.ATOM_NS)
733        if title:
734            logging.debug("Adding title data")
735            self.title = title
736
737        summary = tree.findtext('{%s}summary' %ndgObject.ATOM_NS)
738        if summary:
739            self.Summary = summary#.decode('unicode_escape')
740
741        authorElement = tree.find('{%s}author' %ndgObject.ATOM_NS)
742        if authorElement:
743            logging.debug("Adding author data")
744            author = Person()
745            author.fromETElement(authorElement)
746            self.author = author
747
748        contributorElements = tree.findall('{%s}contributor' %ndgObject.ATOM_NS)
749        for contributorElement in contributorElements:
750            logging.debug("Adding contributor data")
751            contributor = Person(personType = Person.CONTRIBUTOR_TYPE)
752            contributor.fromETElement(contributorElement)
753            self.contributors.append(contributor)
754
755        molesElement = tree.find('{%s}entity' %ndgObject.MOLES_NS)
756        if molesElement:
757            self.ME.fromET(molesElement)
758               
759        atomID = tree.findtext('{%s}id' %ndgObject.ATOM_NS)
760        self.__parseAtomID(atomID)
761       
762        self._parseCategoryData(tree.findall('{%s}category' %ndgObject.ATOM_NS))
763
764        self._parseLinksData(tree.findall('{%s}link' %ndgObject.ATOM_NS))
765           
766        contentTag = tree.find('{%s}content' %ndgObject.ATOM_NS)
767        if contentTag != None:
768            logging.debug("Found content tag - checking for CSML/CDML file data")
769            file = contentTag.attrib.get('src')
770            if file:
771                # NB, the path will reveal more reliably whether we're dealing with CSML and CDML files
772                if file.upper().find('CSML') > -1:
773                    logging.debug("Adding CSML file data")
774                    self.csmlFile = file
775                elif file.upper().find('CDML') > -1:
776                    logging.debug("Adding CDML file data")
777                    self.cdmlFile = file
778                self.contentFile = file
779            else:
780                logging.debug("No file data - adding contents of element instead")
781                div = contentTag.findtext('{%s}div'%ndgObject.ATOM_NS)#XHTML_NS)
782                self.Content = div
783       
784        range = tree.findtext('{%s}temporalRange' %ndgObject.MOLES_NS)
785        if range:
786            logging.debug("Adding temporal range data")
787            timeData = range.split('/')
788            self.t1 = timeData[0]
789            if len(timeData) > 1:
790                self.t2 = timeData[1]
791       
792        where = tree.find('{%s}where' %ndgObject.GEOSS_NS)
793        if where:
794            # NB, this parser won't mind if we're dealing with Envelope or EnvelopeWithTimePeriod
795            minBBox = where.findall('.//{%s}lowerCorner' %ndgObject.GML_NS)
796            if minBBox:
797                logging.debug("Adding min spatial range data")
798                minBBox = minBBox[0]
799                spatialData = minBBox.text.split()
800                self.minX = spatialData[0]
801                if len(spatialData) > 1:
802                    self.minY = spatialData[1]
803           
804            maxBBox = where.findall('.//{%s}upperCorner' %ndgObject.GML_NS)
805            if maxBBox:
806                maxBBox = maxBBox[0]
807                logging.debug("Adding max spatial range data")
808                spatialData = maxBBox.text.split()
809                self.maxX = spatialData[0]
810                if len(spatialData) > 1:
811                    self.maxY = spatialData[1]
812               
813        publishedDate = tree.findtext('{%s}published' %ndgObject.ATOM_NS)
814        if publishedDate:
815            logging.debug("Adding published date")
816            self.publishedDate = publishedDate
817               
818        updatedDate = tree.findtext('{%s}updated' %ndgObject.ATOM_NS)
819        if updatedDate:
820            logging.debug("Adding updated date")
821            self.updatedDate = updatedDate
822           
823        logging.info("Completed data ingest")
824   
825   
826    def _parseCategoryData(self, categories):
827        logging.debug("Adding category/parameters data")
828        for category in categories:
829            cat = Category()
830            cat.fromETElement(category)
831           
832            if cat.term == self.ATOM_TYPE:
833                logging.debug("Found atom type data")
834                self.atomTypeID = cat.label
835                self.atomTypeName = self.VTD.TERM_DATA[cat.label].title
836                continue
837            elif cat.term == self.ATOM_SUBTYPE:
838                logging.debug("Found atom subtype data")
839                self.subtypeID = cat.label
840                self.subtype = cat.scheme
841                continue
842
843            self.parameters.append(cat)
844
845
846    def __parseAtomID(self, atomID):
847        '''
848        Given an atom ID, extract the useful bits of info and set these on
849        the relevant atom attributes
850        @param atomID: an atom ID in the 'tag' format
851        '''
852        logging.debug("Extracting atom info from ID, '%s'" %atomID)
853        self.atomID = atomID
854        self.datasetID = atomID.split("__ATOM__")[-1]
855        self._generateAtomName(self.datasetID)
856        logging.debug("- all info extracted")
857   
858
859    def setDatasetID(self, datasetID):
860        '''
861        Set the dataset ID for the atom - and generate an appropriate atom name using this
862        @param datasetID: ID to set for the atom
863        '''
864        self.datasetID = datasetID
865        self._generateAtomName(datasetID) 
866        self.atomID = self.createAtomID(datasetID)
867
868
869    def createAtomID(self, datasetID):
870        '''
871        Create a unique ID, conforming to atom standards, for atom
872        NB, see http://diveintomark.org/archives/2004/05/28/howto-atom-id
873        @param datasetID: ID of atom's dataset
874        @return: unique ID
875        '''
876        logging.info("Creating unique ID for atom")
877        if not self.atomBrowseURL:
878            self._generateAtomName(datasetID)
879        urlBit = self.atomBrowseURL.split('://')[1]
880        urlBit = urlBit.replace('#', '')
881        urlBits = urlBit.split('/')
882        host = urlBits[0].split(':')[0] # avoid the port colon - as this breaks the ID format
883        dateBit = datetime.datetime.today().strftime("%Y-%m-%d")
884       
885        id = "tag:" + host + "," + dateBit + ":/" + "/".join(urlBits[1:])
886        logging.info("- unique ID created for atom")
887        logging.debug(" - '%s'" %id)
888        return id
889       
890       
891    def _generateAtomName(self, datasetID):
892        '''
893        Generate a consistent name for the atom - with full eXist doc path
894        @param datasetID: ID of atom's dataset
895        '''
896        self.atomName = datasetID + ".atom"
897        if not self.ME.providerID:
898            raise ValueError("Provider ID has not been specified for atom - please add this and retry")
899        self.ndgURI = self.ME.providerID + "__ATOM__" + datasetID
900        self.atomBrowseURL = VTD.BROWSE_ROOT_URL + self.ndgURI
901
902
903    def _parseLinksData(self, links):
904        '''
905        Extract links and atom data from array of link elements in the XML representation of the atom
906        @param links: an array of <link> elements
907        '''
908        # firstly, get all data to start with, so we can properly process it afterwards
909        linkData = {}
910        logging.debug("Getting link data")
911        for linkTag in links:
912            link = Link()
913            link.fromETElement(linkTag)
914
915            if not linkData.has_key(link.rel):
916                linkData[link.rel] = []
917           
918            linkData[link.rel].append(link)
919
920        # there should be one self referencing link - which will provide info on the atom itself
921        if not linkData.has_key('self'):
922            errorMessage = "Atom does not have self referencing link - " + \
923                "cannot ascertain datasetID without this - please fix"
924            logging.error(errorMessage)
925            raise ValueError(errorMessage)
926       
927        # this is the link describing the atom itself
928        self.atomBrowseURL = linkData['self'][0].href
929       
930        self.datasetID = self.atomBrowseURL.split("__ATOM__")[-1]
931        self.atomName = self.datasetID + ".atom"
932        # NB, only split on the stem, since the browse host may not be
933        # the same as that defined in VTD
934        self.ndgURI = self.atomBrowseURL.split(VTD.BROWSE_STEM_URL)[-1]
935       
936        # now remove this value and the associated moles doc link
937        del linkData['self']
938        molesDoc = self.atomBrowseURL.replace('ATOM', 'NDG-B1')
939        if linkData.has_key('related'):
940            relatedLinks = []
941            for link in linkData['related']:
942                if link.href != molesDoc:
943                    relatedLinks.append(link)
944           
945            linkData['related'] = relatedLinks
946               
947        # now add the remaining links to the atom
948        for key in linkData:
949            for link in linkData[key]:
950                logging.debug("Adding link data")
951                self.relatedLinks.append(link)
952       
953
954    def _addSpatialData(self, element):
955        '''
956        Add spatial coverage element to an input element
957        @param element: element to add coverage data to
958        '''
959        logging.info("Adding spatial data to Atom")
960        if not self.minX:
961            logging.info("No spatial data specified")
962            return
963        bbox = ET.SubElement(element, "georss:where")
964        envelope = ET.SubElement(bbox, "gml:Envelope")
965        lc = ET.SubElement(envelope, "gml:lowerCorner")
966        lc.text = str(self.minX) + " " + str(self.minY)
967        uc = ET.SubElement(envelope, "gml:upperCorner")
968        uc.text = str(self.maxX) + " " + str(self.maxY)
969
970       
971    def setAttribute(self, attributeName, attributeValue):
972        '''
973        Set the value of an atom attribute - and do some basic tidying up of the string content
974        - to escape any XML unfriendly characters
975        @param attributeName: name of the attribute whose value to set
976        @param attributeValue: value to set the attribute to 
977        '''
978        logging.debug("Setting attribute, %s, to %s" %(attributeName, attributeValue))
979        origValue = attributeValue
980       
981        # escape any special characters if a value has been specified
982        # NB, need to cope with both single values and arrays
983        if attributeValue:
984            if type(attributeValue) is list:
985                newVals = []
986                for val in attributeValue:
987                    newVals.append(self.objectify(utilities.escapeSpecialCharacters(val), attributeName))
988                attributeValue = newVals
989                   
990            else:
991                attributeValue = self.objectify(utilities.escapeSpecialCharacters(attributeValue), attributeName)
992
993        # handle the special case of authors; only one author is allowed per atom
994        # - the others should be treated as contributors
995        if attributeName == "authors":
996            setattr(self, "author", attributeValue[0])
997            if len(attributeValue) > 1:
998                setattr(self, "contributors", attributeValue[1:])
999        elif attributeName == "atomAuthors":
1000            if isinstance(attributeValue, list):
1001                for val in attributeValue:
1002                    self.ME.responsibleParties.append(val)
1003            else:
1004                self.ME.responsibleParties.append(attributeValue)
1005        elif attributeName == "files":
1006            self.addUniqueRelatedLinks(attributeValue)
1007        else:
1008            setattr(self, attributeName, attributeValue)
1009
1010
1011    def objectify(self, objectVals, attributeName):
1012        '''
1013        Some inputs are specified as strings but need to be converted into
1014        objects - do this here
1015        @param objectVals: a '|' delimited string of values
1016        @param attributeName: name of attribute the values belong to
1017        '''
1018        obj = None
1019        if type(objectVals) != str:
1020            return objectVals
1021       
1022        if attributeName == "relatedLinks":
1023            obj = Link()
1024        elif attributeName == "atomAuthors":
1025            obj = Person(personType = Person.RESPONSIBLE_PARTY_TYPE)
1026        elif attributeName == "authors":
1027            # NB, ensure there is only one author tag - extra authors are contributors
1028            authorType = Person.AUTHOR_TYPE
1029            if self.author and self.author.hasValue():
1030                authorType= Person.CONTRIBUTOR_TYPE
1031            obj = Person(personType = authorType)
1032        elif attributeName == 'files':
1033            obj = Link()
1034            objectVals = '%s|%s|%s' \
1035                %(self.VTD.getTermCurrentVocabURL(VTD.METADATA_SOURCE_TERM), objectVals, VTD.METADATA_SOURCE_TERM)
1036
1037        if obj:
1038            obj.fromString(objectVals)
1039            # NB, need to set it now, just in case we don't set it before coming back
1040            if attributeName == "authors" and (not self.author or not self.author.hasValue()):
1041                self.author = obj
1042            return obj
1043       
1044        return objectVals
1045
1046
1047    def toPrettyXML(self):
1048        '''
1049        Returns nicely formatted XML as string
1050        '''
1051        atomXML = self.toXML()
1052
1053        # create the string
1054        logging.debug("Converting the elementtree object into a string")
1055        prettyXML = et2text(atomXML.getroot())
1056
1057        # add XML version tag
1058        prettyXML = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + prettyXML
1059        logging.info("Created formatted version of XML object")
1060        return prettyXML
1061
1062
1063    def getLinksOfType(self, termID):
1064        '''
1065        Returns links in the atom related links attribute which match the specified
1066        term ID
1067        @param termID: the termID to look for in the related links - NB, this is
1068        matched to the end of the link.rel value
1069        @return links: array of Link objects with matching term type
1070        '''
1071        logging.debug("Getting atom links of type, '%s'" %termID)
1072        matchingLinks = []
1073        for link in self.relatedLinks:
1074            # firstly, handle special case where we only want the online ref type links
1075            # returned
1076            if termID == self.ONLINE_REF_LABEL:
1077                if not link.isChildAtom():
1078                    logging.debug("- found link with matching term type")
1079                    matchingLinks.append(link)
1080               
1081            elif link and link.rel and link.rel.lower().endswith(termID.lower()):
1082                logging.debug("- found link with matching term type")
1083                matchingLinks.append(link)
1084               
1085        logging.debug("Returning matched links")
1086        return matchingLinks
1087       
1088       
1089    def getLogos(self):
1090        '''
1091        Return related links that are logos
1092        @return: array of Links containing the logos for the atom
1093        '''
1094        logos = []
1095        for link in self.relatedLinks:
1096            if link.rel.lower().endswith(VTD.LOGO_TERM.lower()):
1097                logos.append(link)
1098               
1099        return logos
1100   
1101   
1102    def isGranule(self):
1103        if self.atomTypeID == VTD.GRANULE_TERM:
1104            return True
1105        return False
1106   
1107   
1108    def isDE(self):
1109        if self.atomTypeID == VTD.DE_TERM:
1110            return True
1111        return False
1112   
1113    def isDeployment(self):
1114        if self.subtypeID and self.subtypeID == VTD.DEPLOYMENT_TERM:
1115            return True
1116        return False
1117   
1118    def isDeployable(self):
1119        if (self.atomTypeID == VTD.ACTIVITY_TERM and self.subtypeID != VTD.DEPLOYMENT_TERM) or \
1120            self.atomTypeID == VTD.DPT_TERM or \
1121            self.atomTypeID == VTD.OBS_TERM:
1122            return True
1123        return False
1124   
1125    def isPublished(self):
1126        '''
1127        Check state of atom doc - if published or Published return True,
1128        otherwise return False
1129        '''
1130        return self.state.isPublishedState()
1131       
1132       
1133    def addCSMLData(self, csmlName, csmlContent, aggregateCoverage=False, useCSMLID=False):
1134        '''
1135        Parse CSML data and add extracted info to the atom
1136        @param csmlName: name of the csml file
1137        @param csmlContent: content of the csml file - NB, if this is set to None and the
1138        file, csmlName, is available locally, CsmlParser.Dataset will read in the file
1139        directly
1140        @keyword aggregateCoverage: if set to True, only coverage data that extends the
1141        atom coverage data will be added
1142        @keyword useCSMLID: if True, use the CSML doc ID as the dataset ID - NB,
1143        this should only be True if creating a new atom - e.g. from a granulite
1144        @return csmlDoc: the CsmlParser.Dataset object with the csml data in
1145        '''
1146        logging.info("Creating CSML data model")
1147        self.csmlFile = csmlName
1148        self.contentFile = csmlName
1149        content = csmlContent or csmlName
1150   
1151        csmlDoc = CsmlParser.Dataset(file=content)
1152       
1153        logging.info("Extracting info from CSML file")
1154        logging.debug("Got dataset ID: %s" %csmlDoc.id)
1155        if useCSMLID:
1156            logging.debug(" - using this ID for the atom")
1157            self.setDatasetID(VTD.GRANULE_TERM + '_' + csmlDoc.id)
1158       
1159        title = csmlDoc.name.CONTENT
1160        logging.debug("Got dataset name (title): '%s'" %title)
1161        # NB, if a title is specified (and not as the default value), it automatically is used in
1162        # place of anything in the granulite file
1163        if title and title != "NAME OF DATASET GOES HERE":
1164            logging.info("Title, '%s', extracted from CSML file" %title)
1165            if self.title:
1166                logging.info("- NB, this will override the title specified in the granulite file ('%s')" \
1167                             %self.title)
1168            self.title = title
1169               
1170        bbox1 = csmlDoc.getBoundingBox()
1171        bbox2 = csmlDoc.getCSMLBoundingBox()
1172
1173        time = None
1174        if bbox2:
1175            time = bbox2.getTimeLimits()
1176   
1177        # now check for other parameters to add to granule
1178        # Firstly, extract the bounding envelope
1179        if bbox1:
1180            w, e = utilities.normaliseLongitude(bbox1[0],bbox1[2])
1181            n, s = (bbox1[3], bbox1[1])
1182   
1183            if not aggregateCoverage or (not self.maxY or float(n) > float(self.maxY)):
1184                self.maxY = n
1185               
1186            if not aggregateCoverage or (not self.minY or float(s) < float(self.minY)):
1187                self.minY = s
1188           
1189            if not aggregateCoverage or (not self.minX or float(w) < float(self.minX)):
1190                self.minX = w
1191   
1192            if not aggregateCoverage or (not self.maxX or float(e) > float(self.maxX)):
1193                self.maxX = e
1194           
1195            logging.debug("Got bounding box data from file: (%s, %s) , (%s, %s)" \
1196                          %(w, s, e, n))
1197           
1198            logging.debug("Updated atom bounding box data: (%s, %s) , (%s, %s)" \
1199                          %(self.minX, self.minY, self.maxX, self.maxY))
1200        else:
1201            logging.debug("No valid bounding box data found")
1202   
1203        if time:
1204            t1 = utilities.formatDateYYYYMMDD(time[0])
1205            if not aggregateCoverage or \
1206                (not self.t1 or datetime.datetime.strptime(t1, YEAR_FORMAT) < \
1207                    datetime.datetime.strptime(self.t1, YEAR_FORMAT)):
1208                self.t1 = t1
1209   
1210            t2 = time[1]
1211            if t2 and t2 != 'None':
1212                t2 = utilities.formatDateYYYYMMDD(t2)
1213                if not aggregateCoverage or \
1214                    (not self.t2 or datetime.datetime.strptime(t2, YEAR_FORMAT) > \
1215                        datetime.datetime.strptime(self.t2, YEAR_FORMAT)):
1216                    self.t2 = t2
1217           
1218            logging.debug("Got time range: %s -> %s" %(self.t1, self.t2))
1219        else:
1220            logging.debug("No valid time range data found")
1221   
1222        #create parameter summaries:
1223        #set up list to hold the parameters data
1224        parameters = []
1225        for feature in csmlDoc.featureCollection.featureMembers:
1226            if hasattr(feature.parameter, 'href'):
1227                paramTriple = ""
1228                if hasattr(feature, 'description'):
1229                    paramTriple = feature.description.CONTENT
1230                    paramTriple += " | " + feature.parameter.href
1231                   
1232                    term = ""
1233                    if hasattr(feature, 'name'):
1234                        term = feature.name.CONTENT
1235   
1236                    paramTriple += " | " + term
1237                   
1238                    logging.debug("Got parameter info: %s" %paramTriple)
1239                    parameters.append(paramTriple)
1240       
1241        # update the atom with the extracted parameters
1242        logging.info("Adding CSML parameters to granule atom")
1243        self.addParameters(parameters)
1244        logging.info("Finished adding CSML data")
1245        return csmlDoc
1246
1247
1248    def lookupAssociatedData(self, type, searchClient, lookupIndirectReferences=False):
1249        '''
1250        Check through the atom links and retrieve any associated data of the
1251        specified type
1252        @param type: type of associated data to lookup - currently VTD.DEPLOYMENT_TERM
1253        or VTD.DE_TERM
1254        @param searchClient: Client implementing the AbstractSearchXMLDBClient class
1255        @keyword lookupIndirectReferences: if True, the atom ID is used to search
1256        defined deployments to find those which reference it, otherwise only
1257        deployments data featured in the atom related links are processed
1258        '''
1259        logging.info("Looking up %s info" %type)
1260        self.allActivities = []
1261        self.allObs = []
1262        self.allDpts = []
1263
1264        if type != VTD.DE_TERM and type != VTD.DEPLOYMENT_TERM:
1265            raise ValueError('Unrecognised associated data type: %s' %type)
1266       
1267        # avoid duplicating lookup effort
1268        if (type == VTD.DEPLOYMENT_TERM and self.deployments) or \
1269            (type == VTD.DE_TERM and self.dataEntities):
1270            logging.info("- this info has already been looked up - returning")
1271            return
1272
1273        # firstly, collect all the references to the info required
1274        if lookupIndirectReferences:
1275            logging.info("Looking up indirect references")
1276           
1277            # if we're looking up DE data for deployments data, need to have the
1278            # deployments info looked up first
1279            if type == VTD.DE_TERM and self.isDeployable() and not self.deployments:
1280                self.lookupAssociatedData(VTD.DEPLOYMENT_TERM, searchClient, 
1281                                          lookupIndirectReferences = lookupIndirectReferences)
1282           
1283            logging.info("Looking up references to this atom from other %s" %type)
1284           
1285            # NB, if we're looking up deployments info, we only look up references
1286            # to this atom - if we're looking up DEs, we need to look up references
1287            # to the deployments referenced by this atom
1288            urls = [self.atomBrowseURL]
1289           
1290            if type == VTD.DE_TERM and self.isDeployable():
1291                urls = []
1292                for dep in self.deployments:
1293                    urls.append(dep.browseURL)
1294                   
1295            links = []
1296            for url in urls:
1297                doc = searchClient.getNDGDoc(type, ndgObject.ASSOCIATED_ATOM_DOC_TYPE, url,
1298                                             targetCollection = dc.ATOM_COLLECTION_PATH)
1299                # now need to turn this results set into actual atoms
1300                tree = ET.fromstring(doc)
1301                for atom in tree:
1302                    logging.debug("- found reference in %s" %type)
1303                    links.append(ET.tostring(atom))
1304                   
1305            logging.info("Finished looking up indirect references")
1306        else:
1307            links = self.getLinksOfType(self.VTD.DEPLOYMENT_TERM)
1308
1309        # now retrieve the references and extract the required data
1310        logging.info("Retrieving info from %s references" %type)
1311        if type == VTD.DEPLOYMENT_TERM:
1312            logging.info("Extracting links data to deployment entitites")
1313            self.deployments = []
1314            for link in links:
1315                if lookupIndirectReferences:
1316                    deploymentAtom = link
1317                else:
1318                    localID = link.href.split("__ATOM__")[-1]
1319                    deploymentAtom = searchClient.getNDGDoc('', 
1320                                                            'ATOM', localID, 
1321                                                            targetCollection = dc.ATOM_COLLECTION_PATH)
1322   
1323                deployment = Deployment.Deployment(Atom(xmlString=str(deploymentAtom)))
1324                self.deployments.append(deployment)
1325               
1326                self.addUniqueLinks(self.allActivities, deployment.activities)
1327                self.addUniqueLinks(self.allObs, deployment.obs)
1328                self.addUniqueLinks(self.allDpts, deployment.dpts)
1329        else:
1330            # for DE data, just store the title + link in a Link object
1331            self.dataEntities = []
1332            logging.info("Extracting links data to data entitites")
1333            for data in links:
1334                atom = Atom(xmlString=str(data))
1335                link = Link()
1336                link.title = atom.title
1337                link.href = atom.atomBrowseURL
1338                link.rel = atom.datasetID
1339               
1340                # NB, different deployments may be used by the same DE - so
1341                # avoid duplication
1342                self.addUniqueLinks(self.dataEntities, link)
1343           
1344        logging.info("Finished looking up %s info" %type)
1345
1346
1347    def addUniqueLinks(self, dataArray, links):
1348        '''
1349        Add links to specified array - if they are not already included
1350        @param dataArray: a list, potentially arlready containing links
1351        @param links: a Link or array of Links to add to the dataArray
1352        '''
1353        logging.debug("Adding new links")
1354        if not links:
1355            return
1356       
1357        if type(links) is not list:
1358            links = [links]
1359       
1360        for link in links:
1361            if type(link) is not Link:
1362                logging.warning("Link is not of 'Link' object type (type='%s') - skipping" %type(link))
1363                continue
1364            if link not in dataArray:
1365                logging.debug("- adding unique link")
1366                dataArray.append(link)
1367        logging.debug("Finished adding links")
1368
1369       
1370    def getFullPath(self):
1371        '''
1372        Return full path to atom in eXist, if it exists, or None, otherwise
1373        @return fullPath: string - collection + filename of atom in eXist
1374        '''
1375        # NB, name assigned when atom created in eXist - so if not defined, not
1376        # in eXist
1377        logging.debug("Getting full path to atom")
1378        if self.atomName:
1379            logging.debug("Return full path to atom in eXist")
1380            return self.getDefaultCollectionPath() + self.atomName
1381        logging.debug("Atom doesn't currently exist in eXist - return 'None'")
1382        return None
1383   
1384   
1385    def getSubTypePrettyName(self):
1386        '''
1387        Return the subtype of the atom in a human readable form
1388        @return: sub type of atom as a verbose string
1389        '''
1390        logging.debug("Getting human readable version of atom subtype")
1391        subType = self.SUB_TYPE_NOT_DEFINED_NAME
1392        if self.subtypeID:
1393           subType = self.VTD.tidySubTypeTitle(self.subtypeID)
1394           
1395        logging.debug("- returning subtype: '%s'" %subType)
1396        return subType
Note: See TracBrowser for help on using the repository browser.