diff --git a/jsonld.php b/jsonld.php index 321a62c..e8481c8 100644 --- a/jsonld.php +++ b/jsonld.php @@ -1,7 +1,7 @@ 0) { - if($input[0] === '.' || ($input[0] === '' && count($input) > 1)) { - array_shift($input); - continue; - } - if($input[0] === '..') { - array_shift($input); - if($has_authority || - (count($output) > 0 && $output[count($output) - 1] !== '..')) { - array_pop($output); - } - // leading relative URL '..' - else { - $output[] = '..'; - } - continue; - } - $output[] = array_shift($input); - } - - return $rval . implode('/', $output); -} +/** + * Removes dot segments from a URL path. + * + * @param string $path the path to remove dot segments from. + * @param bool $has_authority true if the URL has an authority, false if not. + */ +function jsonld_remove_dot_segments($path, $has_authority) { + $rval = ''; + + if(strpos($path, '/') === 0) { + $rval = '/'; + } + + // RFC 3986 5.2.4 (reworked) + $input = explode('/', $path); + $output = array(); + while(count($input) > 0) { + if($input[0] === '.' || ($input[0] === '' && count($input) > 1)) { + array_shift($input); + continue; + } + if($input[0] === '..') { + array_shift($input); + if($has_authority || + (count($output) > 0 && $output[count($output) - 1] !== '..')) { + array_pop($output); + } + // leading relative URL '..' + else { + $output[] = '..'; + } + continue; + } + $output[] = array_shift($input); + } + + return $rval . implode('/', $output); +} /** * Prepends a base IRI to the given relative IRI. @@ -548,13 +548,13 @@ function jsonld_remove_base($base, $iri) { // prepend remaining segments $rval .= implode('/', $iri_segments); - // add query and hash - if(isset($rel['query'])) { - $rval .= "?{$rel['query']}"; - } - if(isset($rel['fragment'])) { - $rval .= "#{$rel['fragment']}"; - } + // add query and hash + if(isset($rel['query'])) { + $rval .= "?{$rel['query']}"; + } + if(isset($rel['fragment'])) { + $rval .= "#{$rel['fragment']}"; + } if($rval === '') { $rval = './'; @@ -620,8 +620,6 @@ class JsonLdProcessor { // set default options isset($options['base']) or $options['base'] = ''; isset($options['strict']) or $options['strict'] = true; - isset($options['renameBlankNodes']) or $options['renameBlankNodes'] = - true; isset($options['compactArrays']) or $options['compactArrays'] = true; isset($options['graph']) or $options['graph'] = false; isset($options['skipExpansion']) or $options['skipExpansion'] = false; @@ -735,8 +733,6 @@ class JsonLdProcessor { * @param mixed $input the JSON-LD object to expand. * @param assoc $options the options to use: * [base] the base IRI to use. - * [renameBlankNodes] true to rename blank nodes, false not to, - * defaults to true. * [keepFreeFloatingNodes] true to keep free-floating nodes, * false not to, defaults to false. * [loadContext(url)] the context loader. @@ -746,8 +742,6 @@ class JsonLdProcessor { public function expand($input, $options) { // set default options isset($options['base']) or $options['base'] = ''; - isset($options['renameBlankNodes']) or $options['renameBlankNodes'] = - true; isset($options['keepFreeFloatingNodes']) or $options['keepFreeFloatingNodes'] = false; isset($options['loadContext']) or $options['loadContext'] = @@ -1062,8 +1056,6 @@ class JsonLdProcessor { * @param stdClass $active_ctx the current active context. * @param mixed $local_ctx the local context to process. * @param assoc $options the options to use: - * [renameBlankNodes] true to rename blank nodes, false not to, - * defaults to true. * [loadContext(url)] the context loader. * * @return stdClass the new active context. @@ -1071,8 +1063,6 @@ class JsonLdProcessor { public function processContext($active_ctx, $local_ctx, $options) { // set default options isset($options['base']) or $options['base'] = ''; - isset($options['renameBlankNodes']) or $options['renameBlankNodes'] = - true; isset($options['loadContext']) or $options['loadContext'] = 'jsonld_get_url'; @@ -1698,7 +1688,7 @@ class JsonLdProcessor { return $this->_compactValue($active_ctx, $active_property, $element); } - // FIXME: avoid misuse of active property as an expanded property? + // FIXME: avoid misuse of active property as an expanded property? $inside_reverse = ($active_property === '@reverse'); // process element keys in order @@ -1735,33 +1725,33 @@ class JsonLdProcessor { continue; } - // handle @reverse - if($expanded_property === '@reverse') { - // recursively compact expanded value - $compacted_value = $this->_compact( - $active_ctx, '@reverse', $expanded_value, $options); - - // handle double-reversed properties - foreach($compacted_value as $compacted_property => $value) { + // handle @reverse + if($expanded_property === '@reverse') { + // recursively compact expanded value + $compacted_value = $this->_compact( + $active_ctx, '@reverse', $expanded_value, $options); + + // handle double-reversed properties + foreach($compacted_value as $compacted_property => $value) { if(property_exists($active_ctx->mappings, $compacted_property) && - $active_ctx->mappings->{$compacted_property} && + $active_ctx->mappings->{$compacted_property} && $active_ctx->mappings->{$compacted_property}->reverse) { if(!property_exists($rval, $compacted_property) && !$options['compactArrays']) { $rval->{$compacted_property} = array(); - } - self::addValue($rval, $compacted_property, $value); - unset($compacted_value->{$compacted_property}); - } - } - - if(count(array_keys((array)$compacted_value)) > 0) { - // use keyword alias and add value - $alias = $this->_compactIri($active_ctx, $expanded_property); - self::addValue($rval, $alias, $compacted_value); - } - - continue; + } + self::addValue($rval, $compacted_property, $value); + unset($compacted_value->{$compacted_property}); + } + } + + if(count(array_keys((array)$compacted_value)) > 0) { + // use keyword alias and add value + $alias = $this->_compactIri($active_ctx, $expanded_property); + self::addValue($rval, $alias, $compacted_value); + } + + continue; } // handle @index property @@ -1971,11 +1961,11 @@ class JsonLdProcessor { continue; } - if(self::_isKeyword($expanded_property) && + if(self::_isKeyword($expanded_property) && $expanded_active_property === '@reverse') { - throw new JsonLdException( - 'Invalid JSON-LD syntax; a keyword cannot be used as a @reverse ' . - 'property.', + throw new JsonLdException( + 'Invalid JSON-LD syntax; a keyword cannot be used as a @reverse ' . + 'property.', 'jsonld.SyntaxError', array('value' => $value)); } @@ -2027,58 +2017,58 @@ class JsonLdProcessor { } } - // @reverse must be an object - if($expanded_property === '@reverse') { - if(!is_object($value)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; "@reverse" value must be an object.', - 'jsonld.SyntaxError', array('value' => $value)); - } - - $expanded_value = $this->_expand( - $active_ctx, '@reverse', $value, $options, $inside_list); - - // properties double-reversed - if(property_exists($expanded_value, '@reverse')) { - foreach($expanded_value->{'@reverse'} as $rproperty => $rvalue) { + // @reverse must be an object + if($expanded_property === '@reverse') { + if(!is_object($value)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; "@reverse" value must be an object.', + 'jsonld.SyntaxError', array('value' => $value)); + } + + $expanded_value = $this->_expand( + $active_ctx, '@reverse', $value, $options, $inside_list); + + // properties double-reversed + if(property_exists($expanded_value, '@reverse')) { + foreach($expanded_value->{'@reverse'} as $rproperty => $rvalue) { self::addValue( - $rval, $rproperty, $rvalue, array('propertyIsArray' => true)); - } - } - - // FIXME: can this be merged with code below to simplify? + $rval, $rproperty, $rvalue, array('propertyIsArray' => true)); + } + } + + // FIXME: can this be merged with code below to simplify? // merge in all reversed properties if(property_exists($rval, '@reverse')) { $reverse_map = $rval->{'@reverse'}; } else { $reverse_map = null; - } - foreach($expanded_value as $property => $items) { - if($property === '@reverse') { - continue; - } - if($reverse_map === null) { - $reverse_map = $rval->{'@reverse'} = new stdClass(); - } + } + foreach($expanded_value as $property => $items) { + if($property === '@reverse') { + continue; + } + if($reverse_map === null) { + $reverse_map = $rval->{'@reverse'} = new stdClass(); + } self::addValue( $reverse_map, $property, array(), array('propertyIsArray' => true)); - foreach($items as $item) { - if(self::_isValue($item) || self::_isList($item)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; "@reverse" value must not be a ' + - '@value or an @list.', + foreach($items as $item) { + if(self::_isValue($item) || self::_isList($item)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; "@reverse" value must not be a ' + + '@value or an @list.', 'jsonld.SyntaxError', - array('value' => $expanded_value)); - } + array('value' => $expanded_value)); + } self::addValue( $reverse_map, $property, $item, - array('propertyIsArray' => true)); - } - } - - continue; + array('propertyIsArray' => true)); + } + } + + continue; } $container = self::getContextValue($active_ctx, $key, '@container'); @@ -2146,25 +2136,25 @@ class JsonLdProcessor { '@list' => self::arrayify($expanded_value)); } - // FIXME: can this be merged with code above to simplify? - // merge in reverse properties + // FIXME: can this be merged with code above to simplify? + // merge in reverse properties if(property_exists($active_ctx->mappings, $key) && $active_ctx->mappings->{$key} && - $active_ctx->mappings->{$key}->reverse) { + $active_ctx->mappings->{$key}->reverse) { $reverse_map = $rval->{'@reverse'} = new stdClass(); $expanded_value = self::arrayify($expanded_value); - foreach($expanded_value as $item) { - if(self::_isValue($item) || self::_isList($item)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; "@reverse" value must not be a ' + - '@value or an @list.', - 'jsonld.SyntaxError', array('value' => $expanded_value)); - } - self::addValue( + foreach($expanded_value as $item) { + if(self::_isValue($item) || self::_isList($item)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; "@reverse" value must not be a ' + + '@value or an @list.', + 'jsonld.SyntaxError', array('value' => $expanded_value)); + } + self::addValue( $reverse_map, $expanded_property, $item, - array('propertyIsArray' => true)); - } - continue; + array('propertyIsArray' => true)); + } + continue; } // add value for property @@ -2695,7 +2685,6 @@ class JsonLdProcessor { if(property_exists($jsonld_cache, 'activeCtx')) { $rval = $jsonld_cache->activeCtx->get($active_ctx, $local_ctx); if($rval) { - $rval->namer = $active_ctx->namer; return $rval; } } @@ -2715,7 +2704,6 @@ class JsonLdProcessor { // reset to initial context if($ctx === null) { $rval = $this->_getInitialContext($options); - $rval->namer = $active_ctx->namer; continue; } @@ -2734,26 +2722,26 @@ class JsonLdProcessor { // define context mappings for keys in local context $defined = new stdClass(); - // handle @base - if(property_exists($ctx, '@base')) { - $base = $ctx->{'@base'}; + // handle @base + if(property_exists($ctx, '@base')) { + $base = $ctx->{'@base'}; if($base === null) { - $base = $options['base']; - } - else if(!is_string($base)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; the value of "@base" in a ' . - '@context must be a string or null.', - 'jsonld.SyntaxError', array('context' => $ctx)); - } - else if($base !== '' && !self::_isAbsoluteIri($base)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; the value of "@base" in a ' . - '@context must be an absolute IRI or the empty string.', - 'jsonld.SyntaxError', array('context' => $ctx)); - } - $rval->{'@base'} = jsonld_parse_url($base); - $defined->{'@base'} = true; + $base = $options['base']; + } + else if(!is_string($base)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; the value of "@base" in a ' . + '@context must be a string or null.', + 'jsonld.SyntaxError', array('context' => $ctx)); + } + else if($base !== '' && !self::_isAbsoluteIri($base)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; the value of "@base" in a ' . + '@context must be an absolute IRI or the empty string.', + 'jsonld.SyntaxError', array('context' => $ctx)); + } + $rval->{'@base'} = jsonld_parse_url($base); + $defined->{'@base'} = true; } // handle @vocab @@ -2933,10 +2921,6 @@ class JsonLdProcessor { // other type if($type !== null) { - // rename blank node if requested - if($active_ctx->namer !== null && strpos($type, '_:') === 0) { - $type = $active_ctx->namer->getName($type); - } $rval->{'@type'} = $type; } // check for language tagging for strings @@ -3253,20 +3237,20 @@ class JsonLdProcessor { continue; } - // handle reverse properties - if($property === '@reverse') { - $referenced_node = (object)array('@id' => $name); - $reverse_map = $input->{'@reverse'}; + // handle reverse properties + if($property === '@reverse') { + $referenced_node = (object)array('@id' => $name); + $reverse_map = $input->{'@reverse'}; foreach($reverse_map as $reverse_property => $items) { foreach($items as $item) { - self::addValue( + self::addValue( $item, $reverse_property, $referenced_node, - array('propertyIsArray' => true, 'allowDuplicate' => false)); - $this->_createNodeMap($item, $graphs, $graph, $namer); + array('propertyIsArray' => true, 'allowDuplicate' => false)); + $this->_createNodeMap($item, $graphs, $graph, $namer); } - } - continue; - } + } + continue; + } // recurse into graph if($property === '@graph') { @@ -3282,11 +3266,11 @@ class JsonLdProcessor { // copy non-@type keywords if($property !== '@type' && self::_isKeyword($property)) { - if($property === '@index' && property_exists($subject, '@index')) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; conflicting @index property detected.', - 'jsonld.SyntaxError', array('subject' => $subject)); - } + if($property === '@index' && property_exists($subject, '@index')) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; conflicting @index property detected.', + 'jsonld.SyntaxError', array('subject' => $subject)); + } $subject->{$property} = $input->{$property}; continue; } @@ -4120,10 +4104,10 @@ class JsonLdProcessor { $type_or_language = '@language'; $type_or_language_value = '@null'; - if($reverse) { - $type_or_language = '@type'; - $type_or_language_value = '@reverse'; - $containers[] = '@set'; + if($reverse) { + $type_or_language = '@type'; + $type_or_language_value = '@reverse'; + $containers[] = '@set'; } // choose the most specific term that works for all elements in @list else if(self::_isList($value)) { @@ -4205,7 +4189,7 @@ class JsonLdProcessor { } // do term selection - $containers[] = '@none'; + $containers[] = '@none'; $term = $this->_selectTerm( $active_ctx, $iri, $value, $containers, $type_or_language, $type_or_language_value); @@ -4451,28 +4435,28 @@ class JsonLdProcessor { $mapping = new stdClass(); $mapping->reverse = false; - if(property_exists($value, '@reverse')) { + if(property_exists($value, '@reverse')) { if(property_exists($value, '@id') || property_exists($value, '@type') || - property_exists($value, '@language')) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; a @reverse term definition must not ' + - 'contain @id, @type, or @language.', - 'jsonld.SyntaxError', array('context' => $local_ctx)); - } - $reverse = $value->{'@reverse'}; - if(!is_string($reverse)) { - throw new JsonLdException( - 'Invalid JSON-LD syntax; a @context @reverse value must be a string.', - 'jsonld.SyntaxError', array('context' => $local_ctx)); - } - - // expand and add @id mapping, set @type to @id - $mapping->{'@id'} = $this->_expandIri( + property_exists($value, '@language')) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; a @reverse term definition must not ' + + 'contain @id, @type, or @language.', + 'jsonld.SyntaxError', array('context' => $local_ctx)); + } + $reverse = $value->{'@reverse'}; + if(!is_string($reverse)) { + throw new JsonLdException( + 'Invalid JSON-LD syntax; a @context @reverse value must be a string.', + 'jsonld.SyntaxError', array('context' => $local_ctx)); + } + + // expand and add @id mapping, set @type to @id + $mapping->{'@id'} = $this->_expandIri( $active_ctx, $reverse, array('vocab' => true, 'base' => true), - $local_ctx, $defined); - $mapping->{'@type'} = '@id'; - $mapping->reverse = true; + $local_ctx, $defined); + $mapping->{'@type'} = '@id'; + $mapping->reverse = true; } else if(property_exists($value, '@id')) { $id = $value->{'@id'}; @@ -4551,11 +4535,11 @@ class JsonLdProcessor { 'one of the following: @list, @set, @index, or @language.', 'jsonld.SyntaxError', array('context' => $local_ctx)); } - if($mapping->reverse && $container !== '@index') { - throw new JsonLdException( - 'Invalid JSON-LD syntax; @context @container value for a @reverse ' + - 'type definition must be @index.', - 'jsonld.SyntaxError', array('context' => $local_ctx)); + if($mapping->reverse && $container !== '@index') { + throw new JsonLdException( + 'Invalid JSON-LD syntax; @context @container value for a @reverse ' + + 'type definition must be @index.', + 'jsonld.SyntaxError', array('context' => $local_ctx)); } // add @container to mapping @@ -4614,8 +4598,6 @@ class JsonLdProcessor { $this->_createTermDefinition($active_ctx, $local_ctx, $value, $defined); } - $rval = null; - if(isset($relative_to['vocab']) && $relative_to['vocab']) { if(property_exists($active_ctx->mappings, $value)) { $mapping = $active_ctx->mappings->{$value}; @@ -4626,55 +4608,44 @@ class JsonLdProcessor { } // value is a term - $rval = $mapping->{'@id'}; + return $mapping->{'@id'}; } } - if($rval === null) { - // split value into prefix:suffix - $colon = strpos($value, ':'); - if($colon !== false) { - $prefix = substr($value, 0, $colon); - $suffix = substr($value, $colon + 1); + // split value into prefix:suffix + $colon = strpos($value, ':'); + if($colon !== false) { + $prefix = substr($value, 0, $colon); + $suffix = substr($value, $colon + 1); - // do not expand blank nodes (prefix of '_') or already-absolute - // IRIs (suffix of '//') - if($prefix !== '_' && strpos($suffix, '//') !== 0) { - // prefix dependency not defined, define it - if($local_ctx !== null && property_exists($local_ctx, $prefix)) { - $this->_createTermDefinition( - $active_ctx, $local_ctx, $prefix, $defined); - } + // do not expand blank nodes (prefix of '_') or already-absolute + // IRIs (suffix of '//') + if($prefix !== '_' && strpos($suffix, '//') !== 0) { + // prefix dependency not defined, define it + if($local_ctx !== null && property_exists($local_ctx, $prefix)) { + $this->_createTermDefinition( + $active_ctx, $local_ctx, $prefix, $defined); + } - // use mapping if prefix is defined - if(property_exists($active_ctx->mappings, $prefix)) { - $mapping = $active_ctx->mappings->{$prefix}; - if($mapping) { - $rval = $mapping->{'@id'} . $suffix; - } + // use mapping if prefix is defined + if(property_exists($active_ctx->mappings, $prefix)) { + $mapping = $active_ctx->mappings->{$prefix}; + if($mapping) { + return $mapping->{'@id'} . $suffix; } } } } - if($rval === null) { - $rval = $value; + // already absolute IRI + if(self::_isAbsoluteIri($value)) { + return $value; } - // keywords need no expanding (aliasing already handled by now) - if(self::_isKeyword($rval)) { - return $rval; - } + $rval = $value; - if(self::_isAbsoluteIri($rval)) { - // rename blank node if requested - if(!$local_ctx && strpos($rval, '_:') === 0 && - $active_ctx->namer !== null) { - $rval = $active_ctx->namer->getName($rval); - } - } // prepend vocab - else if(isset($relative_to['vocab']) && $relative_to['vocab'] && + if(isset($relative_to['vocab']) && $relative_to['vocab'] && property_exists($active_ctx, '@vocab')) { $rval = $active_ctx->{'@vocab'} . $rval; } @@ -4869,14 +4840,9 @@ class JsonLdProcessor { * @return stdClass the initial context. */ protected function _getInitialContext($options) { - $namer = null; - if(isset($options['renameBlankNodes']) && $options['renameBlankNodes']) { - $namer = new UniqueNamer('_:b'); - } return (object)array( '@base' => jsonld_parse_url($options['base']), 'mappings' => new stdClass(), - 'namer' => $namer, 'inverse' => null); } @@ -4934,37 +4900,37 @@ class JsonLdProcessor { } $entry = $container_map->{$container}; - // term is preferred for values using @reverse - if($mapping->reverse) { + // term is preferred for values using @reverse + if($mapping->reverse) { $this->_addPreferredTerm( - $mapping, $term, $entry->{'@type'}, '@reverse'); - } - // term is preferred for values using specific type - else if(property_exists($mapping, '@type')) { + $mapping, $term, $entry->{'@type'}, '@reverse'); + } + // term is preferred for values using specific type + else if(property_exists($mapping, '@type')) { $this->_addPreferredTerm( - $mapping, $term, $entry->{'@type'}, $mapping->{'@type'}); - } - // term is preferred for values using specific language - else if(property_exists($mapping, '@language')) { + $mapping, $term, $entry->{'@type'}, $mapping->{'@type'}); + } + // term is preferred for values using specific language + else if(property_exists($mapping, '@language')) { $language = $mapping->{'@language'}; - if($language === null) { - $language = '@null'; - } + if($language === null) { + $language = '@null'; + } $this->_addPreferredTerm( - $mapping, $term, $entry->{'@language'}, $language); - } - // term is preferred for values w/default language or no type and - // no language - else { - // add an entry for the default language + $mapping, $term, $entry->{'@language'}, $language); + } + // term is preferred for values w/default language or no type and + // no language + else { + // add an entry for the default language $this->_addPreferredTerm( - $mapping, $term, $entry->{'@language'}, $default_language); - - // add entries for no type and no language + $mapping, $term, $entry->{'@language'}, $default_language); + + // add entries for no type and no language $this->_addPreferredTerm( - $mapping, $term, $entry->{'@type'}, '@none'); + $mapping, $term, $entry->{'@type'}, '@none'); $this->_addPreferredTerm( - $mapping, $term, $entry->{'@language'}, '@none'); + $mapping, $term, $entry->{'@language'}, '@none'); } } } @@ -4996,7 +4962,6 @@ class JsonLdProcessor { $child = new stdClass(); $child->{'@base'} = $active_ctx->{'@base'}; $child->mappings = self::copy($active_ctx->mappings); - $child->namer = $active_ctx->namer; $child->inverse = null; if(property_exists($active_ctx, '@language')) { $child->{'@language'} = $active_ctx->{'@language'}; @@ -5020,9 +4985,6 @@ class JsonLdProcessor { $rval = new stdClass(); $rval->{'@base'} = $active_ctx->{'@base'}; $rval->mappings = $active_ctx->mappings; - if($active_ctx->namer !== null) { - $rval->namer = new UniqueNamer('_:b'); - } $rval->inverse = $active_ctx->inverse; if(property_exists($active_ctx, '@language')) { $rval->{'@language'} = $active_ctx->{'@language'};