opener-outlet 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|