rdf_context 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. data/History.txt +27 -0
  2. data/README.rdoc +22 -9
  3. data/Rakefile +11 -6
  4. data/VERSION +1 -1
  5. data/bin/rdf_context +12 -4
  6. data/lib/rdf_context.rb +3 -1
  7. data/lib/rdf_context/aggregate_graph.rb +86 -0
  8. data/lib/rdf_context/array_hacks.rb +53 -0
  9. data/lib/rdf_context/conjunctive_graph.rb +1 -1
  10. data/lib/rdf_context/exceptions.rb +9 -7
  11. data/lib/rdf_context/graph.rb +174 -15
  12. data/lib/rdf_context/literal.rb +34 -2
  13. data/lib/rdf_context/n3_grammar.treetop +213 -129
  14. data/lib/rdf_context/n3parser.rb +281 -57
  15. data/lib/rdf_context/namespace.rb +23 -12
  16. data/lib/rdf_context/parser.rb +4 -3
  17. data/lib/rdf_context/quoted_graph.rb +38 -0
  18. data/lib/rdf_context/rdfaparser.rb +2 -1
  19. data/lib/rdf_context/rdfxmlparser.rb +4 -3
  20. data/lib/rdf_context/store/abstract_sql_store.rb +4 -4
  21. data/lib/rdf_context/store/abstract_store.rb +5 -1
  22. data/lib/rdf_context/store/sqlite3_store.rb +10 -8
  23. data/lib/rdf_context/string_hacks.rb +44 -21
  24. data/lib/rdf_context/term_utils.rb +73 -4
  25. data/lib/rdf_context/triple.rb +32 -47
  26. data/lib/rdf_context/uriref.rb +33 -26
  27. data/spec/aggregate_graph_spec.rb +59 -0
  28. data/spec/conjunctive_graph_spec.rb +1 -1
  29. data/spec/cwm_spec.rb +32 -0
  30. data/spec/graph_spec.rb +114 -3
  31. data/spec/literal_spec.rb +107 -5
  32. data/spec/matchers.rb +104 -51
  33. data/spec/n3parser_spec.rb +798 -99
  34. data/spec/namespaces_spec.rb +26 -0
  35. data/spec/quoted_graph_spec.rb +0 -0
  36. data/spec/rdf_helper.rb +197 -0
  37. data/spec/rdfa_helper.rb +15 -11
  38. data/spec/rdfa_parser_spec.rb +6 -8
  39. data/spec/rdfxml_spec.rb +21 -28
  40. data/spec/spec_helper.rb +20 -0
  41. data/spec/sqlite3_store_spec.rb +6 -5
  42. data/spec/store_helper.rb +9 -1
  43. data/spec/string_hacks_spec.rb +14 -0
  44. data/spec/{rdfxml_helper.rb → swap_helper.rb} +8 -9
  45. data/spec/swap_spec.rb +77 -0
  46. data/spec/swap_test/animal.rdf +17 -0
  47. data/spec/swap_test/anon-prop.n3 +14 -0
  48. data/spec/swap_test/anonymous_loop.n3 +2 -0
  49. data/spec/swap_test/contexts.n3 +16 -0
  50. data/spec/swap_test/daml-pref.n3 +10 -0
  51. data/spec/swap_test/i18n/hiragana.n3 +22 -0
  52. data/spec/swap_test/i18n/n3string.n3 +4 -0
  53. data/spec/swap_test/list/itemType.rdf +12 -0
  54. data/spec/swap_test/lists-simple.n3 +40 -0
  55. data/spec/swap_test/lists.n3 +35 -0
  56. data/spec/swap_test/n3/n3parser.tests_n3_10001.nt +3 -0
  57. data/spec/swap_test/n3/n3parser.tests_n3_10002.nt +7 -0
  58. data/spec/swap_test/n3/n3parser.tests_n3_10003.nt +3 -0
  59. data/spec/swap_test/n3/n3parser.tests_n3_10004.nt +119 -0
  60. data/spec/swap_test/n3/n3parser.tests_n3_10005.nt +3 -0
  61. data/spec/swap_test/n3/n3parser.tests_n3_10006.nt +225 -0
  62. data/spec/swap_test/n3/n3parser.tests_n3_10007.nt +79 -0
  63. data/spec/swap_test/n3/n3parser.tests_n3_10008.nt +5 -0
  64. data/spec/swap_test/n3/n3parser.tests_n3_10009.nt +13 -0
  65. data/spec/swap_test/n3/n3parser.tests_n3_10010.nt +21 -0
  66. data/spec/swap_test/n3/n3parser.tests_n3_10011.nt +9 -0
  67. data/spec/swap_test/n3/n3parser.tests_n3_10012.nt +53 -0
  68. data/spec/swap_test/n3/n3parser.tests_n3_10013.nt +19 -0
  69. data/spec/swap_test/n3/n3parser.tests_n3_10014.nt +103 -0
  70. data/spec/swap_test/n3/n3parser.tests_n3_10015.nt +103 -0
  71. data/spec/swap_test/n3/n3parser.tests_n3_10016.nt +3 -0
  72. data/spec/swap_test/n3/n3parser.tests_n3_10017.nt +151 -0
  73. data/spec/swap_test/n3/n3parser.tests_n3_10018.nt +9 -0
  74. data/spec/swap_test/n3/n3parser.tests_n3_10019.nt +3 -0
  75. data/spec/swap_test/n3/n3parser.tests_n3_10020.nt +13 -0
  76. data/spec/swap_test/n3parser.tests +160 -0
  77. data/spec/swap_test/nodeID/classes.n3 +9 -0
  78. data/spec/swap_test/nodeID/classes.ref.rdf +15 -0
  79. data/spec/swap_test/nodeID/ex1.rdf +15 -0
  80. data/spec/swap_test/norm/fix.rdf +33 -0
  81. data/spec/swap_test/owl-ex.rdf +150 -0
  82. data/spec/swap_test/ref/animal.n3 +11 -0
  83. data/spec/swap_test/ref/anon-prop-1.n3 +8 -0
  84. data/spec/swap_test/ref/anonymous_loop.ref +7 -0
  85. data/spec/swap_test/ref/bnode.n3 +6 -0
  86. data/spec/swap_test/ref/bnode.rdf +16 -0
  87. data/spec/swap_test/ref/colon-in-uri.n3 +15 -0
  88. data/spec/swap_test/ref/daml-ex.n3 +103 -0
  89. data/spec/swap_test/ref/daml-ont.n3 +289 -0
  90. data/spec/swap_test/ref/djb1a-out.n3 +4 -0
  91. data/spec/swap_test/ref/dot-dash.n3 +8 -0
  92. data/spec/swap_test/ref/in-xml-t.n3 +4 -0
  93. data/spec/swap_test/ref/itemType.n3 +9 -0
  94. data/spec/swap_test/ref/keywords1.n3 +11 -0
  95. data/spec/swap_test/ref/keywords2.n3 +11 -0
  96. data/spec/swap_test/ref/lists-simple-1.rdf +108 -0
  97. data/spec/swap_test/ref/lists.n3 +43 -0
  98. data/spec/swap_test/ref/lstring-out.n3 +20 -0
  99. data/spec/swap_test/ref/n3string.n3 +13 -0
  100. data/spec/swap_test/ref/no-last-nl.n3 +3 -0
  101. data/spec/swap_test/ref/numbers.n3 +18 -0
  102. data/spec/swap_test/ref/path1.n3 +8 -0
  103. data/spec/swap_test/ref/path2.n3 +39 -0
  104. data/spec/swap_test/ref/prefix1.rdf +31 -0
  105. data/spec/swap_test/ref/prefix3.rdf +31 -0
  106. data/spec/swap_test/ref/rdf-redefine.rdf +11 -0
  107. data/spec/swap_test/ref/reluri-1.rdf +18 -0
  108. data/spec/swap_test/ref/strquot.n3 +23 -0
  109. data/spec/swap_test/ref/strquot_a.n3 +23 -0
  110. data/spec/swap_test/ref/xml-base3.n3 +5 -0
  111. data/spec/swap_test/ref/xml-redefine.rdf +20 -0
  112. data/spec/swap_test/ref/xml-redefine2.rdf +23 -0
  113. data/spec/swap_test/ref/xml-syntax-basic-serialization.rdf +10 -0
  114. data/spec/swap_test/ref/xmllit.nt +3 -0
  115. data/spec/swap_test/regression.n3 +231 -0
  116. data/spec/swap_test/reluri-1.n3 +10 -0
  117. data/spec/swap_test/strquot.n3 +23 -0
  118. data/spec/swap_test/syntax/colon-in-uri.rdf +27 -0
  119. data/spec/swap_test/syntax/djb1a.n3 +3 -0
  120. data/spec/swap_test/syntax/dot-dash.n3 +15 -0
  121. data/spec/swap_test/syntax/equals1.n3 +1 -0
  122. data/spec/swap_test/syntax/equals2.n3 +1 -0
  123. data/spec/swap_test/syntax/keywords1.n3 +17 -0
  124. data/spec/swap_test/syntax/keywords2.n3 +18 -0
  125. data/spec/swap_test/syntax/lstring.n3 +26 -0
  126. data/spec/swap_test/syntax/neg-formula-predicate.n3 +1 -0
  127. data/spec/swap_test/syntax/neg-keywords3.n3 +1 -0
  128. data/spec/swap_test/syntax/neg-literal-predicate.n3 +1 -0
  129. data/spec/swap_test/syntax/neg-single-quote.n3 +1 -0
  130. data/spec/swap_test/syntax/neg-thisadoc.n3 +1 -0
  131. data/spec/swap_test/syntax/no-last-nl.n3 +2 -0
  132. data/spec/swap_test/syntax/numbers.n3 +26 -0
  133. data/spec/swap_test/syntax/path1.n3 +23 -0
  134. data/spec/swap_test/syntax/path2.n3 +31 -0
  135. data/spec/swap_test/syntax/qvars1.n3 +19 -0
  136. data/spec/swap_test/syntax/qvars2.n3 +19 -0
  137. data/spec/swap_test/syntax/this-quantifiers.n3 +164 -0
  138. data/spec/swap_test/syntax/this-rules.n3 +43 -0
  139. data/spec/swap_test/syntax/too-nested.n3 +25 -0
  140. data/spec/swap_test/syntax/trailing-semicolon.n3 +12 -0
  141. data/spec/swap_test/syntax/zero-objects.n3 +1 -0
  142. data/spec/swap_test/syntax/zero-predicates.n3 +3 -0
  143. data/spec/swap_test/tests-work.txt +25 -0
  144. data/spec/swap_test/xml-syntax/basic-serialization.n3 +8 -0
  145. data/spec/swap_test/xml-syntax/in-xml.xml +13 -0
  146. data/spec/swap_test/xml-syntax/non-ascii-pred.rdf +14 -0
  147. data/spec/swap_test/xml-syntax/rdf_prefix.n3 +2 -0
  148. data/spec/swap_test/xml-syntax/xml_prefix.n3 +7 -0
  149. data/spec/swap_test/xml-syntax/xml_prefix2.n3 +9 -0
  150. data/spec/swap_test/xml-syntax/xmlbase3.rdf +10 -0
  151. data/spec/swap_test/xml-syntax/xmllit.rdf +33 -0
  152. data/spec/triple_spec.rb +90 -46
  153. data/spec/turtle/README.txt +20 -0
  154. data/spec/turtle/bad-00.ttl +2 -0
  155. data/spec/turtle/bad-01.ttl +3 -0
  156. data/spec/turtle/bad-02.ttl +3 -0
  157. data/spec/turtle/bad-03.ttl +3 -0
  158. data/spec/turtle/bad-04.ttl +3 -0
  159. data/spec/turtle/bad-05.ttl +4 -0
  160. data/spec/turtle/bad-06.ttl +3 -0
  161. data/spec/turtle/bad-07.ttl +4 -0
  162. data/spec/turtle/bad-08.ttl +2 -0
  163. data/spec/turtle/bad-09.ttl +3 -0
  164. data/spec/turtle/bad-10.ttl +3 -0
  165. data/spec/turtle/bad-11.ttl +3 -0
  166. data/spec/turtle/bad-12.ttl +3 -0
  167. data/spec/turtle/bad-13.ttl +3 -0
  168. data/spec/turtle/bad-14.ttl +6 -0
  169. data/spec/turtle/manifest-bad.ttl +88 -0
  170. data/spec/turtle/manifest.ttl +215 -0
  171. data/spec/turtle/rdf-schema.out +126 -0
  172. data/spec/turtle/rdf-schema.ttl +156 -0
  173. data/spec/turtle/rdfq-results.out +36 -0
  174. data/spec/turtle/rdfq-results.ttl +39 -0
  175. data/spec/turtle/rdfs-namespace.out +131 -0
  176. data/spec/turtle/rdfs-namespace.ttl +160 -0
  177. data/spec/turtle/test-00.out +1 -0
  178. data/spec/turtle/test-00.ttl +2 -0
  179. data/spec/turtle/test-01.out +3 -0
  180. data/spec/turtle/test-01.ttl +7 -0
  181. data/spec/turtle/test-02.out +3 -0
  182. data/spec/turtle/test-02.ttl +5 -0
  183. data/spec/turtle/test-03.out +3 -0
  184. data/spec/turtle/test-03.ttl +5 -0
  185. data/spec/turtle/test-04.out +2 -0
  186. data/spec/turtle/test-04.ttl +4 -0
  187. data/spec/turtle/test-05.out +4 -0
  188. data/spec/turtle/test-05.ttl +4 -0
  189. data/spec/turtle/test-06.out +1 -0
  190. data/spec/turtle/test-06.ttl +3 -0
  191. data/spec/turtle/test-07.out +5 -0
  192. data/spec/turtle/test-07.ttl +3 -0
  193. data/spec/turtle/test-08.out +1 -0
  194. data/spec/turtle/test-08.ttl +3 -0
  195. data/spec/turtle/test-09.out +4 -0
  196. data/spec/turtle/test-09.ttl +10 -0
  197. data/spec/turtle/test-10.out +5 -0
  198. data/spec/turtle/test-10.ttl +5 -0
  199. data/spec/turtle/test-11.out +4 -0
  200. data/spec/turtle/test-11.ttl +10 -0
  201. data/spec/turtle/test-12.out +4 -0
  202. data/spec/turtle/test-12.ttl +9 -0
  203. data/spec/turtle/test-13.out +2 -0
  204. data/spec/turtle/test-13.ttl +7 -0
  205. data/spec/turtle/test-14.out +10000 -0
  206. data/spec/turtle/test-14.ttl +10002 -0
  207. data/spec/turtle/test-15.out +10000 -0
  208. data/spec/turtle/test-15.ttl +3 -0
  209. data/spec/turtle/test-16.out +10000 -0
  210. data/spec/turtle/test-16.ttl +10002 -0
  211. data/spec/turtle/test-17.out +1 -0
  212. data/spec/turtle/test-17.ttl +6 -0
  213. data/spec/turtle/test-18.out +2 -0
  214. data/spec/turtle/test-18.ttl +9 -0
  215. data/spec/turtle/test-19.out +1 -0
  216. data/spec/turtle/test-19.ttl +4 -0
  217. data/spec/turtle/test-20.out +2 -0
  218. data/spec/turtle/test-20.ttl +6 -0
  219. data/spec/turtle/test-21.out +3 -0
  220. data/spec/turtle/test-21.ttl +4 -0
  221. data/spec/turtle/test-22.out +3 -0
  222. data/spec/turtle/test-22.ttl +4 -0
  223. data/spec/turtle/test-23.out +1 -0
  224. data/spec/turtle/test-23.ttl +3 -0
  225. data/spec/turtle/test-24.out +2 -0
  226. data/spec/turtle/test-24.ttl +3 -0
  227. data/spec/turtle/test-25.out +7 -0
  228. data/spec/turtle/test-25.ttl +14 -0
  229. data/spec/turtle/test-26.out +1 -0
  230. data/spec/turtle/test-26.ttl +4 -0
  231. data/spec/turtle/test-27.out +1 -0
  232. data/spec/turtle/test-27.ttl +5 -0
  233. data/spec/turtle/test-28-out.ttl +6 -0
  234. data/spec/turtle/test-28.out +22 -0
  235. data/spec/turtle/test-28.ttl +22 -0
  236. data/spec/turtle/test-29.out +1 -0
  237. data/spec/turtle/test-29.ttl +1 -0
  238. data/spec/turtle/test-30.out +5 -0
  239. data/spec/turtle/test-30.ttl +12 -0
  240. data/spec/turtle_spec.rb +64 -0
  241. data/spec/uriref_spec.rb +74 -34
  242. data/test/n3_tests/rdflib/n3-writer-teset-26.n3 +31 -0
  243. data/test/n3_tests/rdflib/n3-writer-teset-26.nt +14 -0
  244. data/test/rdf_tests/xml-literal-mixed.nt +1 -2
  245. metadata +278 -39
  246. data/lib/rdf_context/n3_grammar.rb +0 -2171
  247. data/test/perf_test/test.rb +0 -11
  248. data/test/perf_test/tommorris.rdf +0 -2267
