weft-qda 0.9.6

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 (55) hide show
  1. data/lib/weft.rb +21 -0
  2. data/lib/weft/WEFT-VERSION-STRING.rb +1 -0
  3. data/lib/weft/application.rb +130 -0
  4. data/lib/weft/backend.rb +39 -0
  5. data/lib/weft/backend/marshal.rb +26 -0
  6. data/lib/weft/backend/mysql.rb +267 -0
  7. data/lib/weft/backend/n6.rb +366 -0
  8. data/lib/weft/backend/sqlite.rb +633 -0
  9. data/lib/weft/backend/sqlite/category_tree.rb +104 -0
  10. data/lib/weft/backend/sqlite/schema.rb +152 -0
  11. data/lib/weft/backend/sqlite/upgradeable.rb +55 -0
  12. data/lib/weft/category.rb +157 -0
  13. data/lib/weft/coding.rb +355 -0
  14. data/lib/weft/document.rb +118 -0
  15. data/lib/weft/filters.rb +243 -0
  16. data/lib/weft/wxgui.rb +687 -0
  17. data/lib/weft/wxgui/category.xpm +26 -0
  18. data/lib/weft/wxgui/dialogs.rb +128 -0
  19. data/lib/weft/wxgui/document.xpm +25 -0
  20. data/lib/weft/wxgui/error_handler.rb +52 -0
  21. data/lib/weft/wxgui/inspectors.rb +361 -0
  22. data/lib/weft/wxgui/inspectors/category.rb +165 -0
  23. data/lib/weft/wxgui/inspectors/codereview.rb +275 -0
  24. data/lib/weft/wxgui/inspectors/document.rb +139 -0
  25. data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -0
  26. data/lib/weft/wxgui/inspectors/script.rb +35 -0
  27. data/lib/weft/wxgui/inspectors/search.rb +265 -0
  28. data/lib/weft/wxgui/inspectors/textcontrols.rb +304 -0
  29. data/lib/weft/wxgui/lang.rb +17 -0
  30. data/lib/weft/wxgui/lang/en.rb +45 -0
  31. data/lib/weft/wxgui/mondrian.xpm +44 -0
  32. data/lib/weft/wxgui/search.xpm +25 -0
  33. data/lib/weft/wxgui/sidebar.rb +498 -0
  34. data/lib/weft/wxgui/utilities.rb +148 -0
  35. data/lib/weft/wxgui/weft16.xpm +31 -0
  36. data/lib/weft/wxgui/workarea.rb +249 -0
  37. data/test/001-document.rb +196 -0
  38. data/test/002-category.rb +138 -0
  39. data/test/003-code.rb +370 -0
  40. data/test/004-application.rb +52 -0
  41. data/test/006-filters.rb +139 -0
  42. data/test/009a-backend_sqlite_basic.rb +280 -0
  43. data/test/009b-backend_sqlite_complex.rb +175 -0
  44. data/test/009c_backend_sqlite_bench.rb +81 -0
  45. data/test/010-backend_nudist.rb +5 -0
  46. data/test/all-tests.rb +1 -0
  47. data/test/manual-gui-script.txt +24 -0
  48. data/test/testdata/autocoding-test.txt +15 -0
  49. data/test/testdata/iso-8859-1.txt +5 -0
  50. data/test/testdata/sample_doc.txt +19 -0
  51. data/test/testdata/search_results.txt +1254 -0
  52. data/test/testdata/text1-dos-ascii.txt +2 -0
  53. data/test/testdata/text1-unix-utf8.txt +2 -0
  54. data/weft-qda.rb +28 -0
  55. metadata +96 -0
