| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 Code dealing with meta information.
3
4 In DaCHS, tree-structured metadata can be attached to tables, services,
5 resources as a whole, and some other structures. This module provides
6 a python mixin to keep and manipulate such metadata.
7
8 We deal with VO-style RMI metadata but also allow custom metadata. Custom
9 metadata keys should usually start with _.
10
11 See develNotes for some discussion of why this is so messy and an explanation
12 of why in particular addMeta and helpers are a minefield of selections.
13
14 The rough plan to do this looks like this:
15
16 Metadata is kept in containers that mix in MetaMixin. Meta information is
17 accessed through keys of the form <atom>{.<atom>} The first atom is the
18 primary key. An atom consists exclusively of ascii letters and the
19 underscore.
20
21 There's a meta mixin having the methods addMeta and getMeta that care
22 about finding metadata including handover. For compound metadata, they
23 will split the key and hand over to the parent if the don't have a meta
24 item for the main key.
25 """
26
27 #c Copyright 2008-2019, the GAVO project
28 #c
29 #c This program is free software, covered by the GNU GPL. See the
30 #c COPYING file in the source distribution.
31
32
33 from __future__ import print_function
34
35 import re
36 import textwrap
37 import urllib
38
39 from gavo import utils
40 from gavo.base import attrdef
41 from gavo.base import common
42 from gavo.utils import stanxml
43 from gavo.utils import misctricks
44
45
46 # This dictionary maps meta atoms that were found to be somehow
47 # misleading or confusing to the ones that are now used. This
48 # is so RDs aren't broken.
49 DEPRECATED_ATOMS = {
50 "_associatedDatalinkSvc": "_associatedDatalinkService",
51 }
56 """A base class for metadata-related errors.
57
58 MetaErrors have a carrier attribute that should point to the MetaMixin
59 item queried. Metadata propagation makes this a bit tricky, but we
60 should at least try; for setMeta and addMeta, the top-level entry
61 functions manipulate the carrier attributes for this purpose.
62
63 To yield useful error messages, leave carrier at its default None
64 only when you really have no idea what the meta will end up on.
65 """
70
76
80
85
88 """is raised when a meta value somehow has the wrong cardinality (e.g.,
89 on attempting to stringify a sequence meta).
90 """
97
102
106
107
108 _metaPat = re.compile(r"([a-zA-Z_][\w-]*)(?:\.([a-zA-Z_][\w-]*))*$")
109 _primaryPat = re.compile(r"([a-zA-Z_][\w-]*)(\.|$)")
113 key = _primaryPat.match(metaKey)
114 if not key or not key.group(1):
115 raise MetaSyntaxError("Invalid meta key: %s"%metaKey, None)
116 return key.group(1)
117
120 if not _metaPat.match(metaKey):
121 raise MetaSyntaxError("Invalid meta key: %s"%metaKey, None)
122
123 parsed = metaKey.split(".")
124 for atom in parsed:
125 if atom in DEPRECATED_ATOMS:
126 utils.sendUIEvent("Warning", "Deprecated meta atom %s used in %s;"
127 " please change the atom to %s."%(
128 atom, metaKey, DEPRECATED_ATOMS[atom]))
129 return [DEPRECATED_ATOMS.get(a, a) for a in parsed]
130
131 return parsed
132
135 """parser meta key/value pairs from metaStream and adds them to
136 metaContainer.
137
138 If clearItems is true, for each key found in the metaStream there's
139 first a delMeta for that key executed. This is for re-parsing
140 meta streams.
141
142 The stream format is:
143
144 - continuation lines with backslashes, where any sequence of
145 backslash, (cr?) lf (blank or tab)* is replaced by nothing.
146 - comments are lines like (ws*)# anything
147 - empty lines are no-ops
148 - all other lines are (ws*)<key>(ws*):(ws*)value(ws*)
149 - if a key starts with !, any meta info for the key is cleared before
150 setting
151 """
152 if metaStream is None:
153 return
154
155 # handle continuation lines
156 metaStream = re.sub("\\\\\r?\n[\t ]*", "", metaStream)
157
158 keysSeen = set()
159
160 for line in metaStream.split("\n"):
161 line = line.strip()
162 if line.startswith("#") or not line:
163 continue
164 try:
165 key, value = line.split(":", 1)
166 except ValueError:
167 raise MetaSyntaxError("%s is no valid line for a meta stream"%
168 repr(line), None,
169 hint="In general, meta streams contain lines like 'meta.key:"
170 " meta value; see also the documentation.")
171
172 key = key.strip()
173 if key.startswith("!"):
174 key = key[1:]
175 metaContainer.delMeta(key)
176
177 if key not in keysSeen and clearItems:
178 metaContainer.delMeta(key)
179 keysSeen.add(key)
180 metaContainer.addMeta(key, value.strip())
181
184 """A structure parser that kicks in when meta information is
185 parsed from XML.
186
187 This parser can also handle the notation with an attribute-less
188 meta tag and lf-separated colon-pairs as content.
189 """
190 # These are constructed a lot, so let's keep __init__ as clean as possible,
191 # shall we?
193 self.container, self.nextParser = container, nextParser
194 self.attrs = {}
195 self.children = [] # containing key, metaValue pairs
196 self.next = None
197
199 # this parse can be a temporary meta parent for children; we
200 # record them here an play them back when we have created
201 # the meta value itself
202 self.children.append((key, content, kwargs))
203
205 # see addMeta comment on why we need to do magic here.
206 self.children.append(("!"+key, content, kwargs))
207
209 if name=="meta":
210 return MetaParser(self, self)
211 else:
212 self.next = name
213 return self
214
216 if self.next is None:
217 self.attrs[str(name)] = value
218 else:
219 self.attrs[str(self.next)] = value
220 return self
221
223 content = self.attrs.pop("content_", "")
224 if not self.attrs: # content only, parse this as a meta stream
225 parseMetaStream(self.container, content)
226
227 else:
228 try:
229 content = utils.fixIndentation(content, "", 1).rstrip()
230 except common.Error as ex:
231 raise utils.logOldExc(common.StructureError("Bad text in meta value"
232 " (%s)"%ex))
233 if not "name" in self.attrs:
234 raise common.StructureError("meta elements must have a"
235 " name attribute")
236 metaKey = self.attrs.pop("name")
237 if metaKey.startswith("!"):
238 self.container.setMeta(metaKey[1:], content, **self.attrs)
239 else:
240 self.container.addMeta(metaKey, content, **self.attrs)
241
242 # meta elements can have children; add these, properly fudging
243 # their keys
244 for key, content, kwargs in self.children:
245 if key.startswith("!"):
246 fullKey = "%s.%s"%(metaKey, key[1:])
247 self.container.setMeta(fullKey, content, **kwargs)
248 else:
249 fullKey = "%s.%s"%(metaKey, key)
250 self.container.addMeta(fullKey, content, **kwargs)
251
253 if name=="meta":
254 try:
255 self._doAddMeta()
256 except TypeError as msg:
257 raise utils.StructureError("While constructing meta: %s"%msg)
258 return self.nextParser
259
260 else:
261 self.next = None
262 return self
263
266 """An attribute magically inserting meta values to Structures mixing
267 in MetaMixin.
268
269 We don't want to keep metadata itself in structures for performance
270 reasons, so we define a parser of our own in here.
271 """
272 typedesc = "Metadata"
273
275 attrdef.AttributeDef.__init__(self, "meta_",
276 attrdef.Computed, description)
277 self.xmlName_ = "meta"
278
279 @property
282
284 self.meta_ = value
285
287 """creates a deep copy of the current meta dictionary and returns it.
288
289 This is used when a MetaMixin's attribute is set to copyable and a
290 meta carrier is copied. As there's currently no way to make the
291 _metaAttr copyable, this isn't called by itself. If you
292 must, you can manually call this (_metaAttr.getCopy), but that'd
293 really be an indication the interface needs changes.
294
295 Note that the copying semantics is a bit funky: Copied values
296 remain, but on write, sequences are replaced rather than added to.
297 """
298 oldDict = parent.meta_
299 newMeta = {}
300 for key, mi in oldDict.iteritems():
301 newMeta[key] = mi.copy()
302 return newMeta
303
306
308 return ("**meta** -- a piece of meta information, giving at least a name"
309 " and some content. See Metadata_ on what is permitted here.")
310
312 def doIter(metaDict):
313 for key, item in metaDict.iteritems():
314 for value in item:
315 yield ("start", "meta", None)
316 yield ("value", "name", key)
317 if value.getContent():
318 yield ("value", "content_", value.getContent())
319
320 if value.meta_:
321 for ev in doIter(value.meta_):
322 yield ev
323 yield ("end", "meta", None)
324
325 for ev in doIter(instance.meta_):
326 yield ev
327
330 """is a mixin for entities carrying meta information.
331
332 The meta mixin provides the followng methods:
333
334 - setMetaParent(m) -- sets the name of the meta container enclosing the
335 current one. m has to have the Meta mixin as well.
336 - getMeta(key, propagate=True, raiseOnFail=False, default=None) -- returns
337 meta information for key or default.
338 - addMeta(key, metaItem, moreAttrs) -- adds a piece of meta information
339 here. Key may be a compound, metaItem may be a text, in which
340 case it will be turned into a proper MetaValue taking key and
341 moreAttrs into account.
342 - setMeta(key, metaItem) -- like addMeta, only previous value(s) are
343 overwritten
344 - delMeta(key) -- removes a meta value(s) for key.
345
346 When querying meta information, by default all parents are queried as
347 well (propagate=True).
348
349 Metadata is not copied when the embedding object is copied.
350 That, frankly, has not been a good design descision, and there should
351 probably be a way to pass copypable=True to the mixin's attribute
352 definition.
353 """
354 _metaAttr = MetaAttribute()
355
357 """is a constructor for standalone use. You do *not* want to
358 call this when mixing into a Structure.
359 """
360 self.meta_ = {}
361
363 try:
364 self.__metaParent # assert existence
365 return True
366 except AttributeError:
367 return False
368
371
375
381
383 try:
384 return self._getFromAtom(atoms[0])._getMeta(atoms[1:],
385 acceptSequence=acceptSequence)
386 except NoMetaKey:
387 pass # See if parent has the key
388
389 if propagate:
390 if self.__hasMetaParent():
391 return self.__metaParent._getMeta(atoms, propagate,
392 acceptSequence=acceptSequence)
393 else:
394 return configMeta._getMeta(atoms, propagate=False,
395 acceptSequence=acceptSequence)
396
397 raise NoMetaKey("No meta item %s"%".".join(atoms), carrier=self)
398
399 - def getMeta(self, key, propagate=True,
400 raiseOnFail=False, default=None, acceptSequence=False):
401 try:
402 try:
403 return self._getMeta(
404 parseKey(key), propagate, acceptSequence=acceptSequence)
405 except NoMetaKey as ex:
406 if raiseOnFail:
407 ex.key = key
408 raise
409 except MetaCardError as ex:
410 raise utils.logOldExc(
411 MetaCardError(ex.origMsg, hint=ex.hint, key=key))
412 except MetaError as ex:
413 ex.carrier = self
414 raise
415 return default
416
418 mine, others = atoms[0], atoms[1:]
419 for mv in self.meta_.get(mine, []):
420 if others:
421 for child in mv._iterMeta(others):
422 yield child
423 else:
424 yield mv
425
427 """yields all MetaValues for key.
428
429 This will traverse down all branches necessary to yield, in sequence,
430 all MetaValues reachable by key.
431
432 If propagation is enabled, the first meta carrier that has at least
433 one item exhausts the iteration.
434
435 (this currently doesn't return an iterator but a sequence; that's an
436 implementation detail, though. You should only assume whatever comes
437 back is iterable)
438 """
439 val = list(self._iterMeta(parseKey(key)))
440 if not val and propagate:
441 if self.__hasMetaParent():
442 val = self.__metaParent.iterMeta(key, propagate=True)
443 else:
444 val = configMeta.iterMeta(key, propagate=False)
445 return val
446
448 """iterates over all meta items this container has.
449
450 Each item consists of key, MetaValue. Multiple MetaValues per
451 key may be given.
452
453 This will not iterate up, i.e., in general, getMeta will succeed
454 for more keys than what's given here.
455 """
456 class Accum(object):
457 def __init__(self):
458 self.items = []
459 self.keys = []
460 def startKey(self, key):
461 self.keys.append(key)
462 def enterValue(self, value):
463 self.items.append((".".join(self.keys), value))
464 def endKey(self, key):
465 self.keys.pop()
466
467 accum = Accum()
468 self.traverse(accum)
469 return accum.items
470
472 value = self.getMeta(key, raiseOnFail=raiseOnFail, propagate=propagate)
473 if value:
474 builder.startKey(key)
475 value.traverse(builder)
476 builder.endKey(key)
477 return builder.getResult()
478
481
483 if atom in self.meta_:
484 return self.meta_[atom]
485 raise NoMetaKey("No meta child %s"%atom, carrier=self)
486
488 return self.meta_.keys()
489
490 # XXX TRANS remove; this is too common a name
491 keys = getMetaKeys
492
495
497 primary = atoms[0]
498 if primary in self.meta_:
499 self.meta_[primary]._addMeta(atoms[1:], metaValue)
500 else:
501 self.meta_[primary] = MetaItem.fromAtoms(atoms[1:], metaValue)
502
504 """adds metaItem to self under key.
505
506 moreAttrs can be additional keyword arguments; these are used by
507 the XML constructor to define formats or to pass extra items
508 to special meta types.
509
510 For convenience, this returns the meta container.
511 """
512 try:
513 if doMetaOverride(self, key, metaValue, moreAttrs):
514 return
515
516 self._addMeta(parseKey(key), ensureMetaValue(metaValue, moreAttrs))
517 except MetaError as ex:
518 ex.carrier = self
519 raise
520
521 return self
522
524 if atoms[0] not in self.meta_:
525 return
526 if len(atoms)==1:
527 del self.meta_[atoms[0]]
528 else:
529 child = self.meta_[atoms[0]]
530 child._delMeta(atoms[1:])
531 if child.isEmpty():
532 del self.meta_[atoms[0]]
533
535 """removes a meta item from this meta container.
536
537 This will not propagate, i.e., getMeta(key) might still
538 return something unless you give propagate=False.
539
540 It is not an error do delete an non-existing meta key.
541 """
542 self._delMeta(parseKey(key))
543
545 """replaces any previous meta content of key (on this container)
546 with value.
547 """
548 self.delMeta(key)
549 self.addMeta(key, value, **moreAttrs)
550
552 for key, item in self.meta_.iteritems():
553 builder.startKey(key)
554 item.traverse(builder)
555 builder.endKey(key)
556
558 """sets a copy of other's meta items on self.
559 """
560 for key in other.getMetaKeys():
561 orig = other.getMeta(key)
562 if orig is not None:
563 # (orig None can happen for computed metadata)
564 self.meta_[key] = orig.copy()
565
567 """marks the meta item key, if existing, as original.
568
569 This is for when a meta container has copied metadata. DaCHS'
570 default behaviour is that a subsequent addMeta will clear the
571 copied content. Call this method for the key in question to
572 enable adding to copied metadata.
573 """
574 item = self.getMeta(key)
575 if item is not None and hasattr(item, "copied"):
576 del item.copied
577
578
579 # Global meta, items get added from config
580 configMeta = MetaMixin()
584 """A MetaMixin for classes that want to implement defaults for
585 unresolvable meta items.
586
587 If getMeta would return a NoMetaKey, this mixin's getMeta will check
588 the presence of a _meta_<key> method (replacing dots with two underscores)
589 and, if it exists, returns whatever it returns. Otherwise, the
590 exception will be propagated.
591
592 The _meta_<key> methods should return MetaItems; if something else
593 is returned, it is wrapped in a MetaValue.
594
595 On copying such metadata, the copy will retain the value on the original
596 if it has one. This does not work for computed metadata that would be
597 inherited.
598 """
600 try:
601 return MetaMixin._getFromAtom(self, atom)
602 except NoMetaKey:
603
604 methName = "_meta_"+atom
605 if hasattr(self, methName):
606 res = getattr(self, methName)()
607 if res is None:
608 raise
609 return ensureMetaItem(res)
610
611 raise
612
614 computedKeys = []
615 for name in dir(self):
616 if name.startswith("_meta_"):
617 computedKeys.append(name[6:])
618 return MetaMixin.getMetaKeys(self)+computedKeys
619
633
636 """is a collection of homogenous MetaValues.
637
638 All MetaValues within a MetaItem have the same key.
639
640 A MetaItem contains a list of children MetaValues; it is usually
641 constructed with just one MetaValue, though. Use the alternative
642 constructor formSequence if you already have a sequence of
643 MetaValues. Or, better, use the ensureMetaItem utility function.
644
645 The last added MetaValue is the "active" one that will be changed
646 on _addMeta calls.
647 """
650
651 @classmethod
657
658 @classmethod
660 if len(atoms)==0: # This will become my child.
661 return cls(metaValue)
662
663 elif len(atoms)==1: # Create a MetaValue with the target as child
664 mv = MetaValue()
665 mv._setForAtom(atoms[0], cls(ensureMetaValue(metaValue)))
666 return cls(mv)
667
668 else: # Create a MetaValue with an ancestor of the target as child
669 mv = MetaValue()
670 mv._setForAtom(atoms[0], cls.fromAtoms(atoms[1:],
671 ensureMetaValue(metaValue)))
672 return cls(mv)
673
675 try:
676 res = self.getContent(targetFormat="text")
677 return res
678 except MetaError:
679 return ", ".join(m.getContent(targetFormat="text")
680 for m in self.children)
681
682 __unicode__ = __str__
683
686
689
692
695
697 self.children[-1].addContent(item)
698
700 # XXX should we force metaValue to be "compatible" with what's
701 # already in children?
702 if hasattr(self, "copied"):
703 self.children = []
704 delattr(self, "copied")
705 if metaValue is None:
706 metaValue = MetaValue(None)
707 assert isinstance(metaValue, MetaValue)
708 self.children.append(metaValue)
709
711 if atoms:
712 if len(self.children)!=1 and not acceptSequence:
713 raise MetaCardError("Meta sequence in branch for getMeta")
714 else:
715 return self.children[0]._getMeta(
716 atoms, acceptSequence=acceptSequence)
717 return self
718
720 # See above for this mess -- I'm afraid it has to be that complex
721 # if we want to be able to build compound sequences using text labels.
722
723 # Case 1: Direct child of MetaMixin, sequence addition
724 if not atoms:
725 self.addChild(metaValue)
726 else:
727 self.children[-1]._addMeta(atoms, metaValue)
728
730 if len(self.children)==1:
731 return self.children[0].getMeta(key, *args, **kwargs)
732 else:
733 raise MetaCardError("No getMeta for meta value sequences",
734 carrier=self, key=key)
735
738 if len(self.children)==1 or acceptSequence:
739 return self.children[0].getContent(targetFormat, macroPackage)
740 raise MetaCardError("getContent not allowed for sequence meta items",
741 carrier=self)
742
744 newChildren = []
745 for c in self.children:
746 c._delMeta(atoms)
747 if not c.isEmpty():
748 newChildren.append(c)
749 self.children = newChildren
750
756
758 """returns a deep copy of self.
759 """
760 newOb = self.__class__("")
761 newOb.children = [mv.copy() for mv in self.children]
762 newOb.copied = True
763 return newOb
764
767
770 # we don't want whitespace after hyphens in plain meta strings (looks
771 # funny in HTML), so fix wordsep_re
772 wordsep_re = re.compile(
773 r'(\s+|' # any whitespace
774 r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash
775
778 """is a piece of meta information about a resource.
779
780 The content is always a string.
781
782 The text content may be in different formats, notably
783
784 - literal
785
786 - rst (restructured text)
787
788 - plain (the default)
789
790 - raw (for embedded HTML, mainly -- only use this if you know
791 the item will only be embedded into HTML templates).
792 """
793 knownFormats = set(["literal", "rst", "plain", "raw"])
794 paragraphPat = re.compile("\n\\s*\n")
795 consecutiveWSPat = re.compile("\s\s+")
796 plainWrapper = _NoHyphenWrapper(break_long_words=False,
797 replace_whitespace=True)
798
800 self.initArgs = content, format
801 MetaMixin.__init__(self)
802 if format not in self.knownFormats:
803 raise common.StructureError(
804 "Unknown meta format '%s'; allowed are %s."%(
805 format, ", ".join(self.knownFormats)))
806 self.content = content
807 self.format = format
808 self._preprocessContent()
809
812
814 return self.getContent()
815
817 if self.format=="plain" and self.content is not None:
818 self.content = "\n\n".join(self.plainWrapper.fill(
819 self.consecutiveWSPat.sub(" ", para))
820 for para in self.paragraphPat.split(self.content))
821
823 return content
824
826 if block:
827 encTag = "p"
828 else:
829 encTag = "span"
830 if self.format=="literal":
831 return '<%s class="literalmeta">%s</%s>'%(encTag, content, encTag)
832 elif self.format=="plain":
833 return "\n".join('<%s class="plainmeta">%s</%s>'%(encTag, p, encTag)
834 for p in content.split("\n\n"))
835 elif self.format=="rst":
836 # XXX TODO: figure out a way to have them block=False
837 return metaRstToHtml(content)
838 elif self.format=="raw":
839 return content
840
842 if hasattr(macroPackage, "expand") and "\\" in self.content:
843 return macroPackage.expand(self.content)
844 return self.content
845
847 content = self.getExpandedContent(macroPackage)
848 if targetFormat=="text":
849 return self._getContentAsText(content)
850 elif targetFormat=="html":
851 return self._getContentAsHTML(content)
852 elif targetFormat=="blockhtml":
853 return self._getContentAsHTML(content, block=True)
854 else:
855 raise MetaError("Invalid meta target format: %s"%targetFormat)
856
858 return unicode(self).encode(enc)
859
863
865 # Cases continued from MetaItem._addMeta
866 # Case 2: Part of a compound, metaValue is to become direct child
867 if len(atoms)==1:
868 primary = atoms[0]
869
870 # Case 2.1: Requested child exists
871 if self._hasAtom(primary):
872 self._getFromAtom(primary).addChild(metaValue, primary)
873
874 # Case 2.2: Requested child does not exist
875 else:
876 self._setForAtom(primary, MetaItem(metaValue))
877
878 # Case 3: metaItem will become an indirect child
879 else:
880 primary = atoms[0]
881
882 # Case 3.1: metaValue will become a child of an existing child of ours
883 if self._hasAtom(primary):
884 self._getFromAtom(primary)._addMeta(atoms[1:], metaValue)
885
886 # Case 3.2: metaItem is on a branch that needs yet to be created.
887 else:
888 self._setForAtom(primary, MetaItem.fromAtoms(atoms[1:],
889 metaValue))
890
892 """returns a deep copy of self.
893 """
894 newOb = self.__class__(*self.initArgs)
895 newOb.format, newOb.content = self.format, self.content
896 newOb.copyMetaFrom(self)
897 return newOb
898
899
900 ################## individual meta types (factor out to a new module)
901
902 -class IncludesChildren(unicode):
903 """a formatted result that already includes all meta children.
904
905 This is returned from some of the special meta types' HTML formatters
906 to keep the HTMLMetaBuilder from adding meta items that are already
907 included in their HTML.
908 """
909
912 """A meta value containing a link and optionally a title
913
914 In plain text, this would look like
915 this::
916
917 _related:http://foo.bar
918 _related.title: The foo page
919
920 In XML, you can write::
921
922 <meta name="_related" title="The foo page"
923 ivoId="ivo://bar.org/foo">http://foo.bar</meta>
924
925 or, if you prefer::
926
927 <meta name="_related">http://foo.bar
928 <meta name="title">The foo page</meta></meta>
929
930 These values are used for _related (meaning "visible" links to other
931 services).
932
933 For links within you data center, use the internallink macro, the argument
934 of which the the "path" to a resource, i.e. RD path/service/renderer;
935 we recommend to use the info renderer in such links as a rule. This would
936 look like this::
937
938 <meta name="_related" title="Aspec SSAP"
939 >\internallink{aspec/q/ssa/info}</meta>
940
941 """
945
949
955
958 """A meta value containing an ivo-id and a name of a related resource.
959
960 These all are translated to relationship elements in VOResource
961 renderings. These correspond to the terms in the official relationship
962 vocabulary http://docs.g-vo.org/vocab-test/relationship_type. There,
963 the camelCase terms are preferred, and for DaCHS meta, they are written
964 with a lowercase initial.
965
966 Relationship metas should look like this::
967
968 servedBy: GAVO TAP service
969 servedBy.ivoId: ivo://org.gavo.dc
970
971 ``servedBy`` and ``serviceFor`` are somewhat special cases, as
972 the service attribute of data publications automatically takes care
973 of them; so, you shouldn't usually need to bother with these two manually.
974 """
979
982 """A meta value representing a "news" items.
983
984 The content is the body of the news. In addition, they have
985 date, author, and role children. In plain text, you would write::
986
987 _news: Frobnicated the quux.
988 _news.author: MD
989 _news.date: 2009-03-06
990 _news.role: updated
991
992 In XML, you would usually write::
993
994 <meta name="_news" author="MD" date="2009-03-06">
995 Frobnicated the quux.
996 </meta>
997
998 _news items become serialised into Registry records despite their
999 leading underscores. role then becomes the date's role.
1000 """
1001 discardChildrenInHTML = True
1002
1005 MetaValue.__init__(self, content, format)
1006 self.initArgs = format, author, date, role
1007 for key in ["author", "date", "role"]:
1008 val = locals()[key]
1009 if val is not None:
1010 self._addMeta([key], MetaValue(val))
1011
1013 authorpart = ""
1014 if self.author:
1015 authorpart = " (%s)"%self.author
1016 return IncludesChildren('<span class="newsitem">%s%s: %s</span>'%(
1017 self.date, authorpart, MetaValue._getContentAsHTML(self, content)))
1018
1027
1030 """A meta value representing a "note" item.
1031
1032 This is like a footnote, typically on tables, and is rendered in table
1033 infos.
1034
1035 The content is the note body. In addition, you want a tag child that
1036 gives whatever the note is references as. We recommend numbers.
1037
1038 Contrary to other meta items, note content defaults to rstx format.
1039
1040 Typically, this works with a column's note attribute.
1041
1042 In XML, you would usually write::
1043
1044 <meta name="note" tag="1">
1045 Better ignore this.
1046 </meta>
1047 """
1049 MetaValue.__init__(self, content, format)
1050 self.initArgs = content, format, tag
1051 self.tag = tag
1052
1054 return ('<dt class="notehead">'
1055 '<a name="note-%s">Note %s</a></dt><dd>%s</dd>')%(
1056 self.tag,
1057 self.tag,
1058 MetaValue._getContentAsHTML(self, content))
1059
1065
1068 """A meta value for info items in VOTables.
1069
1070 In addition to the content (which should be rendered as the info element's
1071 text content), it contains an infoName and an infoValue.
1072
1073 They are only used internally in VOTable generation and might go away
1074 without notice.
1075 """
1082
1085 """A MetaValue corresponding to a small image.
1086
1087 These are rendered as little images in HTML. In XML meta, you can
1088 say::
1089
1090 <meta name="_somelogo" type="logo">http://foo.bar/quux.png</meta>
1091 """
1095
1098 """A MetaValue that may contain bibcodes, which are rendered as links
1099 into ADS.
1100 """
1102 # local import of config to avoid circular import.
1103 # (move special metas to separate module?)
1104 from gavo.base import config
1105 adsMirror = config.get("web", "adsMirror")
1106 return '<a href="%s">%s</a>'%(
1107 adsMirror+"/abs/%s"%urllib.quote(matOb.group(0)),
1108 matOb.group(0))
1109
1111 return misctricks.BIBCODE_PATTERN.sub(self._makeADSLink, unicode(content)
1112 ).replace("&", "&")
1113 # Yikes. We should really quote such raw HTML properly...
1117 """A MetaValue serialized into VOTable links (or, ideally,
1118 analoguous constructs).
1119
1120 This exposes the various attributes of VOTable LINKs as href
1121 linkname, contentType, and role. You cannot set ID here; if this ever
1122 needs referencing, we'll need to think about it again.
1123 The href attribute is simply the content of our meta (since
1124 there's no link without href), and there's never any content
1125 in VOTable LINKs).
1126
1127 You could thus say::
1128
1129 votlink: http://docs.g-vo.org/DaCHS
1130 votlink.role: doc
1131 votlink.contentType: text/html
1132 votlink.linkname: GAVO DaCHS documentation
1133 """
1141
1144 """A MetaValue to keep VOSI examples in.
1145
1146 All of these must have a title, which is also used to generate
1147 references.
1148
1149 These also are in reStructuredText by default, and changing
1150 that probably makes no sense at all, as these will always need
1151 interpreted text roles for proper markup.
1152
1153 Thus, the usual pattern here is::
1154
1155 <meta name="_example" title="An example for _example">
1156 See docs_
1157
1158 .. _docs: http://docs.g-vo.org
1159 </meta>
1160 """
1166
1167
1168 META_CLASSES_FOR_KEYS = {
1169 "_related": MetaURL,
1170 "_example": ExampleMeta,
1171
1172 # if you add new RelationResourceMeta meta keys, be you'll also need to
1173 # amend registry.builders._vrResourceBuilder
1174 # VOResource 1.0 terms
1175 "servedBy": RelatedResourceMeta,
1176 "serviceFor": RelatedResourceMeta,
1177 "relatedTo": RelatedResourceMeta,
1178 "mirrorOf": RelatedResourceMeta,
1179 "derivedFrom": RelatedResourceMeta,
1180 "uses": RelatedResourceMeta,
1181
1182 # VOResource 1.1 terms
1183 "cites": RelatedResourceMeta,
1184 "isSupplementTo": RelatedResourceMeta,
1185 "isSupplementedBy": RelatedResourceMeta,
1186 "isContinuedBy": RelatedResourceMeta,
1187 "continues": RelatedResourceMeta,
1188 "isNewVersionOf": RelatedResourceMeta,
1189 "isPreviousVersionOf": RelatedResourceMeta,
1190 "isPartOf": RelatedResourceMeta,
1191 "hasPart": RelatedResourceMeta,
1192 "isSourceOf": RelatedResourceMeta,
1193 "isDerivedFrom": RelatedResourceMeta,
1194 "isIdenticalTo": RelatedResourceMeta,
1195 "isServiceFor": RelatedResourceMeta,
1196 "isServedBy": RelatedResourceMeta,
1197
1198 "_news": NewsMeta,
1199 "referenceURL": MetaURL,
1200 "info": InfoItem,
1201 "logo": LogoMeta,
1202 "source": BibcodeMeta,
1203 "note": NoteMeta,
1204 "votlink": VotLinkMeta,
1205 "creator.logo": LogoMeta,
1206 "logo": LogoMeta,
1207 }
1211 """handles the adding of the creator meta.
1212
1213 value is empty or a parsed meta value, this does nothing (which will cause
1214 addMeta to do its default operation).
1215
1216 If value is a non-empty string, it will be split along semicolons
1217 to produce individual creator metas with names .
1218 """
1219 if not value or isinstance(value, MetaValue):
1220 return None
1221
1222 for authName in (s.strip() for s in value.split(";")):
1223 container.addMeta("creator",
1224 MetaValue().addMeta("name", authName))
1225
1226 return True
1227
1230 #for debugging
1231 md = metaContainer.meta_
1232 for childName in md:
1233 childKey = curKey+"."+childName
1234 for child in md[childName]:
1235 print(childKey, child.getContent("text"))
1236 printMetaTree(child, childKey)
1237
1240 """makes a MetaValue out of val and a dict moreAttrs unless val already
1241 is a MetaValue.
1242 """
1243 if isinstance(val, MetaValue):
1244 return val
1245 return MetaValue(val, **moreAttrs)
1246
1249 """ensures that thing is a MetaItem.
1250
1251 If it is not, thing is turned into a sequence of MetaValues, which is
1252 then packed into a MetaItem.
1253
1254 Essentially, if thing is not a MetaValue, it is made into one with
1255 moreAttrs. If thing is a list, this recipe is used for all its items.
1256 """
1257 if isinstance(thing, MetaItem):
1258 return thing
1259
1260 if isinstance(thing, list):
1261 return MetaItem.fromSequence(
1262 [ensureMetaValue(item, moreAttrs) for item in thing])
1263
1264 return MetaItem(ensureMetaValue(thing, moreAttrs))
1265
1268 """creates the representation of metaKey/metaValue in container.
1269
1270 If metaKey does not need any special action, this returns None.
1271
1272 This gets called from one central point in MetaMixin.addMeta, and
1273 essentially all magic involved should be concentrated here.
1274 """
1275 if metaKey in META_CLASSES_FOR_KEYS and not isinstance(metaValue, MetaValue):
1276 try:
1277 container.addMeta(metaKey,
1278 META_CLASSES_FOR_KEYS[metaKey](metaValue, **extraArgs))
1279 return True
1280 except TypeError:
1281 raise utils.logOldExc(MetaError(
1282 "Invalid arguments for %s meta items: %s"%(metaKey,
1283 utils.safe_str(extraArgs)), None))
1284
1285 # let's see if there's some way to rationalise this kind of thing
1286 # later.
1287 if metaKey=="creator":
1288 return _doCreatorMetaOverride(container, metaValue)
1289
1290 # fallthrough: let addMeta do its standard thing.
1291
1292 -def getMetaText(ob, key, default=None, **kwargs):
1293 """returns the meta item key form ob in text form if present, default
1294 otherwise.
1295
1296 You can pass getMeta keyword arguments (except default).
1297
1298 Additionally, there's acceptSequence; if set to true, this will
1299 return the first item of a sequence-valued meta item rather than
1300 raising an error.
1301
1302 ob will be used as a macro package if it has an expand method; to
1303 use something else as the macro package, pass a macroPackage keyword
1304 argument.
1305 """
1306 macroPackage = ob
1307 if "macroPackage" in kwargs:
1308 macroPackage = kwargs.pop("macroPackage")
1309 acceptSequence = kwargs.get("acceptSequence", False)
1310
1311 m = ob.getMeta(key, default=None, **kwargs)
1312 if m is None:
1313 return default
1314 try:
1315 return m.getContent(
1316 macroPackage=macroPackage, acceptSequence=acceptSequence)
1317 except MetaCardError as ex:
1318 raise MetaCardError(ex.origMsg, carrier=ex.carrier, hint=ex.hint,
1319 key=key)
1320
1323 """A base class for meta builders.
1324
1325 Builders are passed to a MetaItem's traverse method or to MetaMixin's
1326 buildRepr method to build representations of the meta information.
1327
1328 You can override startKey, endKey, and enterValue. If you are
1329 not doing anything fancy, you can get by by just overriding enterValue
1330 and inspecting curAtoms[-1] (which contains the last meta key).
1331
1332 You will want to override getResult.
1333 """
1336
1339
1342
1345
1348
1351 """is a MetaBuilder that recovers a tuple sequence of the meta items
1352 in text representation.
1353 """
1357
1360
1363
1366 """returns a factory for ModelBasedBuilder built from a stan-like "tag".
1367
1368 Do *not* pass in instanciated tags -- they will just keep accumulating
1369 children on every model run.
1370 """
1371 if isinstance(tag, stanxml.Element):
1372 raise utils.ReportableError("Do not use instanciated stanxml element"
1373 " in stanFactories. Instead, return them from a zero-argument"
1374 " function.")
1375
1376 def factory(args, localattrs=None):
1377 if localattrs:
1378 localattrs.update(kwargs)
1379 attrs = localattrs
1380 else:
1381 attrs = kwargs
1382 if isinstance(tag, type):
1383 el = tag
1384 else: # assume it's a function if it's not an element type.
1385 el = tag()
1386 return el(**attrs)[args]
1387 return factory
1388
1389
1390 # Within this abomination of code, the following is particularly nasty.
1391 # It *must* go.
1392
1393 -class ModelBasedBuilder(object):
1394 """is a meta builder that can create stan-like structures from meta
1395 information
1396
1397 It is constructed with with a tuple-tree of keys and DOM constructors;
1398 these must work like stan elements, which is, e.g., also true for our
1399 registrymodel elements.
1400
1401 Each node in the tree can be one of:
1402
1403 - a meta key and a callable,
1404 - this, and a sequence of child nodes
1405 - this, and a dictionary mapping argument names for the callable
1406 to meta keys of the node; the arguments extracted in this way
1407 are passed in a single dictionary localattrs.
1408
1409 The callable can also be None, which causes the corresponding items
1410 to be inlined into the parent (this is for flattening nested meta
1411 structures).
1412
1413 The meta key can also be None, which causes the factory to be called
1414 exactly once (this is for nesting flat meta structures).
1415 """
1418
1421 if not metaItem:
1422 return []
1423 result = []
1424 for child in metaItem.children:
1425 content = []
1426 c = child.getContent(self.format, macroPackage=macroPackage)
1427 if c:
1428 content.append(c)
1429 childContent = self._build(children, child, macroPackage)
1430 if childContent:
1431 content.append(childContent)
1432 if content:
1433 result.append(processContent(content, child))
1434 return result
1435
1436 - def _getItemsForConstructor(self, metaContainer, macroPackage,
1437 key, factory, children=(), attrs={}):
1438 if factory:
1439 def processContent(childContent, metaItem):
1440 moreAttrs = {}
1441 for argName, metaKey in attrs.iteritems():
1442 val = metaItem.getMeta(metaKey)
1443 if val:
1444 moreAttrs[argName] = val.getContent("text",
1445 macroPackage=macroPackage)
1446 return [factory(childContent, localattrs=moreAttrs)]
1447 else:
1448 def processContent(childContent, metaItem): #noflake: conditional def
1449 return childContent
1450
1451 if key is None:
1452 return [factory(self._build(children, metaContainer, macroPackage))]
1453 else:
1454 return self._buildNode(processContent,
1455 metaContainer.getMeta(key, raiseOnFail=False), children,
1456 macroPackage=macroPackage)
1457
1459 result = []
1460 for item in constructors:
1461 if isinstance(item, basestring):
1462 result.append(item)
1463 else:
1464 try:
1465 result.extend(self._getItemsForConstructor(metaContainer,
1466 macroPackage, *item))
1467 except utils.Error:
1468 raise
1469 except:
1470 raise utils.logOldExc(utils.ReportableError(
1471 "Invalid constructor func in %s, meta container active %s"%(
1472 repr(item), repr(metaContainer))))
1473 return result
1474
1476 if macroPackage is None:
1477 macroPackage = metaContainer
1478 return self._build(self.constructors, metaContainer, macroPackage)
1479
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 2 07:29:09 2019 | http://epydoc.sourceforge.net |