@@ -1,3 +1,30 @@
1
+ === 0.5.0
2
+ * Support for Ruby 1.9, 1.8.7 and 1.8.6.
3
+ * Unicode escapes and URIRefs only work properly in Ruby 1.9
4
+ * Replaced some (&:meth) spells with {|c| c.meth} for Ruby 1.8.6 compatibility
5
+ * Fully compliant N3 parser (n3-rdf level) with extnensive tests, including SWAP, CWM and Turtle test suites.
6
+ * Supports paths, keywords, all semantic expressions.
7
+ * No support yet for Formulae, variables, rules or automatic reification
8
+ * Allow Triple#subject to be like an object, allowing literals and graphs
9
+ * Allow Triple#predicate to be a BNode as well as a URIRef
10
+ * Graph changes
11
+ * Graph#properties(subject) returns properties of a subject expressed as a hash to arrays of objects.
12
+ * Graph#seq(subject) returns ordered rdf:_n objects if subject is a rdf:Seq., or list of rdf:first/rdf:rest
13
+ * Graph#qname(uri) as alternative to uri.qname, has namespaces available.
14
+ * Graph#type_of(subject) array of RDF_TYPE objects.
15
+ * Graph#allow_n3 (getter/setter and option) controls if extra N3 semantics for graphs are allowed. Otherwise, calls Triple#validate_rdf to raise exception
16
+ * Real graph comparisons, including permutation search of triples containing BNodes (obviously, may be expensive)
17
+ * Add QuotedGraph and AggregateGraph
18
+ * Literal changes
19
+ * Literal#== as alias to eql? Needed for sort and uniq.
20
+ * Normalize valid typed literals
21
+ * Added Literal#valid? to perform some content validations.
22
+ * URIRef/Namespace changes
23
+ * Fix URI generation, performing normalizations for normal URI refs, and not for Namespace URIs.
24
+ * Fixed bug in URIRef#namespace & to_qname when namespace does not have a trailing / or #
25
+ * URIRef#short_name may return a different value after a namespace is assigned.
26
+ * Reduce dependency on Redland when running tests
27
+
1
28
  === 0.4.8
