1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from pkg_resources
import resource_filename
25 import dateutil
.parser
26 from pyld
import jsonld
27 from jsonschema
import validate
, FormatChecker
, draft4_format_checker
28 from jsonschema
.compat
import str_types
30 from mediagoblin
.tools
.pluginapi
import hook_handle
34 ########################################################
35 ## Set up the MediaGoblin format checker for json-schema
36 ########################################################
38 URL_REGEX
= re
.compile(
39 r
'^[a-z]+://([^/:]+|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.*)?$',
44 jsonschema uri validator
46 if not isinstance(instance
, str_types
):
49 return URL_REGEX
.match(instance
)
51 def is_datetime(instance
):
53 Is a date or datetime readable string.
55 if not isinstance(instance
, str_types
):
58 return dateutil
.parser
.parse(instance
)
61 class DefaultChecker(FormatChecker
):
63 Default MediaGoblin format checker... extended to include a few extra things
65 checkers
= copy
.deepcopy(draft4_format_checker
.checkers
)
68 DefaultChecker
.checkers
[u
"uri"] = (is_uri
, ())
69 DefaultChecker
.checkers
[u
"date-time"] = (is_datetime
, (ValueError, TypeError))
70 DEFAULT_CHECKER
= DefaultChecker()
72 # Crappy default schema, checks for things we deem important
75 "$schema": "http://json-schema.org/schema#",
84 "format": "date-time",
88 "format": "date-time",
95 def load_resource(package
, resource_path
):
97 Load a resource, return it as a string.
100 - package: package or module name. Eg "mediagoblin.media_types.audio"
101 - resource_path: path to get to this resource, a list of
102 directories and finally a filename. Will be joined with
105 filename
= resource_filename(package
, os
.path
.sep
.join(resource_path
))
106 return open(filename
, encoding
="utf-8").read()
108 def load_resource_json(package
, resource_path
):
110 Load a resource json file, return a dictionary.
113 - package: package or module name. Eg "mediagoblin.media_types.audio"
114 - resource_path: path to get to this resource, a list of
115 directories and finally a filename. Will be joined with
118 return json
.loads(load_resource(package
, resource_path
))
121 ##################################
122 ## Load the MediaGoblin core files
123 ##################################
127 "http://www.w3.org/2013/json-ld-context/rdfa11": load_resource(
128 "mediagoblin", ["static", "metadata", "rdfa11.jsonld"])}
133 def load_context(url
):
135 A self-aware document loader. For those contexts MediaGoblin
136 stores internally, load them from disk.
138 if url
in _CONTEXT_CACHE
:
139 return _CONTEXT_CACHE
[url
]
141 # See if it's one of our basic ones
142 document
= BUILTIN_CONTEXTS
.get(url
, None)
144 # No? See if we have an internal schema for this
146 document
= hook_handle(("context_url_data", url
))
148 # Okay, if we've gotten a document by now... let's package it up
149 if document
is not None:
150 document
= {'contextUrl': None,
152 'document': document
}
154 # Otherwise, use jsonld.load_document
156 document
= jsonld
.load_document(url
)
159 _CONTEXT_CACHE
[url
] = document
163 DEFAULT_CONTEXT
= "http://www.w3.org/2013/json-ld-context/rdfa11"
165 def compact_json(metadata
, context
=DEFAULT_CONTEXT
):
167 Compact json with supplied context.
169 Note: Free floating" nodes are removed (eg a key just named
170 "bazzzzzz" which isn't specified in the context... something like
171 bazzzzzz:blerp will stay though. This is jsonld.compact behavior.
173 compacted
= jsonld
.compact(
176 "documentLoader": load_context
,
177 # This allows for things like "license" and etc to be preserved
178 "expandContext": context
,
179 "keepFreeFloatingNodes": False})
184 def compact_and_validate(metadata
, context
=DEFAULT_CONTEXT
,
185 schema
=DEFAULT_SCHEMA
):
187 compact json with supplied context, check against schema for errors
189 raises an exception (jsonschema.exceptions.ValidationError) if
192 Note: Free floating" nodes are removed (eg a key just named
193 "bazzzzzz" which isn't specified in the context... something like
194 bazzzzzz:blerp will stay though. This is jsonld.compact behavior.
196 You may wish to do this validation yourself... this is just for convenience.
198 compacted
= compact_json(metadata
, context
)
199 validate(metadata
, schema
, format_checker
=DEFAULT_CHECKER
)
204 def expand_json(metadata
, context
=DEFAULT_CONTEXT
):
206 Expand json, but be sure to use our documentLoader.
208 By default this expands with DEFAULT_CONTEXT, but if you do not need this,
209 you can safely set this to None.
211 # @@: Is the above a good idea? Maybe it should be set to None by
215 "documentLoader": load_context
}
216 if context
is not None:
217 options
["expandContext"] = context
218 return jsonld
.expand(metadata
, options
=options
)
221 def rdfa_to_readable(rdfa_predicate
):
222 readable
= rdfa_predicate
.split(u
":")[1].capitalize()