weft-qda 0.9.6 → 0.9.8
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 +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
|