Source code for gavo.base.typedmeta

"""
Typed metadata.

These are subclasses of base.MetaValue that deal with special sorts
of metadata.  Most of the time, they organise some extra (hidden or open)
keys, or they do some special HTML rendering.

Use the meta.forKeys class decorator to teach meta.py where to
use your new class.
"""

#c Copyright 2008-2023, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


import datetime
import re
from urllib import parse as urlparse

from gavo import utils
from gavo.base import config
from gavo.base import meta
from gavo.utils import misctricks
from gavo.utils import stanxml


[docs]@meta.forKeys("_related", "referenceURL") class MetaURL(meta.MetaValue): """A meta value containing a link and optionally a title In plain text, this would look like this:: _related:http://foo.bar _related.title: The foo page In XML, you can write:: <meta name="_related" title="The foo page" ivoId="ivo://bar.org/foo">http://foo.bar</meta> or, if you prefer:: <meta name="_related">http://foo.bar <meta name="title">The foo page</meta></meta> These values are used for _related (meaning "visible" links to other services). For links within you data center, use the internallink macro, the argument of which the the "path" to a resource, i.e. RD path/service/renderer; we recommend to use the info renderer in such links as a rule. This would look like this:: <meta name="_related" title="Aspec SSAP" >\internallink{aspec/q/ssa/info}</meta> """ def __init__(self, url, format="plain", title=None): if url: url = url.strip() meta.MetaValue.__init__(self, url, format) self.title = title def _getContentAsHTML(self, content): title = self.title or content return '<a href=%s>%s</a>'%( stanxml.escapeAttrVal(content), stanxml.escapePCDATA(title)) def _addMeta(self, atoms, metaValue): if atoms[0]=="title": self.title = metaValue.content else: meta.MetaValue._addMeta(self, atoms, metaValue)
[docs]@meta.forKeys( # if you add new RelationResourceMeta meta keys, be you'll also need to # amend registry.builders._vrResourceBuilder # VOResource 1.0 terms "servedBy", "serviceFor", "relatedTo", "mirrorOf", "derivedFrom", "uses", # VOResource 1.1 terms "cites", "isSupplementTo", "isSupplementedBy", "isContinuedBy", "continues", "isNewVersionOf", "isPreviousVersionOf", "isPartOf", "hasPart", "isSourceOf", "isDerivedFrom", "isIdenticalTo", "isServiceFor", "isServedBy") class RelatedResourceMeta(meta.MetaValue): """A meta value containing an ivo-id and a name of a related resource. The sort of relationsip is encoded in the meta name, where the terms are defined in the vocabuary http://www.g-vo.org/rdf/voresource/relationship_type (where there are minor lexical deviations from identifiers there to DaCHS' meta names). All relationship metas should look like this (using isSupplementTo as an example; while an ivoId is not mandatory, it rarely makes sense to declare a relationship without it):: isSupplementTo: GAVO TAP service isSupplementTo.ivoId: ivo://org.gavo.dc ``isServedBy`` and ``isServiceFor`` are somewhat special cases, as the service attribute of data publications automatically takes care of them; so, you shouldn't usually need to bother with these two manually. """ def __init__(self, title, format="plain", ivoId=None): meta.MetaValue.__init__(self, title, format) if ivoId is not None: self._addMeta(["ivoId"], meta.MetaValue(ivoId))
[docs]@meta.forKeys("_news") class NewsMeta(meta.MetaValue): """A meta value representing a "news" items. The content is the body of the news. In addition, they have date, author, and role children. In plain text, you would write:: _news: Frobnicated the quux. _news.author: MD _news.date: 2009-03-06 _news.role: updated In XML, you would usually write:: <meta name="_news" author="MD" date="2009-03-06"> Frobnicated the quux. </meta> _news items become serialised into Registry records despite their leading underscores. role then becomes the date's role. """ discardChildrenInHTML = True def __init__(self, content, format="plain", author=None, date=None, role=None): meta.MetaValue.__init__(self, content, format) self.initArgs = format, author, date, role for key in ["author", "date", "role"]: val = locals()[key] if val is not None: self._addMeta([key], meta.MetaValue(val)) def _getContentAsHTML(self, content): authorpart = "" if self.author: authorpart = " (%s)"%self.author return meta.IncludesChildren('<span class="newsitem">%s%s: %s</span>'%( stanxml.escapePCDATA(self.date), stanxml.escapePCDATA(authorpart), meta.MetaValue._getContentAsHTML(self, content))) def _addMeta(self, atoms, metaValue): if atoms[0]=="author": self.author = metaValue.content elif atoms[0]=="date": self.date = metaValue.content elif atoms[0]=="role": self.role = metaValue.content meta.MetaValue._addMeta(self, atoms, metaValue)
[docs]@meta.forKeys("note") class NoteMeta(meta.MetaValue): """A meta value representing a "note" item. This is like a footnote, typically on tables, and is rendered in table infos. The content is the note body. In addition, you want a tag child that gives whatever the note is references as. We recommend numbers. Contrary to other meta items, note content defaults to rstx format. Typically, this works with a column's note attribute. In XML, you would usually write:: <meta name="note" tag="1"> Better ignore this. </meta> """ def __init__(self, content, format="rst", tag=None): meta.MetaValue.__init__(self, content, format) self.initArgs = content, format, tag self.tag = tag or "(untagged)" def _getContentAsHTML(self, content): return ('<dt class="notehead">' '<a name=%s>Note %s</a></dt><dd>%s</dd>')%( stanxml.escapeAttrVal("note-"+self.tag), stanxml.escapePCDATA(self.tag), meta.MetaValue._getContentAsHTML(self, content)) def _addMeta(self, atoms, metaValue): if atoms[0]=="tag": self.tag = metaValue.content else: meta.MetaValue._addMeta(self, atoms, metaValue)
[docs]@meta.forKeys("creationDate", "_dataUpdated", "_metadataUpdated") class DatetimeMeta(meta.MetaValue): """A meta value representing a timestamp. Accessing it, you will get a formatted ISO/DALI string. You can construct them with both strings (that we'll try to parse and bomb if that's not possible) and datetime.datetime objects. """ def __init__(self, content, *args, **kwargs): if isinstance(content, str): if content.startswith("\\metaString{authority"): # backwards compatibility hack: legacy (1.0) userconfigs would try # to pull authority.creationDate via unexpanded macros, and # that breaks now. Hack around it for now. content = meta.getMetaText(meta.CONFIG_META, "authority.creationDate") content = utils.parseISODT(content) if isinstance(content, datetime.datetime): content = utils.formatISODT(content) meta.MetaValue.__init__(self, content, *args, **kwargs)
[docs]@meta.forKeys("info") class InfoItem(meta.MetaValue): """A meta value for info items in VOTables. In addition to the content (which should be rendered as the info element's text content), it contains an infoName and an infoValue. They are only used internally in VOTable generation and might go away without notice. """ def __init__(self, content, format="plain", infoName=None, infoValue=None, infoId=None): meta.MetaValue.__init__(self, content, format) self.initArgs = content, format, infoName, infoValue, infoId self.infoName, self.infoValue = infoName, infoValue self.infoId = infoId
[docs]@meta.forKeys("logo", "creator.logo") class LogoMeta(meta.MetaValue): """A MetaValue corresponding to a small image. These are rendered as little images in HTML. In XML meta, you can say:: <meta name="_somelogo" type="logo">http://foo.bar/quux.png</meta> """ def __init__(self, content="", *args, **kwargs): if not "\\" in content and not re.match("https?://", content): # see above on avoiding circular imports content = urlparse.urljoin( config.get("web", "serverURL"), content) meta.MetaValue.__init__(self, content, *args, **kwargs) def _getContentAsHTML(self, content): return '<img class="metalogo" src="%s" alt="[Logo]"/>'%( str(content).strip())
[docs]@meta.forKeys("source") class BibcodeMeta(meta.MetaValue): """A MetaValue that may contain bibcodes, which are rendered as links into ADS. """ def _makeADSLink(self, matOb): urlBibcode = urlparse.quote(matOb.group(0)) adsURL = f"https://ui.adsabs.harvard.edu/abs/{urlBibcode}/abstract" return '<a href="{}">{}</a>'.format( adsURL, stanxml.escapePCDATA(matOb.group(0))) def _getContentAsHTML(self, content): content = str(content) mat = misctricks.BIBCODE_PATTERN.match(content) if mat: return self._makeADSLink(mat) else: return stanxml.escapePCDATA(content)
[docs]@meta.forKeys("votlink") class VotLinkMeta(meta.MetaValue): """A MetaValue serialized into VOTable links (or, ideally, analogous constructs). This exposes the various attributes of VOTable LINKs as href linkname, contentType, and role. You cannot set ID here; if this ever needs referencing, we'll need to think about it again. The href attribute is simply the content of our meta (since there's no link without href), and there's never any content in VOTable LINKs). You could thus say:: votlink: http://docs.g-vo.org/DaCHS votlink.role: doc votlink.contentType: text/html votlink.linkname: GAVO DaCHS documentation """ def __init__(self, href, format="plain", linkname=None, contentType=None, role=None): meta.MetaValue.__init__(self, href, format) for key in ["linkname", "contentType", "role"]: val = locals()[key] if val is not None: self._addMeta([key], meta.MetaValue(val))
[docs]@meta.forKeys("_example") class ExampleMeta(meta.MetaValue): """A MetaValue to keep VOSI examples in. All of these must have a title, which is also used to generate references. These also are in reStructuredText by default, and changing that probably makes no sense at all, as these will always need interpreted text roles for proper markup. Thus, the usual pattern here is:: <meta name="_example" title="An example for _example"> See docs_ .. _docs: http://docs.g-vo.org </meta> """ def __init__(self, content, format="rst", title=None): if title is None: raise meta.MetaError("_example meta must always have a title") meta.MetaValue.__init__(self, content, format) self._addMeta(["title"], meta.MetaValue(title))
[docs]@meta.forKeys("doi") class DOIMeta(meta.MetaValue): """A MetaValue for a DOI. This lets people construct DOI meta with or without a doi: prefix. It also creates landing page links in HTML. """ def __init__(self, content, **kwargs): if content: if content.startswith("doi:"): content = content[4:] if not re.match("[0-9.]+/", content): raise meta.MetaValueError("%s does not look like a DOI"%content) meta.MetaValue.__init__(self, content.strip(), **kwargs) def _getContentAsHTML(self, content): return '<a href=%s>%s</a>'%( stanxml.escapeAttrVal("http://dx.doi.org/"+content), stanxml.escapePCDATA(content))
# subject meta is in protocols.vocabularies