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