2
29
  * Add Duration and support in Literal
3
30
  * Add datatype support for xs:boolean, xs:double, xs:duration and xs:time.
@@ -7,7 +7,7 @@ A set of compliant RDF parsers:
7
7
  * RDF/XML
8
8
  * RDFa
9
9
 
10
- Multiple datastores may be attached to a Graph, including Memory, List and SQLite3
10
+ Multiple data-stores may be attached to a Graph, including Memory, List and SQLite3
11
11
 
12
12
  == DESCRIPTION:
13
13
 
@@ -18,7 +18,7 @@ RdfContext parses RDF/XML, RDFa and N3-rdf into a Graph object. It also serializ
18
18
 
19
19
  * Fully compliant RDF/XML parser.
20
20
  * Fully compliant XHTML/RDFa 1.0 parser.
21
- * N3-rdf parser
21
+ * Fully compliant N3-rdf parser (N3-rdf level)
22
22
  * N-Triples and RDF/XML serializer
23
23
  * RDFa tests use SPARQL for most tests due to Rasqal limitations. Other tests compare directly against N-triples.
24
24
  * Graph serializes into RDF/XML and N-Triples.
@@ -49,11 +49,24 @@ Graphs also store namespace associations, and can serialize graphs to RDF/XML or
49
49
  g.to_rdfxml
