lib/json/ld/compact.rb in json-ld-0.3.2 vs lib/json/ld/compact.rb in json-ld-0.9.0
- old
+ new
@@ -19,11 +19,11 @@
# 1) If value is an array, process each item in value recursively using
# this algorithm, passing copies of the active context and the
# active property.
debug("compact") {"Array #{element.inspect}"}
result = depth {element.map {|v| compact(v, property)}}
-
+
# If element has a single member and the active property has no
# @container mapping to @list or @set, the compacted value is that
# member; otherwise the compacted value is element
if result.length == 1 && @options[:compactArrays]
debug("=> extract single element: #{result.first.inspect}")
@@ -33,40 +33,96 @@
result
end
when Hash
# 2) Otherwise, if element is an object:
result = {}
-
+
if k = %w(@list @set @value).detect {|container| element.has_key?(container)}
debug("compact") {"#{k}: container(#{property}) = #{context.container(property)}"}
end
k ||= '@id' if element.keys == ['@id']
-
+
case k
when '@value', '@id'
# If element has an @value property or element is a node reference, return the result of performing Value Compaction on element using active property.
v = context.compact_value(property, element, :depth => @depth)
- debug("compact") {"value optimization, return as #{v.inspect}"}
+ debug("compact") {"value optimization for #{property}, return as #{v.inspect}"}
return v
when '@list'
# Otherwise, if the active property has a @container mapping to @list and element has a corresponding @list property, recursively compact that property's value passing a copy of the active context and the active property ensuring that the result is an array with all null values removed.
-
+
# If there already exists a value for active property in element and the full IRI of property is also coerced to @list, return an error.
# FIXME: check for full-iri list coercion
# Otherwise store the resulting array as value of active property if empty or property otherwise.
- compacted_key = context.compact_iri(k, :position => :predicate, :depth => @depth)
+ compacted_key = context.compact_iri('@list', :position => :predicate, :depth => @depth)
v = depth { compact(element[k], property) }
-
+
# Return either the result as an array, as an object with a key of @list (or appropriate alias from active context
v = [v].compact unless v.is_a?(Array)
- v = {compacted_key => v} unless context.container(property) == k
+ unless context.container(property) == '@list'
+ v = {compacted_key => v}
+ if element['@annotation']
+ compacted_key = context.compact_iri('@annotation', :position => :predicate, :depth => @depth)
+ v[compacted_key] = element['@annotation']
+ end
+ end
debug("compact") {"@list result, return as #{v.inspect}"}
return v
end
+ # Check for property generators before continuing with other elements
+ # For each term pg in the active context which is a property generator
+ # Select property generator terms by shortest term
+ context.mappings.keys.sort.each do |term|
+ next unless context.mapping(term).is_a?(Array)
+ # Using the first expanded IRI p associated with the property generator
+ expanded_iris = context.mapping(term).map(&:to_s)
+ p = expanded_iris.first.to_s
+
+ # Skip to the next property generator term unless p is a property of element
+ next unless element.has_key?(p)
+
+ debug("compact") {"check pg #{term}: #{expanded_iris}"}
+
+ # For each node n which is a value of p in element
+ node_values = []
+ element[p].dup.each do |n|
+ # For each expanded IRI pi associated with the property generator other than p
+ next unless expanded_iris[1..-1].all? do |pi|
+ debug("compact") {"check #{pi} for (#{n.inspect})"}
+ element.has_key?(pi) && element[pi].any? do |ni|
+ nodesEquivalent?(n, ni)
+ end
+ end
+
+ # Remove n as a value of all p and pi in element
+ debug("compact") {"removed matched value #{n.inspect} from #{expanded_iris.inspect}"}
+ expanded_iris.each do |pi|
+ # FIXME: This removes all values equivalent to n, not just the first
+ element[pi] = element[pi].reject {|ni| nodesEquivalent?(n, ni)}
+ end
+
+ # Add the result of performing the compaction algorithm on n to pg to output
+ node_values << n
+ end
+
+ # If there are node_values, or all the values from expanded_iris are empty, add node_values to result, and remove the expanded_iris as keys from element
+ if node_values.length > 0 || expanded_iris.all? {|pi| element.has_key?(pi) && element[pi].empty?}
+ debug("compact") {"compact extracted pg values"}
+ result[term] = depth { compact(node_values, term)}
+ result[term] = [result[term]] if !result[term].is_a?(Array) && context.container(term) == '@set'
+
+ debug("compact") {"remove empty pg keys from element"}
+ expanded_iris.each do |pi|
+ debug(" =>") {"#{pi}? #{element.fetch(pi, []).empty?}"}
+ element.delete(pi) if element.fetch(pi, []).empty?
+ end
+ end
+ end
+
# Otherwise, for each property and value in element:
element.each do |key, value|
debug("compact") {"#{key}: #{value.inspect}"}
if %(@id @type).include?(key)
@@ -78,56 +134,102 @@
# If value is a string, the compacted value is the result of performing IRI Compaction on value.
debug {" => compacted string for #{key}"}
context.compact_iri(value, :position => position, :depth => @depth)
when Array
# Otherwise, value must be an array. Perform IRI Compaction on every entry of value. If value contains just one entry, value is set to that entry
- compacted_value = value.map {|v| context.compact_iri(v, :position => position, :depth => @depth)}
+ compacted_value = value.map {|v2| context.compact_iri(v2, :position => position, :depth => @depth)}
debug {" => compacted value(#{key}): #{compacted_value.inspect}"}
compacted_value = compacted_value.first if compacted_value.length == 1 && @options[:compactArrays]
compacted_value
end
+ elsif key == '@annotation' && context.container(property) == '@annotation'
+ # Skip the annotation key if annotations being applied
+ next
else
if value.empty?
# Make sure that an empty array is preserved
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
next if compacted_key.nil?
result[compacted_key] = value
+ next
end
# For each item in value:
- raise ProcessingError, "found #{value.inspect} for #{key} if #{element.inspect}" unless value.is_a?(Array)
+ value = [value] if key == '@annotation' && value.is_a?(String)
+ raise ProcessingError, "found #{value.inspect} for #{key} of #{element.inspect}" unless value.is_a?(Array)
value.each do |item|
compacted_key = context.compact_iri(key, :position => :predicate, :value => item, :depth => @depth)
+
+ # Result for this item, typically the output object itself
+ item_result = result
+ item_key = compacted_key
debug {" => compacted key: #{compacted_key.inspect} for #{item.inspect}"}
next if compacted_key.nil?
+ # Language maps and annotations
+ if field = %w(@language @annotation).detect {|kk| context.container(compacted_key) == kk}
+ item_result = result[compacted_key] ||= Hash.new
+ item_key = item[field]
+ end
+
compacted_item = depth {self.compact(item, compacted_key)}
debug {" => compacted value: #{compacted_value.inspect}"}
-
- case result[compacted_key]
+
+ case item_result[item_key]
when Array
- result[compacted_key] << compacted_item
+ item_result[item_key] << compacted_item
when nil
if !compacted_value.is_a?(Array) && context.container(compacted_key) == '@set'
compacted_item = [compacted_item].compact
debug {" => as @set: #{compacted_item.inspect}"}
end
- result[compacted_key] = compacted_item
+ item_result[item_key] = compacted_item
else
- result[compacted_key] = [result[compacted_key], compacted_item]
+ item_result[item_key] = [item_result[item_key], compacted_item]
end
end
end
end
-
+
# Re-order result keys
r = Hash.ordered
- result.keys.sort.each {|k| r[k] = result[k]}
+ result.keys.kw_sort.each {|kk| r[kk] = result[kk]}
r
else
# For other types, the compacted value is the element value
debug("compact") {element.class.to_s}
element
+ end
+ end
+
+ private
+
+ # Determines if two nodes are equivalent.
+ # * Value nodes are equivalent using a deep comparison
+ # * Arrays are equivalent if they have the same number of elements and each element is equivalent to the matching element
+ # * Node Defintions/References are equivalent IFF the have the same @id
+ def nodesEquivalent?(n1, n2)
+ depth do
+ r = if n1.is_a?(Array) && n2.is_a?(Array) && n1.length == n2.length
+ equiv = true
+ n1.each_with_index do |v1, i|
+ equiv &&= nodesEquivalent?(v1, n2[i]) if equiv
+ end
+ equiv
+ elsif value?(n1) && value?(n2)
+ n1 == n2
+ elsif list?(n1)
+ list?(n2) &&
+ n1.fetch('@annotation', true) == n2.fetch('@annotation', true) &&
+ nodesEquivalent?(n1['@list'], n2['@list'])
+ elsif (node?(n1) || node_reference?(n2))
+ (node?(n2) || node_reference?(n2)) && n1['@id'] == n2['@id']
+ else
+ false
+ end
+
+ debug("nodesEquivalent?(#{n1.inspect}, #{n2.inspect}): #{r.inspect}")
+ r
end
end
end
end