scrappy 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|