50
50
  g.to_ntriples
51
51
 
52
+ Resource properties
53
+
54
+ graph.parse(':foo a :bar; rdfs:label "An example" .', "http://example.com/")
55
+ graph.resources("http://example.com/subject") =>
56
+ {
57
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" => [<http://example.com/#bar>],
58
+ "http://example.com/#label" => ["An example"]
59
+ }
60
+
52
61
  Instantiate an existing graph from a datastore
53
62
 
54
63
  s = SQLIte3Store.new(:path => "store.db")
55
64
  g = Graph.new(:store => s, :identifier => "http://example.com/context")
56
65
 
66
+ QuotedGraph implements N3 Formulae semantics, by creating a graph within a store that is formula_aware. QuotedGraph triples are not returned in a query to a ConjunctiveGraph.
67
+
68
+ AggregateGraphs allow multiple graphs from a given context_aware store to be combined into a single read-only graph.
69
+
57
70
  === Parsers
58
71
  Instantiate a parser and parse source, specifying type and base-URL
59
72
 
@@ -94,14 +107,9 @@ SQLite3Store:: context aware datastore using a SQLite3 database to create a pers
94
107
 
95
108
  == TODO:
96
109
  * Testing
97
- * Integrate ntriples tests in spec/ntriples/test.nt
98
- * Turtle tests from http://www.w3.org/2000/10/swap/test/n3/turtle-manifest.n3 (if can find .ttl and .out files)
99
- * N3-RDF tests from http://www.w3.org/2000/10/swap/test/n3/n3-rdf.tests
100
- * N3-Full tests from http://www.w3.org/2000/10/swap/test/n3/n3-full.tests
101
- * N3 grammar tests?
102
110
  * RDFa updates for new tests and non XHTML representations.
103
111
  * Graphs
104
- * n3 serialization
112
+ * n3/turtle serialization
105
113
  * Reasoner/inference engine
106
114
  * SPARQL
107
115
  * RDFS logic and RDF entailment tests
@@ -109,11 +117,16 @@ SQLite3Store:: context aware datastore using a SQLite3 database to create a pers
109
117
  * ActiveRDF-like class support
