scrappy 0.3.0 → 0.3.1
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/History.txt +6 -0
- data/Manifest +21 -14
- data/README.rdoc +5 -9
- data/Rakefile +1 -2
- data/bin/scrappy +141 -51
- data/lib/scrappy.rb +6 -9
- data/lib/scrappy/agent/agent.rb +3 -3
- data/lib/scrappy/extractor/extractor.rb +108 -0
- data/lib/scrappy/{agent → extractor}/formats.rb +0 -0
- data/lib/scrappy/extractor/fragment.rb +111 -0
- data/lib/scrappy/extractor/selector.rb +41 -0
- data/lib/scrappy/{selectors → extractor/selectors}/base_uri.rb +1 -3
- data/lib/scrappy/extractor/selectors/css.rb +5 -0
- data/lib/scrappy/{selectors → extractor/selectors}/new_uri.rb +1 -3
- data/lib/scrappy/{selectors → extractor/selectors}/root.rb +1 -4
- data/lib/scrappy/{selectors → extractor/selectors}/section.rb +1 -4
- data/lib/scrappy/{selectors → extractor/selectors}/slice.rb +1 -3
- data/lib/scrappy/{selectors → extractor/selectors}/uri.rb +2 -4
- data/lib/scrappy/{selectors → extractor/selectors}/uri_pattern.rb +2 -4
- data/lib/scrappy/extractor/selectors/visual.rb +39 -0
- data/lib/scrappy/{selectors → extractor/selectors}/xpath.rb +1 -4
- data/lib/scrappy/server/admin.rb +89 -2
- data/lib/scrappy/server/helpers.rb +11 -2
- data/lib/scrappy/server/server.rb +1 -0
- data/lib/scrappy/trainer/trainer.rb +101 -0
- data/public/javascripts/annotator.js +75 -0
- data/public/javascripts/remote.js +132 -0
- data/public/stylesheets/application.css +39 -12
- data/scrappy.gemspec +13 -11
- data/views/extractors.haml +24 -0
- data/views/layout.haml +14 -4
- data/views/patterns.haml +19 -0
- data/views/samples.haml +28 -0
- metadata +58 -56
- data/lib/scrappy/agent/extractor.rb +0 -196
- data/lib/scrappy/selectors/css.rb +0 -10
- data/public/javascripts/scrappy.js +0 -65
- data/views/kb.haml +0 -15
File without changes
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Sc
|
2
|
+
class Fragment
|
3
|
+
include RDF::NodeProxy
|
4
|
+
|
5
|
+
def extract options={}
|
6
|
+
uri = options[:doc][:uri]
|
7
|
+
|
8
|
+
# Identify the fragment's mappings
|
9
|
+
docs = sc::selector.map { |s| graph.node(s).select options[:doc] }.flatten
|
10
|
+
|
11
|
+
# Generate nodes for each page mapping
|
12
|
+
docs.map do |doc|
|
13
|
+
# Build RDF nodes from identifier selectors (if present)
|
14
|
+
nodes = self.nodes(uri, doc, options[:referenceable])
|
15
|
+
|
16
|
+
# Add info to each node
|
17
|
+
nodes.map do |node|
|
18
|
+
# Build the object -- it can be a node or a literal
|
19
|
+
object = if sc::type.include?(Node('rdf:Literal'))
|
20
|
+
value = doc[:value].to_s.strip
|
21
|
+
if options[:referenceable]
|
22
|
+
node.rdf::value = value
|
23
|
+
node.rdf::type = Node('rdf:Literal')
|
24
|
+
node
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# Add statements about the node
|
30
|
+
sc::type.each { |type| node.rdf::type += [type] if type != Node('rdf:Resource') }
|
31
|
+
sc::superclass.each { |superclass| node.rdfs::subClassOf += [superclass] }
|
32
|
+
sc::sameas.each { |samenode| node.owl::sameAs += [samenode] }
|
33
|
+
|
34
|
+
node
|
35
|
+
end
|
36
|
+
|
37
|
+
# Process subfragments
|
38
|
+
consistent = true
|
39
|
+
sc::subfragment.each do |subfragment|
|
40
|
+
# Get subfragment object
|
41
|
+
subfragment = graph.node(subfragment, Node('sc:Fragment'))
|
42
|
+
# Extract data from the subfragment
|
43
|
+
subnodes = subfragment.extract(options.merge(:doc=>doc))
|
44
|
+
|
45
|
+
# Add relations
|
46
|
+
subnodes.each do |subnode|
|
47
|
+
node.graph << subnode if subnode.is_a?(RDF::Node)
|
48
|
+
subfragment.sc::relation.each { |relation| node[relation] += [subnode] }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check consistency
|
52
|
+
consistent = false if subfragment.sc::min_cardinality.first and subnodes.size < subfragment.sc::min_cardinality.first.to_i
|
53
|
+
consistent = false if subfragment.sc::max_cardinality.first and subnodes.size > subfragment.sc::max_cardinality.first.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
# Skip the node if it has inconsistent relations
|
57
|
+
# For example: extracting a sioc:Post with no dc:title would
|
58
|
+
# violate the constraint sc:min_cardinality = 1
|
59
|
+
next if !consistent
|
60
|
+
|
61
|
+
# Add referenceable data if requested
|
62
|
+
if options[:referenceable]
|
63
|
+
sources = [doc[:content]].flatten.map { |n| Node(Scrappy::Extractor.node_hash(doc[:uri], n.path)) }
|
64
|
+
sources.each do |source|
|
65
|
+
sc::type.each { |type| source.sc::type += [type] }
|
66
|
+
sc::relation.each { |relation| source.sc::relation += [relation] }
|
67
|
+
node.graph << source
|
68
|
+
node.sc::source += [source]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Object points to either the node or the literal
|
73
|
+
object
|
74
|
+
end
|
75
|
+
end.flatten.compact
|
76
|
+
end
|
77
|
+
|
78
|
+
def nodes uri, doc, referenceable
|
79
|
+
nodes = sc::identifier.map { |s| graph.node(s).select doc }.flatten.map do |d|
|
80
|
+
node = Node(parse_uri(uri, d[:value]))
|
81
|
+
|
82
|
+
if referenceable
|
83
|
+
# Include the fragment where the URI was built from
|
84
|
+
uri_node = Node(nil, node.graph)
|
85
|
+
hash = Scrappy::Extractor.node_hash(d[:uri], d[:content].path)
|
86
|
+
|
87
|
+
node.sc::uri = uri_node
|
88
|
+
uri_node.rdf::value = node.to_s
|
89
|
+
uri_node.sc::source = Node(hash)
|
90
|
+
end
|
91
|
+
|
92
|
+
node
|
93
|
+
end
|
94
|
+
nodes << Node(nil) if nodes.empty?
|
95
|
+
|
96
|
+
nodes
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
# Parses a URI by resolving relative paths
|
101
|
+
def parse_uri(uri, rel_uri)
|
102
|
+
return ID('*') if rel_uri.nil?
|
103
|
+
begin
|
104
|
+
ID(URI::parse(uri.split('/')[0..3]*'/').merge(rel_uri).to_s)
|
105
|
+
rescue
|
106
|
+
ID('*')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Sc
|
2
|
+
class Selector
|
3
|
+
include RDF::NodeProxy
|
4
|
+
include Scrappy::Formats
|
5
|
+
|
6
|
+
def select doc
|
7
|
+
if sc::debug.first=="true" and Scrappy::Agent::Options.debug
|
8
|
+
puts '== DEBUG'
|
9
|
+
puts '== Selector:'
|
10
|
+
puts node.serialize(:yarf, false)
|
11
|
+
puts '== On fragment:'
|
12
|
+
puts "URI: #{doc[:uri]}"
|
13
|
+
puts "Content: #{doc[:content]}"
|
14
|
+
puts "Value: #{doc[:value]}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Process selector
|
18
|
+
# Filter method is defined in each subclass
|
19
|
+
results = filter doc
|
20
|
+
|
21
|
+
if sc::debug.first=="true" and Scrappy::Agent::Options.debug
|
22
|
+
puts "== No results" if results.empty?
|
23
|
+
results.each_with_index do |result, i|
|
24
|
+
puts "== Result ##{i}:"
|
25
|
+
puts "URI: #{result[:uri]}"
|
26
|
+
puts "Content: #{result[:content]}"
|
27
|
+
puts "Value: #{result[:value].inspect}"
|
28
|
+
end
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return results if no nested selectors
|
33
|
+
return results if sc::selector.empty?
|
34
|
+
|
35
|
+
# Process nested selectors
|
36
|
+
results.map do |result|
|
37
|
+
sc::selector.map { |s| graph.node(s).select result }
|
38
|
+
end.flatten
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,8 +1,5 @@
|
|
1
1
|
module Sc
|
2
|
-
class SectionSelector
|
3
|
-
include RDF::NodeProxy
|
4
|
-
include Scrappy::Formats
|
5
|
-
|
2
|
+
class SectionSelector < Selector
|
6
3
|
def filter doc
|
7
4
|
rdf::value.map do |pattern|
|
8
5
|
doc[:content].search('h1, h2, h3, h4, h5, h6, h7, h8, h9, h10').select { |n| n.parent.name!='script' and n.text.downcase.strip == pattern }.map do |node|
|
@@ -1,11 +1,9 @@
|
|
1
1
|
module Sc
|
2
|
-
class UriSelector
|
3
|
-
include RDF::NodeProxy
|
4
|
-
|
2
|
+
class UriSelector < Selector
|
5
3
|
def filter doc
|
6
4
|
# Check if the UriSelector has this URI as value (without params: ?param1=value1¶m2=value2)
|
7
5
|
if rdf::value.include?(doc[:uri].match(/\A([^\?]*)(\?.*\Z)?/).captures.first)
|
8
|
-
[ { :uri=>doc[:uri], :content=>doc[:content], :value=>doc[:
|
6
|
+
[ { :uri=>doc[:uri], :content=>doc[:content], :value=>format(doc[:value], sc::format, doc[:uri]) } ]
|
9
7
|
else
|
10
8
|
[]
|
11
9
|
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
module Sc
|
2
|
-
class UriPatternSelector
|
3
|
-
include RDF::NodeProxy
|
4
|
-
|
2
|
+
class UriPatternSelector < Selector
|
5
3
|
def filter doc
|
6
4
|
# Check if the uri fits the pattern
|
7
5
|
if rdf::value.any? { |v| doc[:uri] =~ /\A#{v.gsub('.','\.').gsub('*', '.+')}\Z/ }
|
8
|
-
[ { :uri=>doc[:uri], :content=>doc[:content], :value=>doc[:
|
6
|
+
[ { :uri=>doc[:uri], :content=>doc[:content], :value=>format(doc[:value], sc::format, doc[:uri]) } ]
|
9
7
|
else
|
10
8
|
[]
|
11
9
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sc
|
2
|
+
class VisualSelector < Selector
|
3
|
+
def filter doc
|
4
|
+
doc[:content].search(sc::tag.first || "*").select do |node|
|
5
|
+
relative_x = node['vx'].to_i - doc[:content]['vx'].to_i
|
6
|
+
relative_y = node['vy'].to_i - doc[:content]['vy'].to_i
|
7
|
+
|
8
|
+
!node.text? and
|
9
|
+
( !sc::min_relative_x.first or relative_x >= sc::min_relative_x.first.to_i) and
|
10
|
+
( !sc::max_relative_x.first or relative_x <= sc::max_relative_x.first.to_i) and
|
11
|
+
( !sc::min_relative_y.first or relative_y >= sc::min_relative_y.first.to_i) and
|
12
|
+
( !sc::max_relative_y.first or relative_y <= sc::max_relative_y.first.to_i) and
|
13
|
+
|
14
|
+
( !sc::min_x.first or node['vx'].to_i >= sc::min_x.first.to_i) and
|
15
|
+
( !sc::max_x.first or node['vx'].to_i <= sc::max_x.first.to_i) and
|
16
|
+
( !sc::min_y.first or node['vy'].to_i >= sc::min_y.first.to_i) and
|
17
|
+
( !sc::max_y.first or node['vy'].to_i <= sc::max_y.first.to_i) and
|
18
|
+
|
19
|
+
( !sc::min_width.first or node['vw'].to_i >= sc::min_width.first.to_i) and
|
20
|
+
( !sc::max_width.first or node['vw'].to_i <= sc::max_width.first.to_i) and
|
21
|
+
( !sc::min_height.first or node['vh'].to_i >= sc::min_height.first.to_i) and
|
22
|
+
( !sc::max_height.first or node['vh'].to_i <= sc::max_height.first.to_i) and
|
23
|
+
|
24
|
+
( !sc::min_font_size.first or node['vsize'].to_i >= sc::min_font_size.first.to_i) and
|
25
|
+
( !sc::max_font_size.first or node['vsize'].to_i <= sc::max_font_size.first.to_i) and
|
26
|
+
( !sc::min_font_weight.first or node['vweight'].to_i >= sc::min_font_weight.first.to_i) and
|
27
|
+
( !sc::max_font_weight.first or node['vweight'].to_i <= sc::max_font_weight.first.to_i) and
|
28
|
+
( !sc::font_family.first or node['vfont'] == sc::font_family.first)
|
29
|
+
end.map do |content|
|
30
|
+
if sc::attribute.first
|
31
|
+
# Select node's attribute if given
|
32
|
+
sc::attribute.map { |attribute| { :uri=>doc[:uri], :content=>content, :value=>content[attribute] } }
|
33
|
+
else
|
34
|
+
[ { :uri=>doc[:uri], :content=>content, :value=>format(content, sc::format, doc[:uri]) } ]
|
35
|
+
end
|
36
|
+
end.flatten
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/scrappy/server/admin.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
require 'rack-flash'
|
3
|
+
|
1
4
|
module Scrappy
|
2
5
|
module Admin
|
3
6
|
def self.registered app
|
7
|
+
app.set :method_override, true
|
8
|
+
app.use Rack::Flash
|
9
|
+
|
4
10
|
app.get '/' do
|
5
11
|
if params[:format] and params[:uri]
|
6
12
|
redirect "#{settings.base_uri}/#{params[:format]}/#{simplify_uri(params[:uri])}"
|
@@ -9,15 +15,96 @@ module Scrappy
|
|
9
15
|
end
|
10
16
|
end
|
11
17
|
|
18
|
+
app.get '/javascript' do
|
19
|
+
fragments = agent.fragments_for(Scrappy::Kb.extractors, params[:uri])
|
20
|
+
content_type 'application/javascript'
|
21
|
+
"window.scrappy_extractor=#{fragments.any?};" + open("#{settings.public}/javascripts/annotator.js").read
|
22
|
+
end
|
23
|
+
|
12
24
|
app.get '/help' do
|
13
25
|
haml :help
|
14
26
|
end
|
15
27
|
|
16
|
-
|
28
|
+
# Extractors
|
29
|
+
|
30
|
+
app.get '/extractors' do
|
17
31
|
@uris = ( Agent::Options.kb.find(nil, Node('rdf:type'), Node('sc:UriSelector')) +
|
18
32
|
Agent::Options.kb.find(nil, Node('rdf:type'), Node('sc:UriPatternSelector')) ).
|
19
33
|
map { |node| node.rdf::value }.flatten.sort.map(&:to_s)
|
20
|
-
haml :
|
34
|
+
haml :extractors
|
35
|
+
end
|
36
|
+
|
37
|
+
app.post '/extractors' do
|
38
|
+
if params[:html]
|
39
|
+
# Generate extractor automatically
|
40
|
+
iconv = Iconv.new(params[:encoding], 'UTF-8')
|
41
|
+
html = iconv.iconv(params[:html])
|
42
|
+
puts params[:html]
|
43
|
+
puts params[:uri]
|
44
|
+
raise Exception, "Automatic generation of extractors is not supported yet"
|
45
|
+
else
|
46
|
+
# Store the given extractor
|
47
|
+
Scrappy::App.add_extractor RDF::Parser.parse(:yarf,params[:rdf])
|
48
|
+
end
|
49
|
+
flash[:notice] = "Extractor stored"
|
50
|
+
redirect "#{settings.base_uri}/extractors"
|
51
|
+
end
|
52
|
+
app.delete '/extractors/*' do |uri|
|
53
|
+
Scrappy::App.delete_extractor uri
|
54
|
+
flash[:notice] = "Extractor deleted"
|
55
|
+
redirect "#{settings.base_uri}/extractors"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Patterns
|
59
|
+
|
60
|
+
app.get '/patterns' do
|
61
|
+
@uris = Scrappy::Kb.patterns.find(nil, Node('rdf:type'), Node('sc:Fragment')).
|
62
|
+
map { |node| node.sc::type }.flatten.map(&:to_s).sort
|
63
|
+
haml :patterns
|
64
|
+
end
|
65
|
+
|
66
|
+
app.delete '/patterns/*' do |uri|
|
67
|
+
Scrappy::App.delete_pattern uri
|
68
|
+
flash[:notice] = "Pattern deleted"
|
69
|
+
redirect "#{settings.base_uri}/patterns"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Samples
|
73
|
+
|
74
|
+
app.get '/samples' do
|
75
|
+
@samples = Scrappy::App.samples
|
76
|
+
haml :samples
|
77
|
+
end
|
78
|
+
|
79
|
+
app.get '/samples/:id' do |id|
|
80
|
+
Scrappy::App.samples[id.to_i][:html]
|
81
|
+
end
|
82
|
+
|
83
|
+
app.get '/samples/:id/:kb_type' do |id,kb_type|
|
84
|
+
kb = (kb_type == "patterns" ? Scrappy::Kb.patterns : Scrappy::Kb.extractors)
|
85
|
+
sample = Scrappy::App.samples[id.to_i]
|
86
|
+
headers 'Content-Type' => 'text/plain'
|
87
|
+
RDF::Graph.new(agent.extract(sample[:uri], sample[:html], kb, Agent::Options.referenceable)).serialize(:yarf)
|
88
|
+
end
|
89
|
+
|
90
|
+
app.post '/samples/:id/train' do |id|
|
91
|
+
new_extractor = agent.train Scrappy::App.samples[id.to_i]
|
92
|
+
Scrappy::App.add_pattern new_extractor
|
93
|
+
flash[:notice] = "Training completed"
|
94
|
+
redirect "#{settings.base_uri}/samples"
|
95
|
+
end
|
96
|
+
|
97
|
+
app.post '/samples' do
|
98
|
+
html = Iconv.iconv('UTF-8', params[:encoding], params[:html]).first
|
99
|
+
sample = Scrappy::App.add_sample(:html=>html, :uri=>params[:uri], :date=>Time.now)
|
100
|
+
flash[:notice] = "Sample stored"
|
101
|
+
redirect "#{settings.base_uri}/samples"
|
102
|
+
end
|
103
|
+
|
104
|
+
app.delete '/samples/:id' do |id|
|
105
|
+
Scrappy::App.delete_sample id.to_i
|
106
|
+
flash[:notice] = "Sample deleted"
|
107
|
+
redirect "#{settings.base_uri}/samples"
|
21
108
|
end
|
22
109
|
end
|
23
110
|
end
|
@@ -6,10 +6,19 @@ module Scrappy
|
|
6
6
|
"var e=document.createElement('script');" +
|
7
7
|
"e.src='https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';" +
|
8
8
|
"e.id='scrappy';" +
|
9
|
-
"document.getElementsByTagName('head')[0].appendChild(e);
|
9
|
+
"document.getElementsByTagName('head')[0].appendChild(e);" +
|
10
|
+
"e=document.createElement('script');" +
|
11
|
+
"e.src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/jquery-ui.min.js';" +
|
12
|
+
"document.getElementsByTagName('head')[0].appendChild(e);" +
|
13
|
+
"e=document.createElement('link');" +
|
14
|
+
"e.href='http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.10/themes/ui-lightness/jquery-ui.css';" +
|
15
|
+
"e.rel='stylesheet';" +
|
16
|
+
"e.type='text/css';" +
|
17
|
+
"document.getElementsByTagName('head')[0].appendChild(e);" +
|
18
|
+
"};" +
|
10
19
|
"if(!window.scrappy_loaded){" +
|
11
20
|
"e=document.createElement('script');" +
|
12
|
-
"e.src='http://localhost:3434/
|
21
|
+
"e.src='http://localhost:3434/javascript?#{Time.now.to_i}&uri='+escape(window.location);" +
|
13
22
|
"e.onerror=function(){alert('Error: Please start Scrappy Server at http://localhost:3434');};" +
|
14
23
|
"document.getElementsByTagName('head')[0].appendChild(e);" +
|
15
24
|
"}"+
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Scrappy
|
2
|
+
module Trainer
|
3
|
+
# Generates visual patterns
|
4
|
+
def train *samples
|
5
|
+
RDF::Graph.new( samples.inject([]) do |triples, sample|
|
6
|
+
triples + train_sample(sample).triples
|
7
|
+
end )
|
8
|
+
end
|
9
|
+
|
10
|
+
# Optimizes the knowledge base by generalizing patterns
|
11
|
+
def optimize
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def train_sample sample
|
16
|
+
results = RDF::Graph.new extract(sample[:uri], sample[:html], Scrappy::Kb.extractors, :minimum)
|
17
|
+
|
18
|
+
typed_nodes = results.find(nil, Node("rdf:type"), [])
|
19
|
+
non_root_nodes = results.find([], [], nil)
|
20
|
+
|
21
|
+
nodes = typed_nodes - non_root_nodes
|
22
|
+
|
23
|
+
RDF::Graph.new( nodes.inject([]) do |triples, node|
|
24
|
+
triples + fragment_for(node).graph.triples
|
25
|
+
end )
|
26
|
+
end
|
27
|
+
|
28
|
+
def fragment_for node, parent=nil
|
29
|
+
fragment = Node(nil)
|
30
|
+
node.keys.each do |predicate|
|
31
|
+
case predicate
|
32
|
+
when ID("sc:source") then
|
33
|
+
selector = selector_for(node.sc::source.first, parent)
|
34
|
+
fragment.graph << selector
|
35
|
+
fragment.sc::selector = selector
|
36
|
+
when ID("sc:uri") then
|
37
|
+
# Assumption: URIs are extracted from a link
|
38
|
+
selector = selector_for(node.sc::uri.first.sc::source.first, node)
|
39
|
+
selector.sc::tag = "a"
|
40
|
+
selector.sc::attribute = "href"
|
41
|
+
|
42
|
+
fragment.graph << selector
|
43
|
+
fragment.sc::identifier = selector
|
44
|
+
when ID("rdf:type") then
|
45
|
+
fragment.sc::type = node.rdf::type
|
46
|
+
else
|
47
|
+
if node[predicate].map(&:class).uniq.first != String
|
48
|
+
subfragments = node[predicate].map { |subnode| fragment_for(subnode, node) }
|
49
|
+
# Mix the subfragments
|
50
|
+
id = subfragments.first
|
51
|
+
graph = RDF::Graph.new( subfragments.inject([]) do |triples, subfragment|
|
52
|
+
triples + subfragment.graph.triples.map { |s,p,o| [s==subfragment.id ? id : s,p,o] }
|
53
|
+
end )
|
54
|
+
subfragment = graph[id]
|
55
|
+
subfragment.sc::relation = Node(predicate)
|
56
|
+
subfragment.sc::min_cardinality = "1"
|
57
|
+
|
58
|
+
fragment.graph << subfragment
|
59
|
+
fragment.sc::subfragment += [subfragment]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
fragment.rdf::type = Node("sc:Fragment") if parent.nil?
|
64
|
+
fragment
|
65
|
+
end
|
66
|
+
|
67
|
+
def selector_for fragment, parent=nil
|
68
|
+
presentation = fragment.sc::presentation.first
|
69
|
+
|
70
|
+
selector = Node(nil)
|
71
|
+
selector.rdf::type = Node("sc:VisualSelector")
|
72
|
+
|
73
|
+
origin_x = parent ? parent.sc::source.first.sc::presentation.first.sc::x.first.to_i : 0
|
74
|
+
origin_y = parent ? parent.sc::source.first.sc::presentation.first.sc::y.first.to_i : 0
|
75
|
+
|
76
|
+
relative_x = presentation.sc::x.first.to_i - origin_x
|
77
|
+
relative_y = presentation.sc::y.first.to_i - origin_y
|
78
|
+
|
79
|
+
selector.sc::min_relative_x = relative_x.to_s
|
80
|
+
selector.sc::max_relative_x = relative_x.to_s
|
81
|
+
selector.sc::min_relative_y = relative_y.to_s
|
82
|
+
selector.sc::max_relative_y = relative_y.to_s
|
83
|
+
selector.sc::min_x = presentation.sc::x
|
84
|
+
selector.sc::max_x = presentation.sc::x
|
85
|
+
selector.sc::min_y = presentation.sc::y
|
86
|
+
selector.sc::max_y = presentation.sc::y
|
87
|
+
|
88
|
+
selector.sc::min_width = presentation.sc::width
|
89
|
+
selector.sc::max_width = presentation.sc::width
|
90
|
+
selector.sc::min_height = presentation.sc::height
|
91
|
+
selector.sc::max_height = presentation.sc::height
|
92
|
+
selector.sc::min_font_size = presentation.sc::font_size
|
93
|
+
selector.sc::max_font_size = presentation.sc::font_size
|
94
|
+
selector.sc::min_font_weight = presentation.sc::font_weight
|
95
|
+
selector.sc::max_font_weight = presentation.sc::font_weight
|
96
|
+
selector.sc::font_family = presentation.sc::font_family
|
97
|
+
|
98
|
+
selector
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|