@@ -0,0 +1,138 @@
1
+ require 'english'
2
+
3
+ $:.push('../lib/')
4
+
5
+ require 'weft/category'
6
+ require 'test/unit'
7
+
8
+ class TestDocument < Test::Unit::TestCase
9
+ def setup
10
+
11
+ end
12
+
13
+ def test_basic
14
+ c = QDA::Category.new('The Title',nil)
15
+ assert_equal('The Title', c.name,
16
+ 'Set title at initialisation')
17
+ assert_equal(0, c.children.length,
18
+ 'Empty at initialisation')
19
+
20
+ c2 = QDA::Category.new('Child Node',c)
21
+ assert_equal(1, c.children.length,
22
+ 'Added a child')
23
+ end
24
+
25
+ def test_memo
26
+ c = QDA::Category.new('The Title',nil, 'My Memo')
27
+ assert_equal('My Memo', c.memo,
28
+ 'Set memo at initialisation')
29
+
30
+ c2 = QDA::Category.new('Child Node',c)
31
+ assert_equal('', c2.memo,
32
+ 'Default memo = ""')
33
+
34
+ c2.memo = 'Foo'
35
+ assert_equal('Foo', c2.memo,
36
+ 'Set memo with accessor')
37
+ end
38
+
39
+ def test_code
40
+ a = QDA::Code.new(1, 0, 5)
41
+ assert( a.include?(0), "Code#include?")
42
+ assert( a.include?(1), "Code#include?")
43
+ assert( a.include?(4), "Code#include?")
44
+ assert_equal(false, a.include?(5), "Code#include?")
45
+ assert_equal(false, a.include?(9), "Code#include?")
46
+ end
47
+
48
+ def test_do_code
49
+ c = QDA::Category.new('The Title',nil)
50
+ # create an initial code
51
+ c.code(2, 0, 20)
52
+ assert_equal(1, c.num_of_docs,
53
+ 'Have coded one document')
54
+ assert_equal(1, c.num_of_codes,
55
+ 'Have added one code')
56
+ assert_equal(0, c.codes[2][0].offset,
57
+ 'Have set code offset')
58
+ assert_equal(20, c.codes[2][0].length,
59
+ 'Have set code length')
60
+ assert_equal(20, c.num_of_chars,
61
+ 'Have calculated total chars')
62
+
63
+ # create a separate code of the same document
64
+ c.code(2, 90, 10)
65
+ assert_equal(1, c.num_of_docs,
66
+ 'Have still coded one document')
67
+ assert_equal(2, c.num_of_codes,
68
+ 'Have added one code')
69
+ assert_equal(30, c.num_of_chars,
70
+ 'Have increased total chars')
71
+
72
+ # code a region which partiall overlaps and extends the first code
73
+ c.code(2, 10, 15)
74
+ assert_equal(1, c.num_of_docs,
75
+ 'Have done joint coding')
76
+ assert_equal(2, c.num_of_codes,
77
+ 'Have done joint coding')
78
+ assert_kind_of(QDA::Code, c.codes[2][0],
79
+ 'Have done joint one code')
80
+ assert_equal(0, c.codes[2][0].offset,
81
+ 'Have updated code offset')
82
+ assert_equal(25, c.codes[2][0].length,
83
+ 'Have updated code length')
84
+ assert_equal(35, c.num_of_chars,
85
+ 'Have increased total chars')
86
+
87
+ # code another region that is distinct and between the first two
88
+ c.code(2, 30, 30)
89
+ assert_equal(1, c.num_of_docs,
90
+ 'Have done discrete coding')
91
+ assert_equal(3, c.num_of_codes,
92
+ 'Have done discrete coding')
93
+ assert_kind_of(QDA::Code, c.codes[2][1],
94
+ 'Have done discrete one code')
95
+ assert_equal(30, c.codes[2][1].offset,
96
+ 'Have updated code offset')
97
+ assert_equal(30, c.codes[2][1].length,
98
+ 'Have updated code length')
99
+ assert_equal(65, c.num_of_chars,
100
+ 'Have increased total chars')
101
+
102
+ # code another document
103
+ c.code(1, 120, 100)
104
+ assert_equal(2, c.num_of_docs,
105
+ 'Have increased documents with new document')
106
+ assert_equal(4, c.num_of_codes,
107
+ 'Have increased codes with new document')
108
+
109
+ # uncode a portion of first code
110
+ c.uncode(2, 5, 15)
111
+ assert_equal(2, c.num_of_docs,
112
+ 'Have done split uncoding')
113
+ assert_equal(4, c.codes[2].length,
114
+ 'Have done split uncoding')
115
+ assert_kind_of(QDA::Code, c.codes[2][0],
116
+ 'Have done split uncode')
117
+ assert_equal(0, c.codes[2][0].offset,
118
+ 'Have updated code offset')
119
+ assert_equal(5, c.codes[2][0].length,
120
+ 'Have updated code length')
121
+
122
+ assert_kind_of(QDA::Code, c.codes[2][1],
123
+ 'Have done split uncode')
124
+ assert_equal(20, c.codes[2][1].offset,
125
+ 'Have updated code offset')
126
+ assert_equal(5, c.codes[2][1].length,
127
+ 'Have updated code length')
128
+
129
+ assert_kind_of(QDA::Code, c.codes[2][2],
130
+ 'Have done split uncode')
131
+ assert_equal(30, c.codes[2][2].offset,
132
+ 'Have updated code offset')
133
+ assert_equal(30, c.codes[2][2].length,
134
+ 'Have updated code length')
135
+
136
+ # more tests ? ... seems to be all OK, not
137
+ end
138
+ end
data/test/003-code.rb ADDED
@@ -0,0 +1,370 @@
1
+ require 'english'
2
+
3
+ $:.push('../lib/')
4
+
5
+ require 'weft'
6
+ require 'test/unit'
7
+
8
+ class TestCode < Test::Unit::TestCase
9
+ include QDA
10
+
11
+ def setup
12
+
13
+ end
14
+
15
+ def test_basic
16
+ c_1 = Code.new(3, 0, 4)
17
+ assert_equal(3, c_1.docid,
18
+ 'Set docid at initialization')
19
+ assert_equal(0, c_1.offset,
20
+ 'Set offset at initialization')
21
+ assert_equal(4, c_1.length,
22
+ 'Set length at initialization')
23
+
24
+ c_1 << Code.new(3, 4, 5)
25
+ assert_equal(0, c_1.offset,
26
+ '<< other')
27
+ assert_equal(9, c_1.length,
28
+ '<< other.length')
29
+
30
+ end
31
+
32
+ def test_basic_codeset
33
+ # this shouldn work
34
+ q = CodeSet[ '11' ]
35
+ assert_kind_of(CodeSet, q)
36
+
37
+ assert_raises(ArgumentError) { CodeSet.new([ '11' ]) }
38
+ # boring constructor
39
+ q = CodeSet.new [ [4, 5, 6] ]
40
+ assert_equal(1, q.length)
41
+ assert_kind_of(Code, q[0])
42
+ c = q[0]
43
+ assert_equal(4, c.docid)
44
+ assert_equal(5, c.offset)
45
+ assert_equal(6, c.length)
46
+ end
47
+
48
+ def test_addition
49
+ c_1 = Code.new(3, 0, 3)
50
+ c_2 = Code.new(3, 2, 4)
51
+ c_3 = c_1 + c_2
52
+ assert_equal(6, c_3.length,
53
+ 'Overlapping addition')
54
+ assert_equal(0, c_3.offset,
55
+ 'Overlapping offset of first element')
56
+
57
+ c_1 = Code.new(3, 2, 6)
58
+ c_2 = Code.new(3, 2, 6)
59
+ c_3 = c_1 + c_2
60
+ assert_equal(6, c_3.length,
61
+ 'Identical addition')
62
+ assert_equal(2, c_3.offset,
63
+ 'Identical addition offset of first element')
64
+
65
+ c_1 = Code.new(3, 2, 6)
66
+ c_2 = Code.new(3, 2, 4)
67
+ c_3 = c_1 + c_2
68
+ assert_equal(6, c_3.length,
69
+ 'Identical addition')
70
+ assert_equal(2, c_3.offset,
71
+ 'Identical addition offset of first element')
72
+
73
+
74
+
75
+ c_1 = Code.new(3, 0, 2)
76
+ c_2 = Code.new(3, 4, 4)
77
+ c_3 = c_1 + c_2
78
+ assert_equal(2, c_3.length,
79
+ 'Disjoint addition')
80
+ assert_equal(0, c_3[0].offset,
81
+ 'Disjoint addition first element offset')
82
+ assert_equal(2, c_3[0].length,
83
+ 'Disjoint addition first element length')
84
+ assert_equal(4, c_3[1].offset,
85
+ 'Disjoint addition second element offset')
86
+ assert_equal(4, c_3[1].length,
87
+ 'Disjoint addition second element length')
88
+
89
+ c_1 = Code.new(3, 0, 2)
90
+ c_2 = Code.new(3, 4, 4)
91
+ c_3 = c_2 + c_1
92
+ assert_equal(2, c_3.length,
93
+ 'Disjoint addition')
94
+ assert_equal(0, c_3[0].offset,
95
+ 'Disjoint addition first element offset')
96
+ assert_equal(2, c_3[0].length,
97
+ 'Disjoint addition first element length')
98
+ assert_equal(4, c_3[1].offset,
99
+ 'Disjoint addition second element offset')
100
+ assert_equal(4, c_3[1].length,
101
+ 'Disjoint addition second element length')
102
+
103
+ end
104
+
105
+
106
+ def test_comparison
107
+ c_1 = Code.new(3, 0, 7)
108
+ c_2 = Code.new(3, 0, 7)
109
+ c_3 = Code.new(3, 5, 7)
110
+ c_4 = Code.new(3, 0, 19)
111
+ assert(c_1 == c_1, 'Identity Equality')
112
+ assert(c_1 == c_2, 'Equivalence Equality')
113
+ assert(c_1 != c_3, 'Offset inequality')
114
+ assert(c_1 != c_4, 'Length inequality')
115
+
116
+ c_1 = Code.new(3, 0, 7)
117
+ c_2 = Code.new(3, 2, 9)
118
+ assert(c_1.overlap?(c_2), 'Overlap 1/2')
119
+ assert(c_2.overlap?(c_1), 'Overlap 1/1')
120
+ assert(c_1.touch?(c_2), 'Touch 1/1')
121
+ assert(c_2.touch?(c_1), 'Touch 1/2')
122
+
123
+ c_1 = Code.new(3, 0, 7)
124
+ c_2 = Code.new(3, 7, 9)
125
+ assert(! c_1.overlap?(c_2), 'Overlap 2/2')
126
+ assert(! c_2.overlap?(c_1), 'Overlap 2/1')
127
+ assert(c_1.touch?(c_2), 'Touch 2/1')
128
+ assert(c_2.touch?(c_1), 'Touch 2/2')
129
+
130
+ c_1 = Code.new(3, 0, 7)
131
+ c_2 = Code.new(3, 8, 9)
132
+ assert(! c_1.overlap?(c_2), 'Overlap 3/2')
133
+ assert(! c_2.overlap?(c_1), 'Overlap 3/1')
134
+ assert(! c_1.touch?(c_2), 'Touch 3/1')
135
+ assert(! c_2.touch?(c_1), 'Touch 3/2')
136
+
137
+ end
138
+
139
+ def test_subtraction
140
+ c_1 = Code.new(3, 0, 7)
141
+ c_2 = Code.new(3, 2, 2)
142
+ c_3 = c_1 - c_2
143
+ assert_equal(2, c_3.length,
144
+ 'Disjoint subtraction')
145
+ assert_equal(0, c_3[0].offset,
146
+ 'Disjoint subtraction first element offset')
147
+ assert_equal(2, c_3[0].length,
148
+ 'Disjoint subtraction first element length')
149
+ assert_equal(4, c_3[1].offset,
150
+ 'Disjoint subtraction second element offset')
151
+ assert_equal(3, c_3[1].length,
152
+ 'Disjoint subtraction second element length')
153
+
154
+ c_1 = Code.new(3, 0, 4)
155
+ c_2 = Code.new(3, 2, 2)
156
+ c_3 = c_1 - c_2
157
+ assert_equal(1, c_3.length,
158
+ 'Tail subtraction')
159
+ assert_equal(0, c_3[0].offset,
160
+ 'Tail subtraction first element offset')
161
+ assert_equal(2, c_3[0].length,
162
+ 'Tail subtraction first element length')
163
+
164
+ c_1 = Code.new(3, 2, 4)
165
+ c_2 = Code.new(3, 0, 4)
166
+ c_3 = c_1 - c_2
167
+ assert_equal(1, c_3.length,
168
+ 'Head subtraction')
169
+ assert_equal(4, c_3[0].offset,
170
+ 'Tail subtraction first element offset')
171
+ assert_equal(2, c_3[0].length,
172
+ 'Tail subtraction first element length')
173
+
174
+ c_1 = Code.new(3, 12, 10)
175
+ c_2 = Code.new(3, 10, 20)
176
+ c_3 = c_1 - c_2
177
+ assert_equal(0, c_3.length,
178
+ 'Erase subtraction')
179
+
180
+ c_1 = Code.new(3, 5, 5)
181
+ c_2 = Code.new(3, 10, 20)
182
+ c_3 = c_1 - c_2
183
+ assert_equal(1, c_3.length,
184
+ 'Null subtraction')
185
+ assert_equal(5, c_3[0].offset,
186
+ 'Null subtraction first element offset')
187
+ assert_equal(5, c_3[0].length,
188
+ 'Null subtraction first element length')
189
+
190
+ c_1 = Code.new(3, 30, 30)
191
+ c_2 = Code.new(3, 5, 15)
192
+ c_3 = c_1 - c_2
193
+ assert_equal(1, c_3.length,
194
+ 'Null subtraction')
195
+ assert_equal(30, c_3[0].offset,
196
+ 'Null subtraction first element offset')
197
+ assert_equal(30, c_3[0].length,
198
+ 'Null subtraction first element length')
199
+
200
+ end
201
+
202
+ def test_intersection
203
+ c_1 = Code.new(3, 0, 7)
204
+ c_2 = Code.new(3, 2, 2)
205
+ c_3 = c_1 % c_2
206
+ assert_equal(2, c_3.length,
207
+ 'Island intersection')
208
+ assert_equal(2, c_3.offset,
209
+ 'Island intersection')
210
+
211
+ c_1 = Code.new(3, 5, 8)
212
+ c_2 = Code.new(3, 8, 20)
213
+ c_3 = c_1 % c_2
214
+ assert_equal(5, c_3.length,
215
+ 'Overlap intersection')
216
+ assert_equal(8, c_3.offset,
217
+ 'overlap intersection')
218
+
219
+ c_1 = Code.new(3, 0, 5)
220
+ c_2 = Code.new(3, 10, 5)
221
+ c_3 = c_1 % c_2
222
+ assert_nil(c_3, 'Disjoint intersection')
223
+
224
+ c_1 = Code.new(3, 0, 5)
225
+ c_2 = Code.new(3, 5, 10)
226
+ c_3 = c_1 % c_2
227
+ assert_nil(c_3, 'Adjacent intersection')
228
+ end
229
+
230
+ def test_set_intersect
231
+ cs_2 = CodeSet[ Code.new(3, 0, 5),
232
+ Code.new(3, 10, 10) ]
233
+
234
+ cs_1 = CodeSet[ Code.new(3, 3, 9) ]
235
+
236
+ intersect = cs_1.intersect(cs_2)
237
+
238
+ assert_equal(Code.new(3, 3, 2), intersect[0],
239
+ 'Overlap intersection')
240
+
241
+ assert_equal(Code.new(3, 10, 2), intersect[1],
242
+ 'Overlap intersection')
243
+
244
+ cs_2 = CodeSet[ Code.new(3, 2, 7) ]
245
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
246
+
247
+ intersect = cs_1.intersect(cs_2)
248
+ assert([], 'Adjacent intersection')
249
+
250
+ cs_2 = CodeSet[ Code.new(3, 0, 4) ]
251
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
252
+ intersect = cs_1.intersect(cs_2)
253
+ assert([], 'Disjoint intersection')
254
+ end
255
+
256
+ def test_set_union
257
+ cs_2 = CodeSet[ Code.new(3, 0, 5),
258
+ Code.new(3, 10, 10)]
259
+
260
+ cs_1 = CodeSet[ Code.new(3, 3, 9) ]
261
+
262
+ union = cs_1.union(cs_2)
263
+ assert_equal(Code.new(3, 0, 20), union[0],
264
+ 'Overlap set union')
265
+
266
+ cs_2 = CodeSet[ Code.new(3, 2, 7) ]
267
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
268
+ union = cs_1.union(cs_2)
269
+
270
+ assert_equal(Code.new(3, 2, 12), union[0],
271
+ 'Adjacent set union')
272
+
273
+ cs_2 = CodeSet[ Code.new(3, 0, 4), Code.new(3, 20, 8) ]
274
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
275
+ union = cs_1.union(cs_2)
276
+
277
+ assert_equal(Code.new(3, 0, 4), union[0],
278
+ 'Disjoint set union')
279
+ assert_equal(Code.new(3, 9, 5), union[1],
280
+ 'Disjoint set union')
281
+ assert_equal(Code.new(3, 20, 8), union[2],
282
+ 'Disjoint set union')
283
+ end
284
+
285
+ def test_set_exclude
286
+ cs_2 = CodeSet[ Code.new(3, 0, 5), Code.new(3, 10, 10) ]
287
+ cs_1 = CodeSet[ Code.new(3, 3, 9), Code.new(3, 25, 5) ]
288
+
289
+ exclusion = cs_2.exclude(cs_1)
290
+ assert_equal(Code.new(3, 0, 3), exclusion[0],
291
+ 'Overlap set exclusion')
292
+ assert_equal(Code.new(3, 12, 8), exclusion[1],
293
+ 'Overlap set exclusion')
294
+
295
+ exclusion = cs_1.exclude(cs_2)
296
+ assert_equal(Code.new(3, 5, 5), exclusion[0],
297
+ 'Overlap set exclusion')
298
+ assert_equal(Code.new(3, 25, 5), exclusion[1],
299
+ 'Overlap set exclusion')
300
+
301
+ cs_2 = CodeSet[ Code.new(3, 2, 7) ]
302
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
303
+
304
+
305
+ exclusion = cs_2.exclude(cs_1)
306
+ assert_equal(Code.new(3, 2, 7), exclusion[0],
307
+ 'Adjacent set exclusion')
308
+
309
+ cs_2 = CodeSet[ Code.new(3, 0, 4), Code.new(3, 22, 8) ]
310
+ cs_1 = CodeSet[ Code.new(3, 9, 5) ]
311
+
312
+ exclusion = cs_2.exclude(cs_1)
313
+
314
+ assert_equal(Code.new(3, 0, 4), exclusion[0],
315
+ 'Disjoint set exclusion')
316
+ assert_equal(Code.new(3, 22, 8), exclusion[1],
317
+ 'Disjoint set exclusion')
318
+ end
319
+
320
+ def test_code_plus_fragment
321
+ fs = CodeSet[ Fragment.new('weft is nice', 'title', 5, 1) ]
322
+ cs = CodeSet[ Code.new(1, 13, 8) ]
323
+
324
+ isect = fs.intersect(cs)
325
+ first = isect[0]
326
+ assert_kind_of(Code, first)
327
+ assert_equal(13, first.offset)
328
+ assert_equal(4, first.length)
329
+ assert_equal(1, first.docid)
330
+ end
331
+
332
+ def test_codetable
333
+ tbl = CodingTable.new()
334
+ tbl.add( Code.new(3, 5, 6) )
335
+ assert_equal(1, tbl.num_of_docs)
336
+ assert_equal(6, tbl.num_of_chars)
337
+ assert_equal(1, tbl.num_of_codes)
338
+ end
339
+
340
+ def test_fragment_table
341
+ ft = FragmentTable.new()
342
+ ft.add( Fragment.new('the text', 'title', 6, 2) )
343
+
344
+ assert_equal(1, ft.num_of_docs)
345
+ assert_equal(8, ft.num_of_chars)
346
+ assert_equal(1, ft.num_of_codes)
347
+ ft.each_title do | title, codes |
348
+ assert_equal('title', title)
349
+ assert_equal(1, codes.length)
350
+ end
351
+
352
+
353
+ ct = ft.to_codingtable()
354
+ assert_equal(1, ct.num_of_docs)
355
+ assert_equal(8, ct.num_of_chars)
356
+ assert_equal(1, ct.num_of_codes)
357
+ assert_equal(Code, ct[2][0].class)
358
+ end
359
+
360
+ def test_codetable_plus_fragmenttable
361
+ ft = FragmentTable.new()
362
+ ft.add( Fragment.new('the text', 'title', 6, 2) )
363
+ ct = CodingTable.new()
364
+ ct.add( Code.new(2, 8, 10) )
365
+
366
+ out = ft.dup
367
+ ft.merge(ct)
368
+ ft.add( Fragment.new('something', 'xxx', 10, 1) )
369
+ end
370
+ end