rdf_context 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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