110
118
  * Integrate with RDFObjects (http://github.com/rsinger/RDFObjects)
111
119
 
120
+ == Resources:
121
+ * Distiller[http://kellogg-assoc/distiller]
122
+ * RDoc[http://rdoc.info/projects/gkellogg/rdf_context]
123
+ * History[http://github.com/gkellogg/rdf_context/blob/master/History.txt]
124
+
112
125
  == LICENSE:
113
126
 
114
127
  (The MIT License)
115
128
 
116
- Copyright (c) 2009 Gregg Kellogg
129
+ Copyright (c) 2009-2010 Gregg Kellogg
117
130
 
118
131
  Copyright (c) 2008 Tom Morris and contributors
119
132
 
data/Rakefile CHANGED
@@ -50,28 +50,33 @@ Spec::Rake::SpecTask.new(:spec) do |spec|
50
50
  end
51
51
 
52
52
  desc "Run specs through RCov"
53
- Spec::Rake::SpecTask.new(:rcov) do |spec|
53
+ Spec::Rake::SpecTask.new("spec:rcov") do |spec|
54
54
  spec.libs << 'lib' << 'spec'
55
55
  spec.pattern = 'spec/*_spec.rb'
56
56
  spec.rcov = true
57
57
  end
58
58
 
59
+ desc "Generate HTML report specs"
60
+ Spec::Rake::SpecTask.new("doc:spec") do |spec|
61
+ spec.libs << 'lib' << 'spec'
62
+ spec.spec_files = FileList['spec/*_spec.rb']
63
+ spec.spec_opts = ["--format", "html:doc/spec.html"]
64
+ end
65
+
59
66
  task :spec => :check_dependencies
60
67
 
61
68
  task :default => :spec
62
69
 
63
70
  require 'rake/rdoctask'
64
- Rake::RDocTask.new do |rdoc|
71
+ Rake::RDocTask.new("doc:rdoc") do |rdoc|
65
72
  if File.exist?('VERSION')
66
73
  version = File.read('VERSION')
67
74
  else
68
- version = RdfaParser::VERSION
75
+ version = RdfContext::VERSION
69
76
  end
70
77
 
71
- rdoc.rdoc_dir = 'rdoc'
78
+ rdoc.rdoc_dir = 'doc/rdoc'
72
79
  rdoc.title = "rdf_context #{version}"
73
80
  rdoc.rdoc_files.include('README*', "History.txt")
74
81
  rdoc.rdoc_files.include('lib/**/*.rb')
75
82
  end
76
-
77
- # vim: syntax=Ruby
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.8
1
+ 0.5.0
@@ -9,7 +9,7 @@ class Parse
9
9
  graph_opts = {:identifier => base_uri}
10
10
  graph_opts[:store] = store if store
11
11
  parser = Parser.new(:graph => Graph.new(graph_opts))
12
- parser.parse(File.read(file), base_uri, :strict => true)
12
+ parser.parse(file.respond_to?(:read) ? file : File.open(file), base_uri, :strict => true)
13
13
  output = case $format
14
14
  when "xml"
15
15
  parser.graph.to_rdfxml
@@ -30,9 +30,12 @@ class Parse
30
30
  end
31
31
  end
32
32
 
33
+ mode = ARGV.shift
34
+ raise "Mode must be one of 'parse'" unless mode == "parse"
35
+
33
36
  $verbose = false
34
37
  base_uri = "http://example.com"
35
- store = :list_store
38
+ store = :list_store
36
39
  opts = GetoptLong.new(
37
40
  ["--verbose", GetoptLong::NO_ARGUMENT],
38
41
  ["--quiet", GetoptLong::NO_ARGUMENT],
@@ -62,7 +65,12 @@ opts.each do |opt, arg|
62
65
  end
63
66
 
64
67
  x = Parse.new
65
- ARGV.each do |test_file|
66
- x.parse(test_file, base_uri, store)
68
+ if ARGV.empty?
69
+ s = $stdin.read
70
+ x.parse(StringIO.new(s), base_uri, store)
71
+ else
72
+ ARGV.each do |test_file|
73
+ x.parse(test_file, base_uri, store)
74
+ end
67
75
  end
68
76
 
@@ -43,7 +43,7 @@ module RdfContext
43
43
  | \\\\u[0-9a-fA-F]
44
44
  )
45
45
  ( [0-9a-zA-Z_\.-]
46
- | \\\\u([0-9a-fA-F]{4}) # \u followed by a sequence of four hex digits
46
+ | \\\\u([0-9a-fA-F]{4})
47
47
  )*
48
48
  $},
49
49
  Regexp::EXTENDED)
@@ -51,6 +51,8 @@ module RdfContext
51
51
  RDF_TYPE = URIRef.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
52
52
  XML_LITERAL = Literal::Encoding.xmlliteral
53
53
 
54
+ OWL_NS = Namespace.new("http://www.w3.org/2002/07/owl#", "owl")
55
+ LOG_NS = Namespace.new("http://www.w3.org/2000/10/swap/log#", "log")
54
56
  RDF_NS = Namespace.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf")
55
57
  RDFS_NS = Namespace.new("http://www.w3.org/2000/01/rdf-schema#", "rdfs")
56
58
  XHV_NS = Namespace.new("http://www.w3.org/1999/xhtml/vocab#", "xhv")
@@ -0,0 +1,86 @@
1
+ require File.join(File.dirname(__FILE__), "graph")
2
+
3
+ module RdfContext
4
+ # AggregateGraph - A read-only graph composed of multiple other graphs.
5
+ #
6
+ # Essentially a ConjunctiveGraph over an explicit subset of the entire store.
7
+ class AggregateGraph < Graph
8
+ attr_reader :graphs
9
+
10
+ # List of graphs to aggregate
11
+ def initialize(*graph)
12
+ @graphs = graph
13
+ end
14
+
15
+ def destroy(configuration = nil); raise ReadOnlyGraphException; end
16
+ def commit; raise ReadOnlyGraphException; end
17
+ def rollback; raise ReadOnlyGraphException; end
18
+ def add; raise ReadOnlyGraphException; end
19
+ def remove; raise ReadOnlyGraphException; end
20
+ def bind(namespace); raise ReadOnlyGraphException; end
21
+ def parse(stream, uri, options = {}); raise ReadOnlyGraphException; end
22
+ def n3; raise ReadOnlyGraphException; end
23
+
24
+ # Open the graphs
25
+ def open(configuration = {})
26
+ @graphs.each {|g| g.open(configuration)}
27
+ end
28
+
29
+ # Close the graphs
30
+ def close(configuration = {})
31
+ @graphs.each {|g| g.close(configuration)}
32
+ end
33
+
34
+ # Number of Triples in the graph
35
+ def size
36
+ @graphs.inject(0) {|memo, g| memo += g.size}
37
+ end
38
+
39
+ # List of distinct subjects in graph
40
+ def subjects
41
+ @graphs.inject([]) {|memo, g| memo += g.subjects}
42
+ end
43
+
44
+ # List of distinct predicates in graph
45
+ def predicates
46
+ @graphs.inject([]) {|memo, g| memo += g.predicates}
47
+ end
48
+
49
+ # List of distinct objects in graph
50
+ def objects
51
+ @graphs.inject([]) {|memo, g| memo += g.objects}
52
+ end
53
+
54
+ # Triples from graph, optionally matching subject, predicate, or object.
55
+ # Delegates to Store#triples.
56
+ #
57
+ # @param [Triple, nil] triple:: Triple to match, may be a pattern triple or nil
58
+ # @return [Array]:: List of matched triples
59
+ def triples(triple = Triple.new(nil, nil, nil), &block) # :yields: triple, context
60
+ @graphs.inject([]) {|memo, g| memo += g.triples(triple, &block)}
61
+ end
62
+
63
+ # Check to see if this graph contains the specified triple
64
+ def contains?(triple)
65
+ @graphs.any? {|g| g.contains?(triple) }
66
+ end
67
+
68
+ # Get all BNodes with usage count used within graph
69
+ def bnodes
70
+ @graphs.inject([]) {|memo, g| memo += g.bnodes}
71
+ end
72
+
73
+ # Only compares to another AggregateGraph. Compares each sub-graph
74
+ def eql?(other)
75
+ other.is_a?(AggregateGraph) ? super : false
76
+ end
77
+
78
+ def nsbinding
79
+ @graphs.inject({}) {|memo, g| memo.merge(g.nsbinding)}
80
+ end
81
+
82
+ def uri_binding
83
+ @graphs.inject({}) {|memo, g| memo.merge(g.uri_binding)}
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ class Array
2
+ # http://wiki.rubygarden.org/Ruby/page/show/ArrayPermute
3
+ # Permute an array, and call a block for each permutation
4
+ # Author: Paul Battley
5
+ def permute(prefixed=[])
6
+ if (length < 2)
7
+ # there are no elements left to permute
8
+ yield(prefixed + self)
9
+ else
10
+ # recursively permute the remaining elements
11
+ each_with_index do |e, i|
12
+ (self[0,i]+self[(i+1)..-1]).permute(prefixed+[e]) { |a| yield a }
13
+ end
14
+ end
15
+ end
16
+
17
+ # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
18
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
19
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
20
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
21
+ def to_sentence(options = {})
22
+ default_words_connector = ", "
23
+ default_two_words_connector = " and "
24
+ default_last_word_connector = ", and "
25
+
26
+ # Try to emulate to_senteces previous to 2.3
27
+ if options.has_key?(:connector) || options.has_key?(:skip_last_comma)
28
+ ::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector
29
+ ::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma
30
+
31
+ skip_last_comma = options.delete :skip_last_comma
32
+ if connector = options.delete(:connector)
33
+ options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}"
34
+ else
35
+ options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector
36
+ end
37
+ end
38
+
39
+ # options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
40
+ options = {:words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector}.merge(options)
41
+
42
+ case length
43
+ when 0
44
+ ""
45
+ when 1
46
+ self[0].to_s
47
+ when 2
48
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
49
+ else
50
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
51
+ end
52
+ end
53
+ end
@@ -36,7 +36,7 @@ module RdfContext
36
36
  # Triples across all contexts in store, optionally matching subject, predicate, or object.
37
37
  # Delegates to Store#triples.
38
38
  #
39
- # @param [Triple, nil] triple:: Triple to match, may be a patern triple or nil
39
+ # @param [Triple, nil] triple:: Triple to match, may be a pattern triple or nil
40
40
  # @return [Array]:: List of matched triples
41
41
  def triples(triple = Triple.new(nil, nil, nil), &block) # :yields: triple, context
42
42
  @store.triples(triple, nil, &block) || []
@@ -1,11 +1,13 @@
1
1
  module RdfContext
2
- class RdfException < RuntimeError; end
2
+ class RdfException < RuntimeError; end
3
+
3
4
  class ParserException < RdfException; end
4
- class GraphException < RdfException; end
5
- class StoreException < RdfException; end
6
- class BNodeException < RdfException; end
7
- class TypeError < RdfException; end
5
+ class GraphException < RdfException; end
6
+ class StoreException < RdfException; end
7
+ class BNodeException < RdfException; end
8
+ class TypeError < RdfException; end
9
+ class InvalidNode < RdfException; end
8
10
  class InvalidPredicate < RdfException; end
9
- class InvalidSubject < RdfException; end
10
- class InvalidObject < RdfException; end
11
+
12
+ class ReadOnlyGraphException < GraphException; end
11
13
  end
@@ -1,5 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), 'namespace')
2
2
  require File.join(File.dirname(__FILE__), 'triple')
