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.
- 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
|