weft-qda 0.9.6 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/weft.rb +16 -1
- data/lib/weft/WEFT-VERSION-STRING.rb +1 -1
- data/lib/weft/application.rb +17 -74
- data/lib/weft/backend.rb +6 -32
- data/lib/weft/backend/sqlite.rb +222 -164
- data/lib/weft/backend/sqlite/category_tree.rb +52 -48
- data/lib/weft/backend/sqlite/database.rb +57 -0
- data/lib/weft/backend/sqlite/upgradeable.rb +7 -0
- data/lib/weft/broadcaster.rb +90 -0
- data/lib/weft/category.rb +139 -47
- data/lib/weft/codereview.rb +160 -0
- data/lib/weft/coding.rb +74 -23
- data/lib/weft/document.rb +23 -10
- data/lib/weft/exceptions.rb +10 -0
- data/lib/weft/filters.rb +47 -224
- data/lib/weft/filters/indexers.rb +137 -0
- data/lib/weft/filters/input.rb +118 -0
- data/lib/weft/filters/output.rb +101 -0
- data/lib/weft/filters/templates.rb +80 -0
- data/lib/weft/filters/win32backtick.rb +246 -0
- data/lib/weft/query.rb +169 -0
- data/lib/weft/wxgui.rb +349 -294
- data/lib/weft/wxgui/constants.rb +43 -0
- data/lib/weft/wxgui/controls.rb +6 -0
- data/lib/weft/wxgui/controls/category_dropdown.rb +192 -0
- data/lib/weft/wxgui/controls/category_tree.rb +314 -0
- data/lib/weft/wxgui/controls/document_list.rb +97 -0
- data/lib/weft/wxgui/controls/multitype_control.rb +37 -0
- data/lib/weft/wxgui/{inspectors → controls}/textcontrols.rb +235 -64
- data/lib/weft/wxgui/dialogs.rb +144 -41
- data/lib/weft/wxgui/error_handler.rb +116 -36
- data/lib/weft/wxgui/exceptions.rb +7 -0
- data/lib/weft/wxgui/inspectors.rb +61 -208
- data/lib/weft/wxgui/inspectors/category.rb +19 -16
- data/lib/weft/wxgui/inspectors/codereview.rb +90 -132
- data/lib/weft/wxgui/inspectors/document.rb +12 -8
- data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -56
- data/lib/weft/wxgui/inspectors/query.rb +284 -0
- data/lib/weft/wxgui/inspectors/script.rb +147 -23
- data/lib/weft/wxgui/lang/en.rb +69 -0
- data/lib/weft/wxgui/sidebar.rb +90 -432
- data/lib/weft/wxgui/utilities.rb +70 -91
- data/lib/weft/wxgui/workarea.rb +150 -43
- data/share/icons/category.ico +0 -0
- data/share/icons/category.xpm +109 -0
- data/share/icons/codereview.ico +0 -0
- data/share/icons/codereview.xpm +54 -0
- data/share/icons/d_and_c.xpm +126 -0
- data/share/icons/document.ico +0 -0
- data/share/icons/document.xpm +70 -0
- data/share/icons/project.ico +0 -0
- data/share/icons/query.ico +0 -0
- data/share/icons/query.xpm +56 -0
- data/{lib/weft/wxgui → share/icons}/search.xpm +0 -0
- data/share/icons/weft.ico +0 -0
- data/share/icons/weft.xpm +62 -0
- data/share/icons/weft16.ico +0 -0
- data/share/icons/weft32.ico +0 -0
- data/share/templates/category_plain.html +18 -0
- data/share/templates/codereview_plain.html +18 -0
- data/share/templates/document_plain.html +13 -0
- data/share/templates/document_plain.txt +7 -0
- data/test/001-document.rb +55 -36
- data/test/002-category.rb +81 -6
- data/test/003-code.rb +8 -4
- data/test/004-application.rb +13 -34
- data/test/005-query_review.rb +139 -0
- data/test/006-filters.rb +54 -42
- data/test/007-output_filters.rb +113 -0
- data/test/009a-backend_sqlite_basic.rb +95 -24
- data/test/009b-backend_sqlite_complex.rb +43 -62
- data/test/009c_backend_sqlite_bench.rb +5 -10
- data/test/053-doc_inspector.rb +46 -0
- data/test/055-query_window.rb +50 -0
- data/test/all-tests.rb +1 -0
- data/test/test-common.rb +19 -0
- data/test/testdata/empty.qdp +0 -0
- data/test/testdata/simple with space.pdf +0 -0
- data/test/testdata/simple.pdf +0 -0
- data/weft-qda.rb +40 -7
- metadata +74 -14
- data/lib/weft/wxgui/category.xpm +0 -26
- data/lib/weft/wxgui/document.xpm +0 -25
- data/lib/weft/wxgui/inspectors/search.rb +0 -265
- data/lib/weft/wxgui/mondrian.xpm +0 -44
- data/lib/weft/wxgui/weft16.xpm +0 -31
@@ -1,40 +1,27 @@
|
|
1
1
|
require 'base64'
|
2
2
|
|
3
3
|
module QDA::Backend::SQLite
|
4
|
-
class CategoryTreeNode
|
5
|
-
attr_reader :dbid, :children
|
6
|
-
attr_accessor :parent, :name
|
7
|
-
protected :parent=
|
8
|
-
|
4
|
+
class CategoryTreeNode < QDA::Category
|
9
5
|
def initialize(parent, dbid, name)
|
10
|
-
|
6
|
+
self.name = name
|
7
|
+
@parent = parent
|
8
|
+
@dbid = dbid
|
11
9
|
@children = []
|
12
10
|
end
|
13
|
-
|
11
|
+
|
14
12
|
def add(dbid, name)
|
15
|
-
|
13
|
+
add_child( CategoryTreeNode.new(@dbid, dbid, name) )[-1]
|
16
14
|
end
|
17
|
-
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def remove(target)
|
24
|
-
@children.delete_if { | c | c.dbid == target.dbid }
|
15
|
+
|
16
|
+
def move(parent)
|
17
|
+
@parent = parent.dbid
|
18
|
+
parent.add_child(self)
|
25
19
|
end
|
26
|
-
|
27
|
-
def like(other)
|
28
|
-
name =~ /^#{other}/i
|
29
|
-
end
|
30
|
-
|
20
|
+
|
31
21
|
def to_s()
|
32
|
-
"<
|
33
|
-
end
|
34
|
-
|
35
|
-
def descendants()
|
36
|
-
@children.map() { | c | [ c.dbid, c.descendants ] }.flatten
|
22
|
+
"<CTNode '#{name}' [#{dbid}]>"
|
37
23
|
end
|
24
|
+
alias :inspect :to_s
|
38
25
|
end
|
39
26
|
|
40
27
|
class CategoryTree
|
@@ -49,20 +36,9 @@ module QDA::Backend::SQLite
|
|
49
36
|
end
|
50
37
|
|
51
38
|
def [](id)
|
52
|
-
@table[id] or raise "Unknown id #{id.inspect}"
|
53
|
-
end
|
54
|
-
|
55
|
-
def find(path)
|
56
|
-
points = path.split('/')
|
57
|
-
scope = points[0].empty? ? @roots : @table.values
|
58
|
-
points.delete('')
|
59
|
-
while elem = points.shift
|
60
|
-
scope = scope.find_all { | x | x.like(elem) }
|
61
|
-
scope.map! { | x | x.children }.flatten! unless points.empty?
|
62
|
-
end
|
63
|
-
scope
|
39
|
+
@table[id] or raise QDA::NotFoundError.new("Unknown id #{id.inspect}")
|
64
40
|
end
|
65
|
-
|
41
|
+
|
66
42
|
def add(parentid, dbid, name)
|
67
43
|
if parentid
|
68
44
|
@table[dbid] = @table[parentid].add(dbid, name)
|
@@ -73,19 +49,47 @@ module QDA::Backend::SQLite
|
|
73
49
|
end
|
74
50
|
|
75
51
|
def remove(dbid)
|
76
|
-
|
77
|
-
@table[
|
52
|
+
me = @table[dbid]
|
53
|
+
@table[me.parent].delete(me) if @table[me.parent]
|
54
|
+
dbids = []
|
55
|
+
me.children.each { | c | dbids += remove(c.dbid) }
|
56
|
+
@table.delete(dbid)
|
57
|
+
dbids.push(dbid)
|
58
|
+
dbids
|
59
|
+
end
|
60
|
+
|
61
|
+
def find(path)
|
62
|
+
paths = QDA::Category.parse_path(path)
|
63
|
+
# if it's a path with "/" at the beginning, search among roots
|
64
|
+
if paths[0].empty?
|
65
|
+
# maybe a named root e.g. '/CATEORIES'
|
66
|
+
fixed_roots = @roots.find_all { | x | x.name =~ /^#{paths[1]}/ }
|
67
|
+
# no root category matched, so search starting from all root nodes
|
68
|
+
if fixed_roots.empty?
|
69
|
+
scope = @roots
|
70
|
+
# root matched, and the path is so short it is the root category itself
|
71
|
+
elsif paths.length < 3
|
72
|
+
return fixed_roots
|
73
|
+
# restrict the search to only the matching root categories
|
74
|
+
else
|
75
|
+
path = paths[2 .. -1].map { | p | p.gsub(/\//, '//') }.join('/')
|
76
|
+
scope = fixed_roots
|
77
|
+
end
|
78
|
+
# if a relative path (not starting with '/'
|
79
|
+
else
|
80
|
+
scope = @table.values
|
81
|
+
end
|
82
|
+
scope.inject([]) { | a, x | a + x.find(path) }
|
78
83
|
end
|
79
|
-
|
80
|
-
def move(dbid,
|
81
|
-
|
82
|
-
|
83
|
-
@table[
|
84
|
-
@table[old_parent].remove(child)
|
84
|
+
|
85
|
+
def move(dbid, new_parentid)
|
86
|
+
movee = @table[dbid]
|
87
|
+
@table[movee.parent].delete(movee)
|
88
|
+
movee.move(@table[new_parentid])
|
85
89
|
end
|
86
90
|
|
87
91
|
def is_descendant?(ancestor, descendant)
|
88
|
-
@table[ancestor].
|
92
|
+
@table[ancestor].is_ancestor_of?(descendant)
|
89
93
|
end
|
90
94
|
|
91
95
|
def serialise()
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# mainly just utility code to handle transparent choice of sqlite v2 or v3 -
|
2
|
+
# all deals diretly with ruby-sqlite module
|
3
|
+
module QDA::Backend::SQLite
|
4
|
+
|
5
|
+
# if working with sqlite v2 with the sqlite-ruby v2, we need a
|
6
|
+
# couple of compatibility tweaks.
|
7
|
+
if defined?(::SQLite)
|
8
|
+
SQLITE_DB_CLASS = ::SQLite::Database
|
9
|
+
# Ruby-SQLite3 statements have a close() method, but Ruby-SQLite
|
10
|
+
# v 2 don't - so we supply a dummy method for when using v2
|
11
|
+
class ::SQLite::Statement
|
12
|
+
def close(); end
|
13
|
+
end
|
14
|
+
# SQLite3 introduced this more ruby-ish notation
|
15
|
+
class ::SQLite::Database::FunctionProxy
|
16
|
+
alias :result= :set_result
|
17
|
+
end
|
18
|
+
elsif defined?(::SQLite3)
|
19
|
+
SQLITE_DB_CLASS = ::SQLite3::Database
|
20
|
+
else
|
21
|
+
raise LoadError, "No SQlite database class loaded"
|
22
|
+
end
|
23
|
+
|
24
|
+
class DatabaseBase < SQLITE_DB_CLASS
|
25
|
+
def initialize(*args)
|
26
|
+
super(*args)
|
27
|
+
self.results_as_hash = true
|
28
|
+
self.synchronous = 'OFF'
|
29
|
+
end
|
30
|
+
|
31
|
+
def date_freeze(date)
|
32
|
+
date ? date.strftime('%Y-%m-%d %H:%M:%S') : ''
|
33
|
+
end
|
34
|
+
|
35
|
+
def date_thaw(str)
|
36
|
+
return nil if str.empty?
|
37
|
+
return Time.local( *str.split(/[- :]/) )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# only for use in testing - this cannot be saved
|
42
|
+
class InMemoryDatabase < DatabaseBase
|
43
|
+
def initialize()
|
44
|
+
super(":memory:")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# A class derived from a standard SQLite database with the ability to save
|
49
|
+
# and revert changes. Under the hood, when a database file is requested to be
|
50
|
+
# opened, a temporary copy of that file is used, and all changes are only
|
51
|
+
# written on a call to save().
|
52
|
+
class FileDatabase < DatabaseBase
|
53
|
+
def initialize(file)
|
54
|
+
super(file)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -27,6 +27,13 @@ module QDA::Backend::SQLite
|
|
27
27
|
legacy_category_tree_storage()
|
28
28
|
save_preference('LastModifiedVersion', WEFT_VERSION)
|
29
29
|
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
old_codes = get_root_category('CODES')
|
33
|
+
old_codes.name = 'CATEGORIES'
|
34
|
+
save_category(old_codes)
|
35
|
+
rescue QDA::NotFoundError
|
36
|
+
end
|
30
37
|
end
|
31
38
|
|
32
39
|
# This is a change from 0.9.5 -> 0.9.6; Category tree structure
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module QDA
|
2
|
+
|
3
|
+
# Allows an object such as the global Wx App to broadcast messages
|
4
|
+
# about changes in the database and other application states that
|
5
|
+
# might require the child to update its appearance.
|
6
|
+
module Broadcaster
|
7
|
+
# intended to be module private variables to managed which widgets
|
8
|
+
# are subscribing to which events
|
9
|
+
attr_accessor :bc__subscribers, :bc__subscriptions
|
10
|
+
|
11
|
+
|
12
|
+
def event_types()
|
13
|
+
self.class.const_get('SUBSCRIBABLE_EVENTS')
|
14
|
+
end
|
15
|
+
|
16
|
+
# broadcast an event to all subscribers to event of type
|
17
|
+
# +event_type+, optionally including +content+
|
18
|
+
def broadcast(event_type, content = nil)
|
19
|
+
return unless bc__subscriptions
|
20
|
+
for subscriber in bc__subscriptions[event_type]
|
21
|
+
subscriber.notify(event_type, content)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# subscribe the widget +subscriber+ to receive notification of
|
26
|
+
# app event types +*events+ (which should be a set of symbols)
|
27
|
+
def add_subscriber(subscriber, *events)
|
28
|
+
reset_subscriptions if not bc__subscriptions
|
29
|
+
if events.length == 1 and events[0] == :all
|
30
|
+
events = self.event_types()
|
31
|
+
end
|
32
|
+
|
33
|
+
events.each do | e |
|
34
|
+
bc__subscriptions[e].push(subscriber)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# accepts a ruby item
|
39
|
+
def delete_subscriber(subscriber)
|
40
|
+
if subscriber.respond_to?(:associated_subscribers)
|
41
|
+
subscriber.associated_subscribers.each do | child |
|
42
|
+
delete_subscriber(child)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
bc__subscribers.delete(subscriber)
|
46
|
+
bc__subscriptions.each_value do | subscriber_set |
|
47
|
+
subscriber_set.delete(subscriber)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset_subscriptions()
|
52
|
+
self.bc__subscribers = []
|
53
|
+
self.bc__subscriptions = Hash.new do | ev |
|
54
|
+
raise "Cannot subscribe to unknown event #{ev}"
|
55
|
+
end
|
56
|
+
event_types.each { | ev | self.bc__subscriptions[ev] = [] }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Any gui element that may need to modify its appearance in response
|
61
|
+
# to application updates may include the subscriber module. Instances
|
62
|
+
# of the class may then call the +subscribe+ method to receive
|
63
|
+
# notification of changes.
|
64
|
+
module Subscriber
|
65
|
+
# receive notification that an event of type +ev+ has been called,
|
66
|
+
# optionally passing +content+ for additional information about the
|
67
|
+
# object the event concerned.
|
68
|
+
def notify(ev, content = nil)
|
69
|
+
receiver = "receive_#{ev}".intern
|
70
|
+
if respond_to?(receiver)
|
71
|
+
send(receiver, content)
|
72
|
+
else
|
73
|
+
warn "#{self} received unhandled event #{ev}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Subscribe this object to the events +events+, which should be a
|
78
|
+
# list of symbols. For example, to receive notification of category
|
79
|
+
# changes and additions, the recipient shoudl call
|
80
|
+
# subscribe(:category_changed, :category_added)
|
81
|
+
#
|
82
|
+
# For every event type to which a subscriber subscribes, it should
|
83
|
+
# implement a corresponding receive_xxx method, where xxx is the
|
84
|
+
# name of the event type. To act upon category changes, the
|
85
|
+
# subscriber should implement +receive_category_changed+
|
86
|
+
def subscribe(broadcaster, *events)
|
87
|
+
broadcaster.add_subscriber(self, *events)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/weft/category.rb
CHANGED
@@ -2,39 +2,108 @@ require 'weft/coding'
|
|
2
2
|
|
3
3
|
module QDA
|
4
4
|
class Category
|
5
|
-
attr_reader :children, :codes
|
6
|
-
attr_accessor :dbid, :text, :
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
attr_reader :children, :codes, :reader, :parent, :name
|
6
|
+
attr_accessor :dbid, :text, :meta, :memo
|
7
|
+
|
8
|
+
def Category.parse_path(path)
|
9
|
+
bits = path.scan(/(?:[^\/]|\/\/)+/)
|
10
|
+
bits.map! { | x | x.gsub(/\/\//, '/') }
|
11
|
+
bits.unshift('') if path =~ /\A\/(?!\/)/
|
12
|
+
bits
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(a_name, a_parent = nil, a_memo = '')
|
12
16
|
@children = []
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@children.push(child)
|
19
|
-
child.parent = self
|
17
|
+
self.name = a_name
|
18
|
+
self.memo = a_memo
|
19
|
+
self.parent = a_parent
|
20
|
+
# @codes = QDA::CodingTable.new()
|
21
|
+
@codes = nil
|
20
22
|
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
def name=(a_name)
|
25
|
+
case a_name
|
26
|
+
when /(\A\/|\/\z)/
|
27
|
+
raise BadNameError.new('Category name cannot have the "/" character' +
|
28
|
+
' at beginning or end')
|
29
|
+
when /[\n\t]/
|
30
|
+
raise BadNameError.new('Category name cannot contain newline or tab' +
|
31
|
+
' characters')
|
32
|
+
when String
|
33
|
+
@name = a_name
|
34
|
+
else
|
35
|
+
raise BadNameError.new('Category name must be a string')
|
36
|
+
end
|
37
|
+
end
|
26
38
|
|
39
|
+
def escape_name()
|
40
|
+
name.gsub(/\//, '//')
|
41
|
+
end
|
42
|
+
|
27
43
|
def ==(other)
|
28
44
|
if other.respond_to?(:dbid)
|
29
|
-
|
45
|
+
if self.dbid.nil? and other.dbid.nil?
|
46
|
+
return self.name == other.name && self.parent == other.parent
|
47
|
+
else
|
48
|
+
return self.dbid == other.dbid && self.parent == other.parent
|
49
|
+
end
|
30
50
|
elsif other.nil?
|
31
51
|
return false
|
32
52
|
else
|
33
53
|
raise "No comparison of Category with #{other.inspect}"
|
34
54
|
end
|
35
55
|
end
|
36
|
-
|
56
|
+
|
57
|
+
def add_child(child)
|
58
|
+
if self[ child.name ]
|
59
|
+
raise NotUniqueNameError.new("Child with the name '#{child.name}'" +
|
60
|
+
"is already attached to '#{self.name}'")
|
61
|
+
end
|
62
|
+
@children.push(child)
|
63
|
+
end
|
64
|
+
protected :add_child
|
65
|
+
|
66
|
+
# set the parent of this category to be +new_parent+, deleting from old
|
67
|
+
# parent if necessary.
|
68
|
+
def parent=(new_parent)
|
69
|
+
if is_ancestor_of?(new_parent)
|
70
|
+
raise BadStructureError.new("Cannot make category a child of " +
|
71
|
+
"#{new_parent.path} as it is an ancestor")
|
72
|
+
end
|
73
|
+
parent.delete(self) if parent
|
74
|
+
@parent = new_parent
|
75
|
+
new_parent.add_child(self) if new_parent
|
76
|
+
end
|
77
|
+
|
78
|
+
# returns all the children who match +idx+
|
79
|
+
def [](idx)
|
80
|
+
case idx
|
81
|
+
when Fixnum, Range
|
82
|
+
return @children[idx]
|
83
|
+
when Regexp
|
84
|
+
return @children.find { | c | c.name =~ idx}
|
85
|
+
when String
|
86
|
+
patt = /\A#{Regexp.escape(idx)}\z/i
|
87
|
+
return @children.find { | c | c.name =~ patt }
|
88
|
+
else
|
89
|
+
raise ArgumentError.new("Bad index #{idx}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete(target)
|
94
|
+
@children.delete(target)
|
95
|
+
end
|
96
|
+
|
37
97
|
# number of separate documents coded by this category
|
98
|
+
def unique_name(name)
|
99
|
+
while self[ name ]
|
100
|
+
puts "#{name} exists"
|
101
|
+
name.sub!(/(?:\s*\((\d+)\))?$/) { " (#{$1.to_i.succ})" }
|
102
|
+
puts "trying #{name}"
|
103
|
+
end
|
104
|
+
name
|
105
|
+
end
|
106
|
+
|
38
107
|
def num_of_docs
|
39
108
|
@codes.num_of_docs
|
40
109
|
end
|
@@ -47,51 +116,74 @@ module QDA
|
|
47
116
|
@codes.num_of_chars
|
48
117
|
end
|
49
118
|
|
50
|
-
def
|
51
|
-
|
119
|
+
def each_child
|
120
|
+
children.each { | c | yield c }
|
121
|
+
end
|
122
|
+
def path()
|
123
|
+
ancestors.inject("/" << escape_name) do | path, anc |
|
124
|
+
"/" << anc.escape_name << path
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def ancestors()
|
129
|
+
ancestor, ancestors = self, []
|
130
|
+
ancestors.push(ancestor) while ancestor = ancestor.parent
|
131
|
+
ancestors
|
132
|
+
end
|
133
|
+
|
134
|
+
def descendants()
|
135
|
+
@children.map() { | c | [ c, c.descendants ] }.flatten
|
52
136
|
end
|
53
137
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
result.codes[docid] = codes.intersect( other.codes[docid] )
|
62
|
-
end
|
138
|
+
def find(path)
|
139
|
+
points = Category.parse_path(path)
|
140
|
+
scope = @children
|
141
|
+
points.delete('')
|
142
|
+
while point = points.shift
|
143
|
+
scope = scope.find_all { | x | x.name =~ /^#{point}/ }
|
144
|
+
scope.map! { | x | x.children }.flatten! unless points.empty?
|
63
145
|
end
|
64
|
-
|
146
|
+
scope
|
147
|
+
end
|
148
|
+
|
149
|
+
# returns true if +cat+ is a Category located below self
|
150
|
+
def is_ancestor_of?(cat)
|
151
|
+
descendants.include?(cat)
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def is_descendant_of?(cat)
|
156
|
+
ancestors.include?(cat)
|
157
|
+
end
|
158
|
+
|
159
|
+
def codetable=(codetable)
|
160
|
+
@codes = codetable
|
65
161
|
end
|
66
162
|
|
163
|
+
def codetable_init()
|
164
|
+
@codes ||= QDA::CodingTable.new()
|
165
|
+
end
|
166
|
+
|
67
167
|
# apply a code to a document; returns the new set of codes applied
|
68
168
|
# to that document. +docid+ should be the database id of the
|
69
169
|
# document to be retrieved (a string)
|
70
170
|
def code(docid, offset, length)
|
71
|
-
|
72
|
-
raise ArgumentError,
|
73
|
-
"Docid should be an integer or nil, got #{docid.inspect}"
|
74
|
-
end
|
75
|
-
unless offset >= 0
|
76
|
-
raise ArgumentError, "Offset should be an integer >= 0, got #{offset}"
|
77
|
-
end
|
78
|
-
unless length > 0
|
79
|
-
raise ArgumentError, "Length should be an integer > 0, got #{length}"
|
80
|
-
end
|
171
|
+
codetable_init()
|
81
172
|
new_code = QDA::Code.new(docid, offset, length)
|
82
173
|
@codes.add(new_code)
|
83
174
|
end
|
84
175
|
|
85
176
|
def uncode(docid, offset, length)
|
177
|
+
@codes ||= QDA::CodingTable.new()
|
86
178
|
# raise "docid should be an integer > 0, is #{docid}" if docid == 0
|
87
179
|
c = Code.new(docid, offset, length)
|
88
180
|
@codes.subtract(c)
|
89
181
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
@codes[docid]
|
182
|
+
|
183
|
+
def to_s()
|
184
|
+
"<Category '#{path}' [#{dbid}]>"
|
94
185
|
end
|
186
|
+
alias :inspect :to_s
|
95
187
|
end
|
96
188
|
|
97
189
|
# object representing a particular application of a code to a
|
@@ -125,7 +217,7 @@ module QDA
|
|
125
217
|
@length = length
|
126
218
|
end
|
127
219
|
|
128
|
-
# a Code is already
|
220
|
+
# a Code is already its own simplest representation, so never
|
129
221
|
# needs to be modified to work with another code-like object.
|
130
222
|
def coerce(other)
|
131
223
|
self
|