3
+ require File.join(File.dirname(__FILE__), 'array_hacks')
3
4
  require File.join(File.dirname(__FILE__), 'store', 'list_store')
4
5
  require File.join(File.dirname(__FILE__), 'store', 'memory_store')
5
6
 
@@ -12,6 +13,7 @@ module RdfContext
12
13
  attr_reader :nsbinding
13
14
  attr_reader :identifier
14
15
  attr_reader :store
16
+ attr_accessor :allow_n3
15
17
 
16
18
  # Create a Graph with the given store and identifier.
17
19
  #
@@ -30,6 +32,7 @@ module RdfContext
30
32
  # @param [Hash] options:: Options
31
33
  # <em>options[:store]</em>:: storage, defaults to a new ListStore instance. May be symbol :list_store or :memory_store
32
34
  # <em>options[:identifier]</em>:: Identifier for this graph, BNode or URIRef
35
+ # <em>options[:allow_n3]</em>:: Allow N3-specific triples: Literals as subject, BNodes as predicate
33
36
  def initialize(options = {})
34
37
  @nsbinding = {}
35
38
 
@@ -41,7 +44,9 @@ module RdfContext
41
44
  else ListStore.new
42
45
  end
43
46
 
44
- @identifier = Triple.coerce_subject(options[:identifier]) || BNode.new
47
+ @allow_n3 = options[:allow_n3]
48
+
49
+ @identifier = Triple.coerce_node(options[:identifier]) || BNode.new
45
50
  end
46
51
 
47
52
  def inspect
@@ -53,7 +58,7 @@ module RdfContext
53
58
  [self.class.to_s, self.identifier].hash
54
59
  end
55
60
 
56
- def context_aware?; @context_aware; end
61
+ def context_aware?; @store.context_aware?; end
57
62
 
58
63
  # Data Store interface
59
64
  def nsbinding; @store.nsbinding; end
@@ -136,7 +141,8 @@ module RdfContext
136
141
  # Add bindings for predicates not already having bindings
137
142
  tmp_ns = "ns0"
138
143
  predicates.each do |p|
139
- unless uri_bindings.has_key?(p.base)
144
+ raise "Attempt to serialize graph containing non-strict RDF compiant BNode as predicate" unless p.is_a?(URIRef)
145
+ if !p.namespace(uri_bindings)
140
146
  uri_bindings[p.base] = Namespace.new(p.base, tmp_ns)
141
147
  rdf_attrs["xmlns:#{tmp_ns}"] = p.base
