opener-outlet 1.0.0
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.
- checksums.yaml +7 -0
- data/README.md +57 -0
- data/bin/outlet-server +10 -0
- data/config.ru +4 -0
- data/config/database.rb +31 -0
- data/lib/opener/outlet.rb +14 -0
- data/lib/opener/outlet/output.rb +11 -0
- data/lib/opener/outlet/public/outlet.css +58 -0
- data/lib/opener/outlet/server.rb +79 -0
- data/lib/opener/outlet/version.rb +5 -0
- data/lib/opener/outlet/views/index.erb +33 -0
- data/lib/opener/outlet/views/show.erb +10 -0
- data/lib/opener/outlet/views/show.html.erb +11 -0
- data/lib/opener/outlet/visualizer.rb +278 -0
- data/opener-outlet.gemspec +39 -0
- data/visualizer.rb +278 -0
- metadata +255 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: df871ce40445a7412cc20219841a08301b1f6b20
|
4
|
+
data.tar.gz: 616bf3d5e1dc8d18dead07853648ff90af349282
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48d8ffb897ff377d690a24a5fe3ac54ec1cce3f5750f9d6aafbc15bdd41317b69c1810a26a5eb794bade1053a5c0507a8fc1cf3a026978d5cdf0296b393530b1
|
7
|
+
data.tar.gz: a7003db8bff48097aab1f5afca8f70377174a6c07997eacc5a806faf35467777131dd498209bff78ff50ef24f1127d6e460e0f4b517ab392f0d1e126a6db9011
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Outlet
|
2
|
+
|
3
|
+
Component that stores results from the Opener Web Services chain into a SQLite
|
4
|
+
Database and shows them into your browser.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
### As part of a Gemfile in a Ruby application
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'opener-outlet',
|
14
|
+
:git=>"git@github.com:opener-project/outlet.git"
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
The Opener Outlet comes equipped with a simple webservice. To start the
|
23
|
+
webservice type:
|
24
|
+
|
25
|
+
outlet-server
|
26
|
+
|
27
|
+
This will launch a mini webserver with the webservice. It defaults to port 9292,
|
28
|
+
so you can access it at:
|
29
|
+
|
30
|
+
http://localhost:9292
|
31
|
+
|
32
|
+
To launch it on a different port provide the ```-p [port-number]``` option like
|
33
|
+
this:
|
34
|
+
|
35
|
+
opinion-detector-server -p 1234
|
36
|
+
|
37
|
+
It then launches at ```http://localhost:1234```
|
38
|
+
|
39
|
+
When you run a chain of web services using callbacks, the last URL should be the
|
40
|
+
one that points to the Outlet Web Service. A unique id is generated and once the
|
41
|
+
chain has finished processing the text, you can view the result in the URL that
|
42
|
+
you get.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
### Procedure
|
47
|
+
|
48
|
+
1. Pull it
|
49
|
+
2. Create your feature branch (`git checkout -b features/my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin features/my-new-feature`)
|
52
|
+
5. If you're confident, merge your changes into master.
|
53
|
+
|
54
|
+
# What's next?
|
55
|
+
|
56
|
+
If you're interested in the opener-outlet, you also might want to check
|
57
|
+
out opener-project/outlet.
|
data/bin/outlet-server
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
# Without calling `Rack::Server#options` manually the CLI arguments will never
|
6
|
+
# be passed, thus the application can't be specified as a constructor argument.
|
7
|
+
server = Rack::Server.new
|
8
|
+
server.options[:config] = File.expand_path('../../config.ru', __FILE__)
|
9
|
+
|
10
|
+
server.start
|
data/config.ru
ADDED
data/config/database.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'activerecord-jdbcmysql-adapter'
|
3
|
+
|
4
|
+
DB_PASS = ENV["DB_PASS"]
|
5
|
+
DB_NAME = ENV["DB_NAME"]
|
6
|
+
DB_USER = ENV["DB_USER"]
|
7
|
+
DB_HOST = ENV["DB_HOST"]
|
8
|
+
|
9
|
+
if ENV["RACK_ENV"] == 'production'
|
10
|
+
ActiveRecord::Base.establish_connection(
|
11
|
+
adapter: 'mysql2',
|
12
|
+
database: DB_NAME,
|
13
|
+
host: DB_HOST,
|
14
|
+
username: DB_USER,
|
15
|
+
password: DB_PASS
|
16
|
+
)
|
17
|
+
else
|
18
|
+
ActiveRecord::Base.establish_connection(
|
19
|
+
adapter: 'mysql2',
|
20
|
+
host: DB_HOST || 'localhost',
|
21
|
+
username: 'root',
|
22
|
+
password: DB_PASS || '',
|
23
|
+
database: DB_NAME || 'opener_development'
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveRecord::Base.connection.execute("CREATE TABLE IF NOT EXISTS outputs (uuid varchar(40), text text, created_at timestamp DEFAULT CURRENT_TIMESTAMP);")
|
28
|
+
|
29
|
+
if ActiveRecord::Base.connection.execute("SHOW INDEX FROM outputs").nil?
|
30
|
+
ActiveRecord::Base.connection.execute("CREATE INDEX uuid_index ON outputs(uuid);")
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'outlet/output'
|
2
|
+
require_relative 'outlet/version'
|
3
|
+
require_relative 'outlet/server'
|
4
|
+
|
5
|
+
module Opener
|
6
|
+
class Outlet
|
7
|
+
def run(input, uuid)
|
8
|
+
output = Output.new(:uuid=>uuid, :text=>input)
|
9
|
+
output.save
|
10
|
+
|
11
|
+
return input
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
body {
|
2
|
+
font-family: arial;
|
3
|
+
line-height: 2em;
|
4
|
+
font-size: 15px;
|
5
|
+
margin-top: 3em;
|
6
|
+
}
|
7
|
+
|
8
|
+
.opener {
|
9
|
+
width: 800px;
|
10
|
+
margin-left: auto;
|
11
|
+
margin-right: auto;
|
12
|
+
}
|
13
|
+
|
14
|
+
.entities {
|
15
|
+
margin: 1em;
|
16
|
+
}
|
17
|
+
|
18
|
+
.entities:before {
|
19
|
+
font-size: 16px;
|
20
|
+
font-weight: bold;
|
21
|
+
content: "Entities";
|
22
|
+
}
|
23
|
+
|
24
|
+
.opinions {
|
25
|
+
margin: 1em;
|
26
|
+
}
|
27
|
+
|
28
|
+
.opinions:before {
|
29
|
+
font-size: 16px;
|
30
|
+
font-weight: bold;
|
31
|
+
content: "Opinions";
|
32
|
+
}
|
33
|
+
|
34
|
+
.properties {
|
35
|
+
margin: 1em;
|
36
|
+
}
|
37
|
+
|
38
|
+
.properties:before {
|
39
|
+
font-size: 16px;
|
40
|
+
font-weight: bold;
|
41
|
+
content: "Aspects";
|
42
|
+
}
|
43
|
+
|
44
|
+
.positive {
|
45
|
+
background-color: #AFA;
|
46
|
+
}
|
47
|
+
|
48
|
+
.negative {
|
49
|
+
background-color: #FAA;
|
50
|
+
}
|
51
|
+
|
52
|
+
.entity {
|
53
|
+
border-bottom: 2px dotted #00F;
|
54
|
+
}
|
55
|
+
|
56
|
+
.property {
|
57
|
+
border-bottom: 2px solid orange;
|
58
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'stringio'
|
4
|
+
require_relative './visualizer'
|
5
|
+
|
6
|
+
require File.expand_path('../../../../config/database', __FILE__)
|
7
|
+
|
8
|
+
module Opener
|
9
|
+
class Outlet
|
10
|
+
class Server < Sinatra::Base
|
11
|
+
|
12
|
+
post '/' do
|
13
|
+
output = Output.new
|
14
|
+
output.uuid = params[:request_id]
|
15
|
+
output.text = params[:input]
|
16
|
+
output.save
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/' do
|
20
|
+
if params[:request_id]
|
21
|
+
redirect "#{url("/")}#{params[:request_id]}"
|
22
|
+
else
|
23
|
+
erb :index
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
get '/:request_id' do
|
28
|
+
unless params[:request_id] == 'favicon.ico'
|
29
|
+
begin
|
30
|
+
output = Output.find_by_uuid(params[:request_id])
|
31
|
+
|
32
|
+
if output
|
33
|
+
content_type(:xml)
|
34
|
+
body(output.text)
|
35
|
+
else
|
36
|
+
halt(404, "No record found for ID #{params[:request_id]}")
|
37
|
+
end
|
38
|
+
rescue => error
|
39
|
+
error_callback = params[:error_callback]
|
40
|
+
|
41
|
+
submit_error(error_callback, error.message) if error_callback
|
42
|
+
|
43
|
+
raise(error)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
get '/html/:request_id' do
|
49
|
+
unless params[:request_id] == 'favicon.ico'
|
50
|
+
begin
|
51
|
+
output = Output.find_by_uuid(params[:request_id])
|
52
|
+
if output
|
53
|
+
output = StringIO.new(output.text)
|
54
|
+
parser = Opener::Kaf::Visualizer::Parser.new(output)
|
55
|
+
doc = parser.parse
|
56
|
+
html = Opener::Kaf::Visualizer::HTMLTextPresenter.new(doc)
|
57
|
+
@parsed = html.to_html
|
58
|
+
erb :show
|
59
|
+
else
|
60
|
+
halt(404, "No record found for ID #{params[:request_id]}")
|
61
|
+
end
|
62
|
+
rescue => error
|
63
|
+
error_callback = params[:error_callback]
|
64
|
+
|
65
|
+
submit_error(error_callback, error.message) if error_callback
|
66
|
+
|
67
|
+
raise(error)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def submit_error(url, message)
|
75
|
+
HTTPClient.post(url, :body => {:error => message})
|
76
|
+
end
|
77
|
+
end # Server
|
78
|
+
end # Outlet
|
79
|
+
end # Opener
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link type="text/css" rel="stylesheet" charset="UTF-8" href="markdown.css"/>
|
5
|
+
<title>Opener Outlet</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>Opener Outlet</h1>
|
9
|
+
|
10
|
+
<p>This Web Service, stores the output from a chain of Opener components in a
|
11
|
+
SQlite database when using callbacks and lets you view the output.</p>
|
12
|
+
<p>When using callbacks, the last Callback URL should be the URL that points
|
13
|
+
to Outlet. Then, when you submit your text, you get a URL where the result
|
14
|
+
will be shown, when the processing is over.</p>
|
15
|
+
|
16
|
+
<h2>Search for an output</h2>
|
17
|
+
|
18
|
+
<p>* required</p>
|
19
|
+
|
20
|
+
|
21
|
+
<form action="<%=url("/")%>" method="GET">
|
22
|
+
<div>
|
23
|
+
<label for="request_id"/>Type the ID here*</label>
|
24
|
+
<br/>
|
25
|
+
|
26
|
+
<input type="text" name="request_id" id="text" rows="10" cols="50"/>
|
27
|
+
</div>
|
28
|
+
<input type="submit" value="Submit" />
|
29
|
+
</form>
|
30
|
+
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
|
@@ -0,0 +1,278 @@
|
|
1
|
+
#require "opener/kaf/visualizer/version"
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Opener
|
5
|
+
module Kaf
|
6
|
+
module Visualizer
|
7
|
+
class Parser
|
8
|
+
attr_reader :doc
|
9
|
+
attr_reader :words, :terms, :entities, :properties, :opinions, :document
|
10
|
+
|
11
|
+
def initialize(input_file_handler)
|
12
|
+
@doc = Nokogiri::XML(input_file_handler)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
@words = parse_words
|
17
|
+
@terms = parse_terms
|
18
|
+
@entities = parse_entities
|
19
|
+
@properties = parse_properties
|
20
|
+
@opinions = parse_opinions
|
21
|
+
@document = KAFDocument.new(
|
22
|
+
:words => words,
|
23
|
+
:terms => terms,
|
24
|
+
:entities => entities,
|
25
|
+
:properties => properties,
|
26
|
+
:opinions => opinions
|
27
|
+
)
|
28
|
+
|
29
|
+
return document
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_words
|
33
|
+
parse_elements("//wf", Word)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_terms
|
37
|
+
# Of course terms should be words here.
|
38
|
+
# Dirty Hack, sufficient for now.
|
39
|
+
parse_elements("//term", Term, :terms=>words)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_entities
|
43
|
+
parse_elements("//entity", Entity, :terms=>terms)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_properties
|
47
|
+
parse_elements("//property", Property, :terms=>terms)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_opinions
|
51
|
+
parse_elements("//opinion", Opinion, :terms=>terms)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_elements(xpath, klass, opts={})
|
55
|
+
elements = doc.xpath(xpath)
|
56
|
+
lookup_table = Hash.new
|
57
|
+
elements.each do |element|
|
58
|
+
instance = klass.new(element, opts)
|
59
|
+
lookup_table[instance.id] = instance
|
60
|
+
end
|
61
|
+
lookup_table
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class KAFNode
|
67
|
+
attr_reader :content, :targets, :tag, :references
|
68
|
+
|
69
|
+
def initialize(tag, references)
|
70
|
+
@references = references
|
71
|
+
@tag = tag
|
72
|
+
|
73
|
+
set_instance_variables
|
74
|
+
set_content
|
75
|
+
set_targets
|
76
|
+
process_subnodes
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_content
|
80
|
+
@content = tag.content
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_instance_variables
|
84
|
+
tag.keys.each do |key|
|
85
|
+
if respond_to?("#{key}=".to_sym)
|
86
|
+
send("#{key}=".to_sym, tag[key])
|
87
|
+
else
|
88
|
+
instance_variable_set("@#{key}", tag[key])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_targets
|
94
|
+
@targets = []
|
95
|
+
tag.css("span target").each do |target|
|
96
|
+
id = target["id"]
|
97
|
+
@targets << references[:terms][id]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_subnodes
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_target?(*ids)
|
105
|
+
ids.flatten.each do |id|
|
106
|
+
return true if target_ids.include?(id)
|
107
|
+
end
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
if targets.size > 0
|
113
|
+
return targets.map(&:to_s).join(" ")
|
114
|
+
else
|
115
|
+
return content
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def target_ids
|
120
|
+
@targets.map(&:id)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
class Word < KAFNode
|
126
|
+
attr_reader :wid, :sent, :para, :offset
|
127
|
+
|
128
|
+
def id
|
129
|
+
wid
|
130
|
+
end
|
131
|
+
|
132
|
+
def offset=(offset)
|
133
|
+
@offset = offset.to_i
|
134
|
+
end
|
135
|
+
|
136
|
+
def length
|
137
|
+
content.nil? ? 0 : content.length
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
class Term < KAFNode
|
143
|
+
attr_reader :tid, :type, :lemma, :pos, :morphofeat
|
144
|
+
|
145
|
+
def id
|
146
|
+
tid
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Entity < KAFNode
|
151
|
+
attr_reader :eid, :type
|
152
|
+
|
153
|
+
def id
|
154
|
+
eid
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
"#{type}: #{targets.map(&:to_s).join(", ")}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Property < KAFNode
|
163
|
+
attr_reader :pid, :lemma
|
164
|
+
|
165
|
+
def id
|
166
|
+
pid
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_s
|
170
|
+
"#{lemma}: #{targets.map(&:to_s).join(", ")}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Opinion < KAFNode
|
175
|
+
attr_reader :oid, :expression
|
176
|
+
|
177
|
+
def id
|
178
|
+
oid
|
179
|
+
end
|
180
|
+
|
181
|
+
def process_subnodes
|
182
|
+
@expression = tag.xpath("opinion_expression").first["polarity"].to_sym
|
183
|
+
end
|
184
|
+
|
185
|
+
def to_s
|
186
|
+
"#{expression}: #{targets.map(&:to_s).join(", ")}"
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
class KAFDocument
|
192
|
+
attr_reader :words, :terms, :entities, :properties, :opinions
|
193
|
+
|
194
|
+
def initialize(opts={})
|
195
|
+
@words = opts.fetch(:words)
|
196
|
+
@terms = opts.fetch(:terms)
|
197
|
+
@entities = opts.fetch(:entities)
|
198
|
+
@properties = opts.fetch(:properties)
|
199
|
+
@opinions = opts.fetch(:opinions)
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
class HTMLTextPresenter
|
205
|
+
attr_reader :document
|
206
|
+
def initialize(document)
|
207
|
+
@document = document
|
208
|
+
end
|
209
|
+
|
210
|
+
def to_html
|
211
|
+
offset = 0
|
212
|
+
prev = Struct.new(:offset).new(0)
|
213
|
+
|
214
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
215
|
+
html.div(:class=>"opener") do
|
216
|
+
html.p do
|
217
|
+
document.words.values.sort_by(&:offset).each do |word|
|
218
|
+
if offset < word.offset
|
219
|
+
spacer = word.offset - offset
|
220
|
+
spacer = Array.new(spacer, " ").join
|
221
|
+
html.span(spacer)
|
222
|
+
end
|
223
|
+
|
224
|
+
terms = targets_for(:terms, word.id)
|
225
|
+
entities = targets_for(:entities, terms)
|
226
|
+
opinions = opinions_for(terms)
|
227
|
+
properties = targets_for(:properties, terms)
|
228
|
+
|
229
|
+
generics = []
|
230
|
+
generics << "term" if terms.size > 0
|
231
|
+
generics << "entity" if entities.size > 0
|
232
|
+
generics << "opinion" if opinions.size > 0
|
233
|
+
generics << "property" if properties.size > 0
|
234
|
+
|
235
|
+
classes = [terms, entities, opinions, properties, generics].flatten.uniq
|
236
|
+
|
237
|
+
word_annotations = classes.join(" ")
|
238
|
+
|
239
|
+
html.span(word.content, :class=>word_annotations, :id=>word.id)
|
240
|
+
offset = word.offset + word.length
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
[:entities, :opinions, :properties].each do |sym|
|
245
|
+
html.div(:class=>sym) do
|
246
|
+
document.public_send(sym).values.each do |entity|
|
247
|
+
html.div(entity.to_s, :id=>entity.id, :class=>entity.target_ids.join(" "))
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
builder.to_html
|
255
|
+
end
|
256
|
+
|
257
|
+
def targets_for(variable, *ids)
|
258
|
+
targets = document.public_send(variable.to_sym).values.select do |value|
|
259
|
+
value.has_target?(ids.flatten)
|
260
|
+
end
|
261
|
+
|
262
|
+
targets.map(&:id)
|
263
|
+
end
|
264
|
+
|
265
|
+
def opinions_for(*ids)
|
266
|
+
targets = document.opinions.values.select do |value|
|
267
|
+
value.has_target?(ids.flatten)
|
268
|
+
end
|
269
|
+
|
270
|
+
ids = targets.map(&:id)
|
271
|
+
sentiments = targets.map(&:expression)
|
272
|
+
return ids.concat(sentiments).uniq
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('../lib/opener/outlet/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'opener-outlet'
|
5
|
+
gem.version = Opener::Outlet::VERSION
|
6
|
+
gem.authors = ['development@olery.com']
|
7
|
+
gem.summary = 'Database storing for the web services output when using callbacks.'
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.homepage = "http://opener-project.github.com/"
|
10
|
+
gem.has_rdoc = 'yard'
|
11
|
+
gem.required_ruby_version = '>= 1.9.2'
|
12
|
+
|
13
|
+
gem.files = Dir.glob([
|
14
|
+
'config/**/*',
|
15
|
+
'lib/**/*',
|
16
|
+
'config.ru',
|
17
|
+
'*.gemspec',
|
18
|
+
'README.md',
|
19
|
+
'visualizer.rb'
|
20
|
+
]).select { |file| File.file?(file) }
|
21
|
+
|
22
|
+
gem.executables = Dir.glob('bin/*').map { |file| File.basename(file) }
|
23
|
+
|
24
|
+
gem.add_dependency 'builder', '~>3.0.0'
|
25
|
+
gem.add_dependency 'sinatra', '~>1.4.2'
|
26
|
+
gem.add_dependency 'nokogiri'
|
27
|
+
gem.add_dependency 'httpclient'
|
28
|
+
gem.add_dependency 'uuidtools'
|
29
|
+
gem.add_dependency 'jdbc-mysql'
|
30
|
+
gem.add_dependency 'activerecord-jdbcmysql-adapter'
|
31
|
+
gem.add_dependency 'activerecord', '~>3.2'
|
32
|
+
gem.add_dependency 'activesupport', '~>3.2'
|
33
|
+
gem.add_dependency 'opener-webservice'
|
34
|
+
|
35
|
+
gem.add_development_dependency 'rspec'
|
36
|
+
gem.add_development_dependency 'cucumber'
|
37
|
+
gem.add_development_dependency 'pry'
|
38
|
+
gem.add_development_dependency 'rake'
|
39
|
+
end
|
data/visualizer.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Opener
|
4
|
+
module Kaf
|
5
|
+
module Visualizer
|
6
|
+
class Parser
|
7
|
+
attr_reader :doc
|
8
|
+
attr_reader :words, :terms, :entities, :properties, :opinions, :document
|
9
|
+
|
10
|
+
def initialize(input_file_handler)
|
11
|
+
@doc = Nokogiri::XML(input_file_handler)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
@words = parse_words
|
16
|
+
@terms = parse_terms
|
17
|
+
@entities = parse_entities
|
18
|
+
@properties = parse_properties
|
19
|
+
@opinions = parse_opinions
|
20
|
+
@document = KAFDocument.new(
|
21
|
+
:words => words,
|
22
|
+
:terms => terms,
|
23
|
+
:entities => entities,
|
24
|
+
:properties => properties,
|
25
|
+
:opinions => opinions
|
26
|
+
)
|
27
|
+
|
28
|
+
return document
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_words
|
32
|
+
parse_elements("//wf", Word)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_terms
|
36
|
+
# Of course terms should be words here.
|
37
|
+
# Dirty Hack, sufficient for now.
|
38
|
+
parse_elements("//term", Term, :terms=>words)
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_entities
|
42
|
+
parse_elements("//entity", Entity, :terms=>terms)
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_properties
|
46
|
+
parse_elements("//property", Property, :terms=>terms)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_opinions
|
50
|
+
parse_elements("//opinion", Opinion, :terms=>terms)
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_elements(xpath, klass, opts={})
|
54
|
+
elements = doc.xpath(xpath)
|
55
|
+
lookup_table = Hash.new
|
56
|
+
elements.each do |element|
|
57
|
+
instance = klass.new(element, opts)
|
58
|
+
lookup_table[instance.id] = instance
|
59
|
+
end
|
60
|
+
lookup_table
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class KAFNode
|
66
|
+
attr_reader :content, :targets, :tag, :references
|
67
|
+
|
68
|
+
def initialize(tag, references)
|
69
|
+
@references = references
|
70
|
+
@tag = tag
|
71
|
+
|
72
|
+
set_instance_variables
|
73
|
+
set_content
|
74
|
+
set_targets
|
75
|
+
process_subnodes
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_content
|
79
|
+
@content = tag.content
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_instance_variables
|
83
|
+
tag.keys.each do |key|
|
84
|
+
if respond_to?("#{key}=".to_sym)
|
85
|
+
send("#{key}=".to_sym, tag[key])
|
86
|
+
else
|
87
|
+
instance_variable_set("@#{key}", tag[key])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_targets
|
93
|
+
@targets = []
|
94
|
+
tag.css("span target").each do |target|
|
95
|
+
id = target["id"]
|
96
|
+
@targets << references[:terms][id]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def process_subnodes
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_target?(*ids)
|
104
|
+
ids.flatten.each do |id|
|
105
|
+
return true if target_ids.include?(id)
|
106
|
+
end
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
if targets.size > 0
|
112
|
+
return targets.map(&:to_s).join(" ")
|
113
|
+
else
|
114
|
+
return content
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def target_ids
|
119
|
+
@targets.map(&:id)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Word < KAFNode
|
125
|
+
attr_reader :wid, :sent, :para, :offset
|
126
|
+
|
127
|
+
def id
|
128
|
+
wid
|
129
|
+
end
|
130
|
+
|
131
|
+
def offset=(offset)
|
132
|
+
@offset = offset.to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def length
|
136
|
+
content.nil? ? 0 : content.length
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class Term < KAFNode
|
142
|
+
attr_reader :tid, :type, :lemma, :pos, :morphofeat
|
143
|
+
|
144
|
+
def id
|
145
|
+
tid
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Entity < KAFNode
|
150
|
+
attr_reader :eid, :type
|
151
|
+
|
152
|
+
def id
|
153
|
+
eid
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
"#{type}: #{targets.map(&:to_s).join(", ")}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Property < KAFNode
|
162
|
+
attr_reader :pid, :lemma
|
163
|
+
|
164
|
+
def id
|
165
|
+
pid
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_s
|
169
|
+
"#{lemma}: #{targets.map(&:to_s).join(", ")}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class Opinion < KAFNode
|
174
|
+
attr_reader :oid, :expression
|
175
|
+
|
176
|
+
def id
|
177
|
+
oid
|
178
|
+
end
|
179
|
+
|
180
|
+
def process_subnodes
|
181
|
+
@expression = tag.xpath("//opinion_expression").first["polarity"].to_sym
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_s
|
185
|
+
"#{expression}: #{targets.map(&:to_s).join(", ")}"
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
class KAFDocument
|
191
|
+
attr_reader :words, :terms, :entities, :properties, :opinions
|
192
|
+
|
193
|
+
def initialize(opts={})
|
194
|
+
@words = opts.fetch(:words)
|
195
|
+
@terms = opts.fetch(:terms)
|
196
|
+
@entities = opts.fetch(:entities)
|
197
|
+
@properties = opts.fetch(:properties)
|
198
|
+
@opinions = opts.fetch(:opinions)
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
class HTMLTextPresenter
|
204
|
+
attr_reader :document
|
205
|
+
def initialize(document)
|
206
|
+
@document = document
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_html
|
210
|
+
offset = 0
|
211
|
+
prev = Struct.new(:offset).new(0)
|
212
|
+
|
213
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
214
|
+
html.div(:class=>"opener") do
|
215
|
+
html.p do
|
216
|
+
document.words.values.sort_by(&:offset).each do |word|
|
217
|
+
if offset < word.offset
|
218
|
+
spacer = word.offset - offset
|
219
|
+
spacer = Array.new(spacer, " ").join
|
220
|
+
html.span(spacer)
|
221
|
+
end
|
222
|
+
|
223
|
+
terms = targets_for(:terms, word.id)
|
224
|
+
entities = targets_for(:entities, terms)
|
225
|
+
opinions = opinions_for(terms)
|
226
|
+
properties = targets_for(:properties, terms)
|
227
|
+
|
228
|
+
generics = []
|
229
|
+
generics << "term" if terms.size > 0
|
230
|
+
generics << "entity" if entities.size > 0
|
231
|
+
generics << "opinion" if opinions.size > 0
|
232
|
+
generics << "property" if properties.size > 0
|
233
|
+
|
234
|
+
classes = [terms, entities, opinions, properties, generics].flatten.uniq
|
235
|
+
|
236
|
+
word_annotations = classes.join(" ")
|
237
|
+
|
238
|
+
html.span(word.content, :class=>word_annotations, :id=>word.id)
|
239
|
+
offset = word.offset + word.length
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
[:entities, :opinions, :properties].each do |sym|
|
244
|
+
html.div(:class=>sym) do
|
245
|
+
document.public_send(sym).values.each do |entity|
|
246
|
+
html.div(entity.to_s, :id=>entity.id, :class=>entity.target_ids.join(" "))
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
builder.to_html
|
254
|
+
end
|
255
|
+
|
256
|
+
def targets_for(variable, *ids)
|
257
|
+
targets = document.public_send(variable.to_sym).values.select do |value|
|
258
|
+
value.has_target?(ids.flatten)
|
259
|
+
end
|
260
|
+
|
261
|
+
targets.map(&:id)
|
262
|
+
end
|
263
|
+
|
264
|
+
def opinions_for(*ids)
|
265
|
+
targets = document.opinions.values.select do |value|
|
266
|
+
value.has_target?(ids.flatten)
|
267
|
+
end
|
268
|
+
|
269
|
+
ids = targets.map(&:id)
|
270
|
+
sentiments = targets.map(&:expression)
|
271
|
+
return ids.concat(sentiments).uniq
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
metadata
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opener-outlet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- development@olery.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: builder
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.4.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: httpclient
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: uuidtools
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jdbc-mysql
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord-jdbcmysql-adapter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activerecord
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.2'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: opener-webservice
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: cucumber
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: pry
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rake
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
description: Database storing for the web services output when using callbacks.
|
210
|
+
email:
|
211
|
+
executables:
|
212
|
+
- outlet-server
|
213
|
+
extensions: []
|
214
|
+
extra_rdoc_files: []
|
215
|
+
files:
|
216
|
+
- README.md
|
217
|
+
- bin/outlet-server
|
218
|
+
- config.ru
|
219
|
+
- config/database.rb
|
220
|
+
- lib/opener/outlet.rb
|
221
|
+
- lib/opener/outlet/output.rb
|
222
|
+
- lib/opener/outlet/public/outlet.css
|
223
|
+
- lib/opener/outlet/server.rb
|
224
|
+
- lib/opener/outlet/version.rb
|
225
|
+
- lib/opener/outlet/views/index.erb
|
226
|
+
- lib/opener/outlet/views/show.erb
|
227
|
+
- lib/opener/outlet/views/show.html.erb
|
228
|
+
- lib/opener/outlet/visualizer.rb
|
229
|
+
- opener-outlet.gemspec
|
230
|
+
- visualizer.rb
|
231
|
+
homepage: http://opener-project.github.com/
|
232
|
+
licenses: []
|
233
|
+
metadata: {}
|
234
|
+
post_install_message:
|
235
|
+
rdoc_options: []
|
236
|
+
require_paths:
|
237
|
+
- lib
|
238
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - ">="
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: 1.9.2
|
243
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
244
|
+
requirements:
|
245
|
+
- - ">="
|
246
|
+
- !ruby/object:Gem::Version
|
247
|
+
version: '0'
|
248
|
+
requirements: []
|
249
|
+
rubyforge_project:
|
250
|
+
rubygems_version: 2.2.2
|
251
|
+
signing_key:
|
252
|
+
specification_version: 4
|
253
|
+
summary: Database storing for the web services output when using callbacks.
|
254
|
+
test_files: []
|
255
|
+
has_rdoc: yard
|