weft-qda 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/weft.rb +21 -0
- data/lib/weft/WEFT-VERSION-STRING.rb +1 -0
- data/lib/weft/application.rb +130 -0
- data/lib/weft/backend.rb +39 -0
- data/lib/weft/backend/marshal.rb +26 -0
- data/lib/weft/backend/mysql.rb +267 -0
- data/lib/weft/backend/n6.rb +366 -0
- data/lib/weft/backend/sqlite.rb +633 -0
- data/lib/weft/backend/sqlite/category_tree.rb +104 -0
- data/lib/weft/backend/sqlite/schema.rb +152 -0
- data/lib/weft/backend/sqlite/upgradeable.rb +55 -0
- data/lib/weft/category.rb +157 -0
- data/lib/weft/coding.rb +355 -0
- data/lib/weft/document.rb +118 -0
- data/lib/weft/filters.rb +243 -0
- data/lib/weft/wxgui.rb +687 -0
- data/lib/weft/wxgui/category.xpm +26 -0
- data/lib/weft/wxgui/dialogs.rb +128 -0
- data/lib/weft/wxgui/document.xpm +25 -0
- data/lib/weft/wxgui/error_handler.rb +52 -0
- data/lib/weft/wxgui/inspectors.rb +361 -0
- data/lib/weft/wxgui/inspectors/category.rb +165 -0
- data/lib/weft/wxgui/inspectors/codereview.rb +275 -0
- data/lib/weft/wxgui/inspectors/document.rb +139 -0
- data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -0
- data/lib/weft/wxgui/inspectors/script.rb +35 -0
- data/lib/weft/wxgui/inspectors/search.rb +265 -0
- data/lib/weft/wxgui/inspectors/textcontrols.rb +304 -0
- data/lib/weft/wxgui/lang.rb +17 -0
- data/lib/weft/wxgui/lang/en.rb +45 -0
- data/lib/weft/wxgui/mondrian.xpm +44 -0
- data/lib/weft/wxgui/search.xpm +25 -0
- data/lib/weft/wxgui/sidebar.rb +498 -0
- data/lib/weft/wxgui/utilities.rb +148 -0
- data/lib/weft/wxgui/weft16.xpm +31 -0
- data/lib/weft/wxgui/workarea.rb +249 -0
- data/test/001-document.rb +196 -0
- data/test/002-category.rb +138 -0
- data/test/003-code.rb +370 -0
- data/test/004-application.rb +52 -0
- data/test/006-filters.rb +139 -0
- data/test/009a-backend_sqlite_basic.rb +280 -0
- data/test/009b-backend_sqlite_complex.rb +175 -0
- data/test/009c_backend_sqlite_bench.rb +81 -0
- data/test/010-backend_nudist.rb +5 -0
- data/test/all-tests.rb +1 -0
- data/test/manual-gui-script.txt +24 -0
- data/test/testdata/autocoding-test.txt +15 -0
- data/test/testdata/iso-8859-1.txt +5 -0
- data/test/testdata/sample_doc.txt +19 -0
- data/test/testdata/search_results.txt +1254 -0
- data/test/testdata/text1-dos-ascii.txt +2 -0
- data/test/testdata/text1-unix-utf8.txt +2 -0
- data/weft-qda.rb +28 -0
- metadata +96 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
module QDA::GUI
|
2
|
+
# ItemData - allows widgets to store information about assocations
|
3
|
+
# with underlying project objects such as categories and documents.
|
4
|
+
module ItemData
|
5
|
+
# for faked-up item data
|
6
|
+
def get_item_data(id)
|
7
|
+
@data_table[id]
|
8
|
+
end
|
9
|
+
alias :get_client_data :get_item_data
|
10
|
+
|
11
|
+
# for faked-up item data
|
12
|
+
def set_item_data(id, data)
|
13
|
+
@data_table ||= Hash.new()
|
14
|
+
@data_table[id] = data
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
alias :set_client_data :set_item_data
|
18
|
+
|
19
|
+
# returns the key associated with the value +value+
|
20
|
+
def value_to_ident(value)
|
21
|
+
return nil if value.nil?
|
22
|
+
unless value.respond_to?(:dbid)
|
23
|
+
Kernel.raise "Cannot search for invalid value #{value.inspect}"
|
24
|
+
end
|
25
|
+
doc = @data_table.each do | k, val |
|
26
|
+
return k if val.dbid == value.dbid
|
27
|
+
end
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Allows an object such as the global Wx App to broadcast messages
|
34
|
+
# about changes in the database and other application states that
|
35
|
+
# might require the child to update its appearance.
|
36
|
+
module Broadcaster
|
37
|
+
SUBSCRIBABLE_EVENTS = [ :document_added, :document_changed,
|
38
|
+
:document_deleted, :category_added, :category_changed,
|
39
|
+
:category_deleted, :focus_category, :savestate_changed ]
|
40
|
+
|
41
|
+
# intended to be module private variables to managed which widgets
|
42
|
+
# are subscribing to which events
|
43
|
+
attr_accessor :bc__subscribers, :bc__subscriptions
|
44
|
+
|
45
|
+
# broadcast an event to all subscribers to event of type
|
46
|
+
# +event_type+, optionally including +content+
|
47
|
+
def broadcast(event_type, content = nil)
|
48
|
+
for subscriber in bc__subscriptions[event_type]
|
49
|
+
subscriber.notify(event_type, content)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# subscribe the widget +subscriber+ to receive notification of
|
54
|
+
# app event types +*events+ (which should be a set of symbols)
|
55
|
+
def add_subscriber(subscriber, *events)
|
56
|
+
events.each do | e |
|
57
|
+
bc__subscriptions[e].push(subscriber)
|
58
|
+
end
|
59
|
+
subscriber.evt_close do | e |
|
60
|
+
delete_subscriber(subscriber)
|
61
|
+
e.skip()
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# accepts a ruby item
|
66
|
+
def delete_subscriber(subscriber)
|
67
|
+
if subscriber.respond_to?(:associated_subscribers)
|
68
|
+
subscriber.associated_subscribers.each do | child |
|
69
|
+
delete_subscriber(child)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
bc__subscribers.delete(subscriber)
|
73
|
+
bc__subscriptions.each_value do | subscriber_set |
|
74
|
+
subscriber_set.delete(subscriber)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def Broadcaster.extended(obj)
|
79
|
+
$wxapp = obj
|
80
|
+
# this is where module-private variables would be great ..
|
81
|
+
obj.reset_subscriptions()
|
82
|
+
end
|
83
|
+
|
84
|
+
def reset_subscriptions()
|
85
|
+
self.bc__subscribers = []
|
86
|
+
self.bc__subscriptions = Hash.new do | ev |
|
87
|
+
raise "Cannot subscribe to unknown event #{ev}"
|
88
|
+
end
|
89
|
+
SUBSCRIBABLE_EVENTS.each { | ev | self.bc__subscriptions[ev] = [] }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Any gui element that may need to modify its appearance in response
|
94
|
+
# to application updates may include the subscriber module. Instances
|
95
|
+
# of the class may then call the +subscribe+ method to receive
|
96
|
+
# notification of changes.
|
97
|
+
module Subscriber
|
98
|
+
# receive notification that an event of type +ev+ has been called,
|
99
|
+
# optionally passing +content+ for additional information about the
|
100
|
+
# object the event concerned.
|
101
|
+
def notify(ev, content = nil)
|
102
|
+
receiver = "receive_#{ev}".intern
|
103
|
+
if respond_to?(receiver)
|
104
|
+
send(receiver, content)
|
105
|
+
else
|
106
|
+
warn "#{self} received unhandled event #{ev}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Subscribe this object to the events +events+, which should be a
|
111
|
+
# list of symbols. For example, to receive notification of category
|
112
|
+
# changes and additions, the recipient shoudl call
|
113
|
+
# subscribe(:category_changed, :category_added)
|
114
|
+
#
|
115
|
+
# For every event type to which a subscriber subscribes, it should
|
116
|
+
# implement a corresponding receive_xxx method, where xxx is the
|
117
|
+
# name of the event type. To act upon category changes, the
|
118
|
+
# subscriber should implement +receive_category_changed+
|
119
|
+
def subscribe(*events)
|
120
|
+
$wxapp.add_subscriber(self, *events)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# extension to Wx::Colour
|
125
|
+
class Wx::Colour
|
126
|
+
# Create a new colour by mixing +self_parts+ of +self+ with
|
127
|
+
# +other_parts+ of other. The new colour is produced by applying the
|
128
|
+
# following averaging formula to the red, green and blue components
|
129
|
+
# of each colour
|
130
|
+
#
|
131
|
+
# ( colour_1 * conc_1 ) + ( colour_2 * conc_2 )
|
132
|
+
# --------------------------
|
133
|
+
# conc_1 + conc_2
|
134
|
+
#
|
135
|
+
|
136
|
+
def mix(other, self_parts = 1, other_parts = 1)
|
137
|
+
return self if self_parts.zero? && other_parts.zero?
|
138
|
+
rgb = [ :red, :green, :blue ].map do | component |
|
139
|
+
mix = self.send(component) * self_parts
|
140
|
+
mix += other.send(component) * other_parts
|
141
|
+
mix = mix.to_f
|
142
|
+
mix /= self_parts + other_parts
|
143
|
+
mix.to_i
|
144
|
+
end
|
145
|
+
Wx::Colour.new(*rgb)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
/* XPM */
|
2
|
+
static char *weft16[] = {
|
3
|
+
/* columns rows colors chars-per-pixel */
|
4
|
+
"16 16 9 1",
|
5
|
+
" c #400000",
|
6
|
+
". c #CCCC00",
|
7
|
+
"X c #E9E991",
|
8
|
+
"o c #999900",
|
9
|
+
"O c gray100",
|
10
|
+
"+ c black",
|
11
|
+
"@ c black",
|
12
|
+
"# c black",
|
13
|
+
"$ c None",
|
14
|
+
/* pixels */
|
15
|
+
"$$$$$ $$$ $$$$",
|
16
|
+
"$$$$ XX $ XX $$$",
|
17
|
+
"$$$$ .XX XX. $$$",
|
18
|
+
"$$ $ .XX o $ $$",
|
19
|
+
"$ XX $ .XX $ X $",
|
20
|
+
"$ XX. o .X. XX. ",
|
21
|
+
"$$ . XXo . XX. $",
|
22
|
+
"$$$ XX. $ XX. $$",
|
23
|
+
"$$ XX. o XX. o $",
|
24
|
+
"$ XX. o.X . o.X ",
|
25
|
+
" XX. $ XXX $ X $",
|
26
|
+
" X. $ o XXX $ $$",
|
27
|
+
"$ $ XXo XXX $$$",
|
28
|
+
"$$$ XX. $ XX $$$",
|
29
|
+
"$$$ X. $$$ $$$$",
|
30
|
+
"$$$$ $$$$$$$$$$"
|
31
|
+
};
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module QDA::GUI
|
2
|
+
# The main application window, where docuemnts, categories, searches
|
3
|
+
# etc are displayed. Only one instance per application.
|
4
|
+
class WorkArea < Wx::MDIParentFrame
|
5
|
+
include Subscriber
|
6
|
+
|
7
|
+
attr_accessor :app
|
8
|
+
def initialize(wx_app)
|
9
|
+
@wx_app = wx_app
|
10
|
+
@window_map = {}
|
11
|
+
super(nil, -1, "Weft QDA")
|
12
|
+
restore_size()
|
13
|
+
subscribe(:savestate_changed)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Resizes the workarea to the state when the application was last used
|
17
|
+
def restore_size()
|
18
|
+
conf = Wx::ConfigBase::get()
|
19
|
+
conf.path = "/MainFrame"
|
20
|
+
x = conf.read_int("x", 200)
|
21
|
+
y = conf.read_int("y", 0)
|
22
|
+
w = conf.read_int("w", 300)
|
23
|
+
h = conf.read_int("h", 400)
|
24
|
+
max = conf.read_bool("max", false)
|
25
|
+
|
26
|
+
if max
|
27
|
+
maximize(true)
|
28
|
+
else
|
29
|
+
move( Wx::Point.new(x, y) )
|
30
|
+
set_size( Wx::Size.new(w, h) )
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# save layout to Config so window is same position next time
|
35
|
+
def remember_size()
|
36
|
+
conf = Wx::ConfigBase::get()
|
37
|
+
# global window size settings
|
38
|
+
if conf
|
39
|
+
size = get_size()
|
40
|
+
pos = get_position
|
41
|
+
conf.path = '/MainFrame'
|
42
|
+
conf.write("x", pos.x)
|
43
|
+
conf.write("y", pos.y)
|
44
|
+
conf.write("w", size.width)
|
45
|
+
conf.write("h", size.height)
|
46
|
+
conf.write("max", is_maximized)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns a Wx::Size object that has a width equal to that of the
|
51
|
+
# current window times +w+, and a height equal to that of the
|
52
|
+
# current window times +h+. This is used to help size MDI children
|
53
|
+
# when they are first created.
|
54
|
+
def proportional_size(w, h)
|
55
|
+
size = client_size()
|
56
|
+
Wx::Size.new( ( size.get_width * w ).to_i,
|
57
|
+
( size.get_height * h ).to_i )
|
58
|
+
end
|
59
|
+
|
60
|
+
# launch a child window of the type +klass+ (which should be a
|
61
|
+
# constant of the name of a subclass of QDA::GUI::InspectorWindow),
|
62
|
+
# optionally bound to the database object +bound_obj+ (typically a
|
63
|
+
# document or category).
|
64
|
+
# Returns a reference to the newly opened inspector window.
|
65
|
+
def launch_window(klass, bound_obj)
|
66
|
+
win = nil
|
67
|
+
Wx::BusyCursor.busy(self) do
|
68
|
+
if win = get_open_window(bound_obj)
|
69
|
+
win.activate()
|
70
|
+
else
|
71
|
+
w_i = window_ident(bound_obj)
|
72
|
+
last_layout = restore_layout( w_i )
|
73
|
+
win = klass.new( bound_obj, @wx_app, self, last_layout )
|
74
|
+
@window_map[w_i] = win
|
75
|
+
|
76
|
+
# set up a hook to clean up when this window is closed later
|
77
|
+
win.evt_close() do | e |
|
78
|
+
remember_layout(w_i, win)
|
79
|
+
@window_map.delete(w_i)
|
80
|
+
win.hide()
|
81
|
+
e.skip()
|
82
|
+
end
|
83
|
+
win.activate()
|
84
|
+
end
|
85
|
+
end
|
86
|
+
# should go over D & C window now
|
87
|
+
self.raise()
|
88
|
+
return win
|
89
|
+
end
|
90
|
+
|
91
|
+
def set_active_category(category)
|
92
|
+
@window_map.each do | name, win |
|
93
|
+
if win.respond_to?(:set_active_category)
|
94
|
+
win.set_active_category(category)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# turns a QDA object into a string window identifier a bit messy b/c
|
100
|
+
# has to deal with Queries (which aren't currently saved in
|
101
|
+
# database) and categories and documents (which are).
|
102
|
+
def window_ident(bound_obj)
|
103
|
+
bound_obj_id = bound_obj.dbid ? bound_obj.dbid : ''
|
104
|
+
# deal with either full or partial class names
|
105
|
+
bound_obj.class.name.sub(/^.*\:/, '') << bound_obj_id.to_s
|
106
|
+
end
|
107
|
+
private :window_ident
|
108
|
+
|
109
|
+
# gets the open MDI window representing +bound_obj+, if there is one
|
110
|
+
def get_open_window(bound_obj)
|
111
|
+
@window_map[ window_ident(bound_obj) ]
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def get_open_windows(type_of)
|
116
|
+
if type_of == QDA::Document
|
117
|
+
doc_wins = @window_map.keys.grep(/^doc-/)
|
118
|
+
return @window_map.values_at( *doc_wins )
|
119
|
+
elsif type_of == QDA::Category
|
120
|
+
cat_wins = @window_map.keys.grep(/^cat-/)
|
121
|
+
return @window_map.values_at( *cat_wins )
|
122
|
+
else
|
123
|
+
return []
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# remember how the windows were arranged
|
128
|
+
def remember_layouts()
|
129
|
+
# project specific settings
|
130
|
+
if @app
|
131
|
+
@window_map.each do | key, window |
|
132
|
+
remember_layout(key, window, true) unless key.nil?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def remember_layout(window_id, window, open = false)
|
138
|
+
if @app
|
139
|
+
layouts = @app.get_preference('Layouts') || {}
|
140
|
+
layouts[window_id] = window.layout
|
141
|
+
layouts[window_id][:open] = open
|
142
|
+
@app.save_preference('Layouts', layouts)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def restore_layout(window_id)
|
147
|
+
if @app
|
148
|
+
layouts = @app.get_preference('Layouts') || {}
|
149
|
+
return layouts[window_id]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def restore_layouts()
|
154
|
+
if @app
|
155
|
+
layouts = @app.get_preference('Layouts')
|
156
|
+
return unless layouts
|
157
|
+
# get all previously open windows
|
158
|
+
open = layouts.find_all { | x | x[1][:open] }
|
159
|
+
open.each do | ident, layout |
|
160
|
+
if ident =~ /^Document(\w+)$/
|
161
|
+
$wxapp.on_document_open($1)
|
162
|
+
elsif ident =~ /^Category(\w+)/ and
|
163
|
+
cat = @app.get_category($1, true)
|
164
|
+
$wxapp.on_category_open(cat)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def set_display_font(font)
|
171
|
+
# super(font)
|
172
|
+
@window_map.values.each do | child |
|
173
|
+
child.set_display_font(font) if child.respond_to?(:set_display_font)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def close_all()
|
178
|
+
@window_map.values.each { | child | child.close() }
|
179
|
+
end
|
180
|
+
|
181
|
+
def receive_savestate_changed(app)
|
182
|
+
title = 'Weft QDA'
|
183
|
+
if app
|
184
|
+
if app.dbfile
|
185
|
+
title << " - #{app.dbfile}"
|
186
|
+
else
|
187
|
+
title << " - [New Project]"
|
188
|
+
end
|
189
|
+
if app.dirty?
|
190
|
+
title << "*"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
set_title(title)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class EasyMenu < Wx::Menu
|
198
|
+
def EasyMenu.next_base()
|
199
|
+
@base_id ||= 0
|
200
|
+
@base_id += 100
|
201
|
+
end
|
202
|
+
|
203
|
+
def initialize(target)
|
204
|
+
super()
|
205
|
+
@target = target
|
206
|
+
@base_id = EasyMenu.next_base()
|
207
|
+
@commands = Hash.new do | hash, key |
|
208
|
+
Kernel.raise ArgumentError.new("No such menu item #{key}")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# adds the item +command_str+ to the menu, with the optional
|
213
|
+
# shortcut key +command_key+, and sets it to run the associated
|
214
|
+
# block when the menu item is chosen. The menu item can later be
|
215
|
+
# referred to by the symbol with the commands name with special
|
216
|
+
# characters removed andspaces turned to underscores. For
|
217
|
+
# example, "Save Project" becomes :save_project
|
218
|
+
def add_item(command_str, command_key = '', itemtype = Wx::ITEM_NORMAL)
|
219
|
+
const = ( @base_id += 1 )
|
220
|
+
ident = command_str.gsub(/\s+/, "_").gsub(/\W/, "").downcase.to_sym
|
221
|
+
@commands[ident] = const
|
222
|
+
append(const, "#{command_str}\t#{command_key}", "", itemtype)
|
223
|
+
@target.evt_menu(const) { | e | yield(e) }
|
224
|
+
return ident
|
225
|
+
end
|
226
|
+
|
227
|
+
# pass a series of symbol idents eg :save_project, :close
|
228
|
+
def enable_items(*idents)
|
229
|
+
idents.each { | ident | enable( @commands[ident], true ) }
|
230
|
+
end
|
231
|
+
alias :enable_item :enable_items
|
232
|
+
|
233
|
+
def disable_items(*idents)
|
234
|
+
idents.each { | ident | enable( @commands[ident], false ) }
|
235
|
+
end
|
236
|
+
alias :disable_item :disable_items
|
237
|
+
|
238
|
+
def check_items(*idents)
|
239
|
+
idents.each { | ident | check( @commands[ident], true ) }
|
240
|
+
end
|
241
|
+
alias :check_item :check_items
|
242
|
+
|
243
|
+
def uncheck_items(*idents)
|
244
|
+
idents.each { | ident | check( @commands[ident], false ) }
|
245
|
+
end
|
246
|
+
alias :uncheck_item :uncheck_items
|
247
|
+
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
$:.push('../lib/')
|
2
|
+
|
3
|
+
require 'weft/document'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'weft/category'
|
6
|
+
|
7
|
+
class TestDocument < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_basic
|
13
|
+
d = QDA::Document.new('The Title')
|
14
|
+
assert_equal('The Title', d.title,
|
15
|
+
'Set title at initialisation')
|
16
|
+
assert_equal(0, d.length,
|
17
|
+
'Document length is 0')
|
18
|
+
|
19
|
+
d.append('ego')
|
20
|
+
assert_equal(4, d.length,
|
21
|
+
'Document length is 4')
|
22
|
+
assert_equal('eg', d[0,2],
|
23
|
+
'Retrieved doc substring')
|
24
|
+
assert_equal('go', d[1,2],
|
25
|
+
'Retrieved doc substring')
|
26
|
+
assert_equal('', d[4,5],
|
27
|
+
'Retrieved doc substring')
|
28
|
+
assert_equal('', d[2,0],
|
29
|
+
'Retrieved doc substring')
|
30
|
+
|
31
|
+
d.append('bar')
|
32
|
+
assert_equal(8, d.length,
|
33
|
+
'Document length is 8')
|
34
|
+
assert_equal("o\nba", d[2,4],
|
35
|
+
'Retrieved doc substring')
|
36
|
+
assert_equal("ba", d[4,2],
|
37
|
+
'Retrieved doc substring')
|
38
|
+
assert_equal("bar\n", d[4,9],
|
39
|
+
'Retrieved doc substring')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_fragment
|
43
|
+
d = QDA::Document.new('The Title')
|
44
|
+
d.append('abc def ghi jkl')
|
45
|
+
d.append('ABC DEF GHI JKL')
|
46
|
+
fr_1 = d[0,4]
|
47
|
+
assert_equal(0, fr_1.offset, 'Give offset correctly')
|
48
|
+
assert_equal(4, fr_1.end, 'Give end correctly')
|
49
|
+
assert_equal('abc ', fr_1, 'String equality')
|
50
|
+
|
51
|
+
fr_2 = d[2,5]
|
52
|
+
assert_equal(2, fr_2.offset, 'Give offset correctly')
|
53
|
+
assert_equal(7, fr_2.end, 'Give end correctly')
|
54
|
+
|
55
|
+
fr_3 = d[12,6]
|
56
|
+
assert_equal(12, fr_3.offset, 'Give offset correctly')
|
57
|
+
assert_equal(18, fr_3.end, 'Give end correctly')
|
58
|
+
|
59
|
+
fr_3.docid = 1
|
60
|
+
code = fr_3.to_code()
|
61
|
+
assert_equal(1, code.docid)
|
62
|
+
assert_equal(12, code.offset)
|
63
|
+
assert_equal(6, code.length)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_fragment_equivalence
|
67
|
+
c_1 = QDA::Fragment.new('ABC', 'title', 5)
|
68
|
+
c_2 = QDA::Fragment.new('XYZ', 'title', 0)
|
69
|
+
c_3 = QDA::Fragment.new('ABC', 'other', 5)
|
70
|
+
c_4 = QDA::Fragment.new('ABC', 'title', 50)
|
71
|
+
c_5 = QDA::Fragment.new('ABC', 'title', 5)
|
72
|
+
|
73
|
+
assert(c_1 == c_1, 'Identity Equality')
|
74
|
+
assert(c_1 == c_5, 'Equivalence Equality')
|
75
|
+
assert(c_1 != c_2, 'Text inequality')
|
76
|
+
assert(c_1 != c_3, 'Document inequality')
|
77
|
+
assert(c_1 != c_4, 'Offset inequality')
|
78
|
+
|
79
|
+
c_1 = QDA::Fragment.new('ABCDEF', 'title', 5)
|
80
|
+
c_2 = QDA::Fragment.new('DEFGH', 'title', 8)
|
81
|
+
|
82
|
+
assert(c_1.overlap?(c_2), 'Overlap 1/2')
|
83
|
+
assert(c_2.overlap?(c_1), 'Overlap 1/1')
|
84
|
+
assert(c_1.touch?(c_2), 'Touch 1/1')
|
85
|
+
assert(c_2.touch?(c_1), 'Touch 1/2')
|
86
|
+
|
87
|
+
c_1 = QDA::Fragment.new('ABCDEF', 'title', 5)
|
88
|
+
c_2 = QDA::Fragment.new('GHIJKL', 'title', 11)
|
89
|
+
|
90
|
+
assert(! c_1.overlap?(c_2), 'Overlap 2/2')
|
91
|
+
assert(! c_2.overlap?(c_1), 'Overlap 2/1')
|
92
|
+
assert(c_1.touch?(c_2), 'Touch 2/1')
|
93
|
+
assert(c_2.touch?(c_1), 'Touch 2/2')
|
94
|
+
|
95
|
+
c_1 = QDA::Fragment.new('ABCDEF', 'title', 5)
|
96
|
+
c_2 = QDA::Fragment.new('JKL', 'title', 14)
|
97
|
+
assert(! c_1.overlap?(c_2), 'Overlap 3/2')
|
98
|
+
assert(! c_2.overlap?(c_1), 'Overlap 3/1')
|
99
|
+
assert(! c_1.touch?(c_2), 'Touch 3/1')
|
100
|
+
assert(! c_2.touch?(c_1), 'Touch 3/2')
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_fragment_comparison
|
104
|
+
c_1 = QDA::Fragment.new('ABC', 'title', 5)
|
105
|
+
c_2 = QDA::Fragment.new('XYZ', 'title', 0)
|
106
|
+
assert(c_2 < c_1, 'Offset comparison')
|
107
|
+
|
108
|
+
cs = QDA::CodeSet[ c_1, c_2 ]
|
109
|
+
cs = cs.sort { | x, y | x <=> y }
|
110
|
+
assert_equal('XYZ', cs[0],
|
111
|
+
'Sort test')
|
112
|
+
|
113
|
+
c_1 = QDA::Fragment.new('abc', 'title', 0)
|
114
|
+
c_2 = QDA::Fragment.new('xyz', 'title', 5)
|
115
|
+
assert(c_2 > c_1, 'Offset comparison')
|
116
|
+
|
117
|
+
cs = QDA::CodeSet[ c_1, c_2 ]
|
118
|
+
cs = cs.sort
|
119
|
+
assert_equal('abc', cs[0],
|
120
|
+
'Sort test')
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_fragment_addition
|
124
|
+
d = QDA::Document.new('The Title')
|
125
|
+
d.append('abc def ghi jkl')
|
126
|
+
d.append('ABC DEF GHI JKL')
|
127
|
+
fr_1 = d[0,4]
|
128
|
+
fr_2 = d[2,5]
|
129
|
+
fr_3 = d[12,6]
|
130
|
+
|
131
|
+
assert_equal('abc def', fr_1 + fr_2,
|
132
|
+
'Fragment addition')
|
133
|
+
assert_equal( d[0, 14], fr_1 + d[3, 11],
|
134
|
+
'Overlapping fragment addition')
|
135
|
+
assert_equal(['abc ', "jkl\nAB"], fr_1 + fr_3,
|
136
|
+
'Disjoint fragment addition')
|
137
|
+
assert_equal('abc ', fr_1 + d[1,2],
|
138
|
+
'Surrounded fragment addition')
|
139
|
+
|
140
|
+
cs_1 = QDA::CodeSet[ fr_1, fr_3 ]
|
141
|
+
cs_2 = QDA::CodeSet[ d[3, 11] ]
|
142
|
+
|
143
|
+
results = cs_1.union(cs_2)
|
144
|
+
assert_equal( [ d[0, 18 ] ], results,
|
145
|
+
'CodeSet union')
|
146
|
+
|
147
|
+
results = cs_1.union(cs_2)
|
148
|
+
assert_equal( [ d[0, 18] ], results,
|
149
|
+
'CodeSet union')
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_fragment_intersect
|
153
|
+
d = QDA::Document.new('The Title')
|
154
|
+
d.append('abc def ghi jkl')
|
155
|
+
d.append('ABC DEF GHI JKL')
|
156
|
+
fr_1 = d[0,4]
|
157
|
+
fr_2 = d[2,5]
|
158
|
+
fr_3 = d[12,6]
|
159
|
+
assert_equal('c ', fr_1 % fr_2,
|
160
|
+
'Fragment intersection')
|
161
|
+
|
162
|
+
assert_nil(fr_1 % fr_3,
|
163
|
+
'Disjoint fragment intersection')
|
164
|
+
|
165
|
+
assert_equal('bc', fr_1 % d[1,2],
|
166
|
+
'overlapping fragment intersection')
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_fragment_exclude
|
170
|
+
d = QDA::Document.new('The Title')
|
171
|
+
d.append('abc def ghi jkl')
|
172
|
+
d.append('ABC DEF GHI JKL')
|
173
|
+
fr_1 = d[0,4]
|
174
|
+
fr_2 = d[2,5]
|
175
|
+
fr_3 = d[12,6]
|
176
|
+
assert_equal([ 'ab' ], fr_1 - fr_2,
|
177
|
+
'Fragment exclusion')
|
178
|
+
|
179
|
+
assert_equal([ 'abc ' ], fr_1 - fr_3,
|
180
|
+
'Disjoint fragment exclusion')
|
181
|
+
|
182
|
+
assert_equal([ 'a', ' ' ], fr_1 - d[1,2],
|
183
|
+
'overlapping fragment exclusion')
|
184
|
+
|
185
|
+
cs_1 = QDA::CodeSet[ fr_1, fr_3 ]
|
186
|
+
cs_2 = QDA::CodeSet[ d[3, 11] ]
|
187
|
+
|
188
|
+
results = cs_1.exclude(cs_2)
|
189
|
+
assert_equal( [ d[0,3], d[14, 4] ], results,
|
190
|
+
'CodeSet subtraction')
|
191
|
+
|
192
|
+
results = cs_1.exclude(cs_2)
|
193
|
+
assert_equal( [ d[0,3], d[14, 4] ], results,
|
194
|
+
'CodeSet subtraction')
|
195
|
+
end
|
196
|
+
end
|