142
148
  tmp_ns = tmp_ns.succ
@@ -188,6 +194,11 @@ module RdfContext
188
194
  # Hash of uri => Namespace bindings
189
195
  def uri_binding; @store.uri_binding; end
190
196
 
197
+ # QName for a URI
198
+ def qname(uri)
199
+ uri.to_qname(self.uri_binding)
200
+ end
201
+
191
202
  # Namespace for prefix
192
203
  def namespace(prefix); @store.namespace(prefix); end
193
204
 
@@ -235,6 +246,7 @@ module RdfContext
235
246
  # @param [Triple] t:: the triple to be added to the graph
236
247
  # @return [Graph]:: Returns the graph
237
248
  def << (triple)
249
+ triple.validate_rdf unless @allow_n3 # Only add triples if n3-mode is set
238
250
  @store.add(triple, self)
239
251
  self
240
252
  end
@@ -254,7 +266,10 @@ module RdfContext
254
266
  def add(*triples)
255
267
  options = triples.last.is_a?(Hash) ? triples.pop : {}
256
268
  ctx = options[:context] || @default_context || self
257
- triples.each {|t| @store.add(t, ctx)}
269
+ triples.each do |t|
270
+ t.validate_rdf unless @allow_n3 # Only add triples if n3-mode is set
271
+ @store.add(t, ctx)
272
+ end
258
273
  self
259
274
  end
260
275
 
@@ -265,19 +280,73 @@ module RdfContext
265
280
  # Triples from graph, optionally matching subject, predicate, or object.
266
281
  # Delegates to Store#triples.
267
282
  #
268
- # @param [Triple, nil] triple:: Triple to match, may be a patern triple or nil
283
+ # @param [Triple, nil] triple:: Triple to match, may be a pattern triple or nil
269
284
  # @return [Array]:: List of matched triples
270
285
  def triples(triple = Triple.new(nil, nil, nil), &block) # :yields: triple, context
271
286
  @store.triples(triple, self, &block) || []
272
287
  end
273
288
  alias_method :find, :triples
