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 """ 7076 80 8588 """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)117120 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 parsed132135 """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())181184 """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?263193 self.container, self.nextParser = container, nextParser 194 self.attrs = {} 195 self.children = [] # containing key, metaValue pairs 196 self.next = None197199 # 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))203205 # see addMeta comment on why we need to do magic here. 206 self.children.append(("!"+key, content, kwargs))207209 if name=="meta": 210 return MetaParser(self, self) 211 else: 212 self.next = name 213 return self214216 if self.next is None: 217 self.attrs[str(name)] = value 218 else: 219 self.attrs[str(self.next)] = value 220 return self221223 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)251253 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 self266 """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" 273327275 attrdef.AttributeDef.__init__(self, "meta_", 276 attrdef.Computed, description) 277 self.xmlName_ = "meta"278 279 @property 282284 self.meta_ = value285287 """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 newMeta303 306308 return ("**meta** -- a piece of meta information, giving at least a name" 309 " and some content. See Metadata_ on what is permitted here.")310312 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 ev330 """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() 355462 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 470357 """is a constructor for standalone use. You do *not* want to 358 call this when mixing into a Structure. 359 """ 360 self.meta_ = {}361363 try: 364 self.__metaParent # assert existence 365 return True 366 except AttributeError: 367 return False368 371 375 381383 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)398399 - 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 default416418 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 mv425427 """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 val446448 """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)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 481483 if atom in self.meta_: 484 return self.meta_[atom] 485 raise NoMetaKey("No meta child %s"%atom, carrier=self)486488 return self.meta_.keys()489 490 # XXX TRANS remove; this is too common a name 491 keys = getMetaKeys 492 495497 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)502504 """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 self522524 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]]533535 """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))543545 """replaces any previous meta content of key (on this container) 546 with value. 547 """ 548 self.delMeta(key) 549 self.addMeta(key, value, **moreAttrs)550552 for key, item in self.meta_.iteritems(): 553 builder.startKey(key) 554 item.traverse(builder) 555 builder.endKey(key)556558 """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()565567 """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.copied577 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 """633600 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 raise612614 computedKeys = [] 615 for name in dir(self): 616 if name.startswith("_meta_"): 617 computedKeys.append(name[6:]) 618 return MetaMixin.getMetaKeys(self)+computedKeys619636 """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 @classmethod767660 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)673675 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 695697 self.children[-1].addContent(item)698700 # 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)709711 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 self718720 # 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)728730 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)735738 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)742744 newChildren = [] 745 for c in self.children: 746 c._delMeta(atoms) 747 if not c.isEmpty(): 748 newChildren.append(c) 749 self.children = newChildren750 756758 """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 newOb764770 # 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-dash775778 """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) 798898800 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 812814 return self.getContent()815817 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))821823 return content824826 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 content840842 if hasattr(macroPackage, "expand") and "\\" in self.content: 843 return macroPackage.expand(self.content) 844 return self.content845847 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)856858 return unicode(self).encode(enc)859 863865 # 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))890892 """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 newOb899 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 """909912 """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 949955958 """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 """979982 """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 100210271005 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))10111013 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)))10181030 """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 """10651049 MetaValue.__init__(self, content, format) 1050 self.initArgs = content, format, tag 1051 self.tag = tag10521054 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))10591068 """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 """10821085 """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 """10951098 """A MetaValue that may contain bibcodes, which are rendered as links 1099 into ADS. 1100 """1113 # Yikes. We should really quote such raw HTML properly...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))11091111 return misctricks.BIBCODE_PATTERN.sub(self._makeADSLink, unicode(content) 1112 ).replace("&", "&")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 """11411144 """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 True12271230 #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)12371240 """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)12461249 """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))12651268 """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)12891290 # 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)13201323 """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 134513481351 """is a MetaBuilder that recovers a tuple sequence of the meta items 1352 in text representation. 1353 """ 1357 136013631366 """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 13881389 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 """ 14181450 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) 14571421 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 result14351436 - 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 childContent1459 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 result14741476 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 |