289
+
290
+ # Returns ordered rdf:_n objects or rdf:first, rdf:rest for a given subject
291
+ def seq(subject)
292
+ props = properties(subject)
293
+ rdf_type = props[RDF_TYPE.to_s] || []
294
+
295
+ if rdf_type.include?(RDF_NS.Seq)
296
+ props.keys.select {|k| k.match(/#{RDF_NS.uri}_(\d)$/)}.
297
+ sort_by {|i| i.sub(RDF_NS._.to_s, "").to_i}.
298
+ map {|key| props[key]}.
299
+ flatten
300
+ elsif self.triples(Triple.new(subject, RDF_NS.first, nil))
301
+ # N3-style first/rest chain
302
+ list = []
303
+ while subject != RDF_NS.nil
304
+ props = properties(subject)
305
+ list += props[RDF_NS.first.to_s]
306
+ subject = props[RDF_NS.rest.to_s].first
307
+ end
308
+ list
309
+ else
310
+ []
311
+ end
312
+ end
313
+
314
+ # Resource properties
315
+ #
316
+ # Properties arranged as a hash with the predicate Term as index to an array of resources or literals
317
+ #
318
+ # Example:
319
+ # graph.parse(':foo a :bar; rdfs:label "An example" .', "http://example.com/")
320
+ # graph.resources("http://example.com/subject") =>
321
+ # {
322
+ # "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" => [<http://example.com/#bar>],
323
+ # "http://example.com/#label" => ["An example"]
324
+ # }
325
+ def properties(subject)
326
+ @properties ||= {}
327
+ @properties[subject.to_s] ||= begin
328
+ hash = Hash.new
329
+ self.triples(Triple.new(subject, nil, nil)).map do |t, ctx|
330
+ pred = t.predicate.to_s
331
+
332
+ hash[pred] ||= []
333
+ hash[pred] << t.object
334
+ end
335
+ hash
336
+ end
337
+ end
338
+
339
+ # Return an n3 identifier for the Graph
340
+ def n3
341
+ "[#{self.identifier.to_n3}]"
342
+ end
274
343
 
275
344
  # Detect the presence of a BNode in the graph, either as a subject or an object
276
345
  #
277
346
  # @param [BNode] bn:: BNode to find
278
347
  #
279
348
  def has_bnode_identifier?(bn)
280
- triples do |triple, context|
349
+ self.triples do |triple, context|
281
350
  return true if triple.subject.eql?(bn) || triple.object.eql?(bn)
282
351
  end
283
352
  false
@@ -300,6 +369,11 @@ module RdfContext
300
369
  triples(Triple.new(nil, RDF_TYPE, object)).map {|t, ctx| t.subject}
301
370
  end
302
371
 
372
+ # Get type(s) of subject, returns a list of symbols
373
+ def type_of(subject)
374
+ triples(Triple.new(subject, RDF_TYPE, nil)).map {|t, ctx| t.object}
375
+ end
376
+
303
377
  # Merge a graph into this graph
304
378
  def merge!(graph)
305
379
  raise GraphException.new("merge without a graph") unless graph.is_a?(Graph)
@@ -310,9 +384,10 @@ module RdfContext
310
384
 
311
385
  graph.triples do |triple, context|
312
386
  # If triple contains bnodes, remap to new values
313
- if triple.subject.is_a?(BNode) || triple.object.is_a?(BNode)
387
+ if triple.subject.is_a?(BNode) || triple.predicate.is_a?(BNode) || triple.object.is_a?(BNode)
314
388
  triple = triple.clone
315
389
  triple.subject = bn[triple.subject] if triple.subject.is_a?(BNode)
390
+ triple.predicate = bn[triple.predicate] if triple.predicate.is_a?(BNode)
316
391
  triple.object = bn[triple.object] if triple.object.is_a?(BNode)
317
392
  end
318
393
  self << triple
@@ -326,26 +401,53 @@ module RdfContext
326
401
  #
327
402
  # We just follow Python RDFlib's lead and do a simple comparison
328
403
  def eql?(other)
329
- #puts "eql? size #{self.size} vs #{other.size}"
404
+ puts "eql? size #{self.size} vs #{other.size}" if $DEBUG
330
405
  return false if !other.is_a?(Graph) || self.size != other.size
331
- return false unless other.identifier.to_s == identifier.to_s
406
+ return false unless other.identifier.to_s == identifier.to_s unless other.identifier.is_a?(BNode) && identifier.is_a?(BNode)
332
407
 
333
408
  bn_self = bnodes.values.sort
334
409
  bn_other = other.bnodes.values.sort
335
- #puts "eql? bnodes '#{bn_self.to_sentence}' vs '#{bn_other.to_sentence}'"
410
+ puts "eql? bnodes '#{bn_self.to_sentence}' vs '#{bn_other.to_sentence}'" if $DEBUG
336
411
  return false unless bn_self == bn_other
337
412
 
338
413
  # Check each triple to see if it's contained in the other graph
339
414
  triples do |t, ctx|
340
- next if t.subject.is_a?(BNode) || t.object.is_a?(BNode)
341
- #puts "eql? contains '#{t.to_ntriples}'"
415
+ next if t.subject.is_a?(BNode) || t.predicate.is_a?(BNode) || t.object.is_a?(BNode)
416
+ puts "eql? contains '#{t.to_ntriples}: #{other.contains?(t)}'" if $DEBUG
342
417
  return false unless other.contains?(t)
343
418
  end
344
- true
419
+
420
+ # For each BNode, check permutations of similar bnodes in other graph
421
+ bnode_permutations(bnodes, other.bnodes) do |bn_map|
422
+ puts "bnode permutations: #{bn_map.inspect}" if $DEBUG
423
+ # bn_map contains 1-1 mapping of bnodes from self to other
424
+ catch :next_perm do
425
+ triples do |t, ctx|
426
+ next unless t.subject.is_a?(BNode) || t.predicate.is_a?(BNode) || t.object.is_a?(BNode)
427
+ subject, predicate, object = t.subject, t.predicate, t.object
428
+ subject = bn_map[subject] if bn_map.has_key?(subject)
429
+ predicate = bn_map[predicate] if bn_map.has_key?(predicate)
430
+ object = bn_map[object] if bn_map.has_key?(object)
431
+ tn = Triple.new(subject, predicate, object)
432
+ puts " eql? contains '#{tn.inspect}': #{other.contains?(tn)}" if $DEBUG
433
+ next if other.contains?(tn)
434
+
435
+ puts " no, next permutation" if $DEBUG
436
+ # Not a match, try next permutation
437
+ throw :next_perm
438
+ end
439
+
440
+ # If we matched all triples in the graph using this permutation, we're done
441
+ return true
442
+ end
443
+ end
444
+
445
+ # Exhausted all permutations, unless there were no bnodes
446
+ bn_self.length == 0
345
447
  end
346
448
 
347
449
  alias_method :==, :eql?
348
-
450
+
349
451
  # Parse source into Graph.
350
452
  #
351
453
  # Merges results into a common Graph
@@ -356,9 +458,66 @@ module RdfContext
356
458
  # <em>options[:debug]</em>:: Array to place debug messages
357
459
  # <em>options[:type]</em>:: One of _rdfxml_, _html_, or _n3_
358
460
  # <em>options[:strict]</em>:: Raise Error if true, continue with lax parsing, otherwise
461
+ # <em>options[:allow_n3]</em>:: Allow N3-specific triples: Literals as subject, BNodes as predicate
359
462
  # @return [Graph]:: Returns the graph containing parsed triples
360
- def parse(stream, uri, options = {}, &block) # :yields: triple
463
+ def parse(stream, uri = nil, options = {}, &block) # :yields: triple
464
+ @allow_n3 ||= options[:allow_n3]
361
465
  Parser.parse(stream, uri, options.merge(:graph => self), &block)
362
466
  end
363
467
  end
468
+
469
+ protected
470
+
471
+ # Permutations of two BNode lists
472
+ #
473
+ # Take source keys and run permutations mapping to other keys, if the permutation
474
+ # maps to the same counts for each
475
+ def bnode_permutations(bn_source, bn_other)
476
+ puts "compare #{bn_source.inspect}\n with #{bn_other.inspect}" if $DEBUG
477
+
478
+ source_keys = bn_source.keys
479
+ other_keys = bn_other.keys
480
+ values = bn_source.values.uniq
481
+
482
+ # Break key lists into groups based on sharing equivalent usage counts
483
+ case values.length
484
+ when 0
485
+ {}
486
+ when 1
487
+ # All keys have equivalent counts, yield permutations
488
+ if source_keys.length == 1
489
+ puts "yield #{{source_keys.first => other_keys.first}.inspect}" if $DEBUG
490
+ yield({source_keys.first => other_keys.first})
491
+ else
492
+ (0..(source_keys.length-1)).to_a.permute do |indicies|
493
+ puts "indicies #{indicies.inspect}" if $DEBUG
494
+ ok = other_keys.dup
495
+ map = indicies.inject({}) { |hash, i| hash[source_keys[i]] = ok.shift; hash}
496
+ puts "yield #{map.inspect}" if $DEBUG
497
+ yield(map)
498
+ end
499
+ end
500
+ else
501
+ # Break bnodes into 2 arrays sharing a common usage count and permute each separately
502
+ max = values.max
503
+ bn_source_min = bn_source.clone
504
+ bn_other_min = bn_other.clone
505
+ bn_source_max = {}
506
+ bn_other_max = {}
507
+ bn_source.each_pair do |bn, v|
508
+ bn_source_max[bn] = bn_source_min.delete(bn) if v == max
509
+ end
510
+ bn_other.each_pair do |bn, v|
511
+ bn_other_max[bn] = bn_other_min.delete(bn) if v == max
512
+ end
513
+
514
+ puts "yield permutations of multiple with max #{bn_source_max.inspect}\n and #{bn_other_max.inspect}" if $DEBUG
515
+ # Yield for each permutation of max joined with permutations of min
516
+ bnode_permutations(bn_source_max, bn_other_max) do |bn_perm_max|
517
+ bnode_permutations(bn_source_min, bn_other_min) do |bn_perm_min|
518
+ yield bn_perm_max.merge(bn_perm_min)
519
+ end
520
+ end
521
+ end
522
+ end
364
523
  end