codemodels-html 0.1.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/Gemfile +4 -0
- data/LICENSE +191 -0
- data/README.md +30 -0
- data/Rakefile +9 -0
- data/codemodels-html.gemspec +30 -0
- data/lib/codemodels/html/angular_embedding_rules.rb +87 -0
- data/lib/codemodels/html/language.rb +18 -0
- data/lib/codemodels/html/metamodel.rb +48 -0
- data/lib/codemodels/html/model_building.rb +32 -0
- data/lib/codemodels/html/monkey_patching.rb +18 -0
- data/lib/codemodels/html/parser.rb +272 -0
- data/lib/codemodels/html/version.rb +5 -0
- data/lib/codemodels/html.rb +8 -0
- data/lib/jars/jericho-html-3.3.jar +0 -0
- data/test/data/puzzle.html +67 -0
- data/test/test_basic_info.rb +67 -0
- data/test/test_basic_parsing.rb +79 -0
- data/test/test_helper.rb +74 -0
- data/test/test_info_extraction.rb +37 -0
- data/test/test_monkey_patching.rb +20 -0
- data/test/test_parsing_embedded_languages.rb +54 -0
- data/test/test_parsing_puzzle.rb +170 -0
- metadata +173 -0
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'jars/jericho-html-3.3.jar'
|
2
|
+
require 'codemodels'
|
3
|
+
require 'codemodels/html/monkey_patching'
|
4
|
+
|
5
|
+
module CodeModels
|
6
|
+
module Html
|
7
|
+
|
8
|
+
class TextBlock
|
9
|
+
attr_accessor :source
|
10
|
+
attr_accessor :value
|
11
|
+
|
12
|
+
def begin_point=(data)
|
13
|
+
@source = SourceInfo.new unless @source
|
14
|
+
@source.begin_point= data
|
15
|
+
end
|
16
|
+
|
17
|
+
def end_point=(data)
|
18
|
+
@source = SourceInfo.new unless @source
|
19
|
+
@source.end_point= data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Java::NetHtmlparserJericho::Element
|
24
|
+
|
25
|
+
def text_blocks(code)
|
26
|
+
blocks = []
|
27
|
+
break_content(self,code).each do |s,e|
|
28
|
+
text = code[s,e-s]
|
29
|
+
unless text==nil or text.strip.empty?
|
30
|
+
#puts "<<<#{text}>>>"
|
31
|
+
block = TextBlock.new
|
32
|
+
block.value = text
|
33
|
+
block.source = SourceInfo.new
|
34
|
+
block.source.position = SourcePosition.from_code_indexes(code,s,e-1)
|
35
|
+
|
36
|
+
blocks << block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
blocks
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def break_content(node,code)
|
45
|
+
text_inside = code[(node.begin)...(node.end)]
|
46
|
+
#puts "Text inside #{node.name} ^#{text_inside}^ It has child elements #{node.child_elements}"
|
47
|
+
i = text_inside.first_index('>')
|
48
|
+
raise "No '>' found in node #{node}, text inside: '#{text_inside}'" unless i
|
49
|
+
#puts "Index i: #{i}"
|
50
|
+
raise "No Fixnum" unless node.begin.is_a?(Fixnum)
|
51
|
+
raise "No Fixnum" unless i.is_a?(Fixnum)
|
52
|
+
start_index = node.begin+i+1
|
53
|
+
li = text_inside.last_index('<')
|
54
|
+
#puts "Index li: #{li}"
|
55
|
+
end_index = node.begin+li
|
56
|
+
#puts "Indexes #{start_index} #{end_index}"
|
57
|
+
#puts "Content of #{node.name} ^#{code[start_index,end_index-start_index]}^"
|
58
|
+
|
59
|
+
# no content
|
60
|
+
return [] if start_index==end_index
|
61
|
+
|
62
|
+
if node.child_elements.count==0
|
63
|
+
return [[start_index,end_index]]
|
64
|
+
else
|
65
|
+
segments = []
|
66
|
+
# before the first
|
67
|
+
segments << [start_index,node.child_elements.first.begin]
|
68
|
+
# between children
|
69
|
+
for i in 0...(node.child_elements.count-1)
|
70
|
+
s = node.child_elements[i].end
|
71
|
+
e = node.child_elements[i+1].begin
|
72
|
+
segments << [s,e]
|
73
|
+
end
|
74
|
+
# after the last
|
75
|
+
i_last = node.child_elements.size-1
|
76
|
+
last_child = node.child_elements[i_last]
|
77
|
+
segments << [last_child.end,end_index]
|
78
|
+
end
|
79
|
+
segments
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class Parser < CodeModels::Parser
|
85
|
+
|
86
|
+
def initialize
|
87
|
+
@embedded_parsers = Hash.new do |h,k|
|
88
|
+
h[k] = []
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_file(path)
|
93
|
+
code = IO.read(path)
|
94
|
+
parse_artifact(FileArtifact.new(path,code))
|
95
|
+
end
|
96
|
+
|
97
|
+
def raw_node_tree(code)
|
98
|
+
Java::net.htmlparser.jericho.Config.IsHTMLEmptyElementTagRecognised = true
|
99
|
+
xhtml = Java::net.htmlparser.jericho.Config::CompatibilityMode::XHTML
|
100
|
+
Java::net.htmlparser.jericho.Config.CurrentCompatibilityMode = xhtml
|
101
|
+
reader = java.io.StringReader.new code
|
102
|
+
source = Java::net.htmlparser.jericho.Source.new reader
|
103
|
+
source
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_code(code)
|
107
|
+
parse_artifact(FileArtifact.new('<code>',code))
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_artifact(artifact)
|
111
|
+
source = raw_node_tree(artifact.code)
|
112
|
+
node_to_model(source,artifact.code,artifact)
|
113
|
+
end
|
114
|
+
|
115
|
+
# It operates on original node, not on the model obtained because
|
116
|
+
# it could have less information. For example in parsing scripts I need the
|
117
|
+
# raw content
|
118
|
+
def register_embedded_parser(node_class,embedded_parser,&selector)
|
119
|
+
@embedded_parsers[node_class] << {embedded_parser: embedded_parser, selector: selector}
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.node_content(node,code)
|
123
|
+
pos = node_content_pos(node,code)
|
124
|
+
code[pos[0]..pos[1]]
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.node_content_pos(node,code)
|
128
|
+
text_inside = code[(node.begin)...(node.end)]
|
129
|
+
i = text_inside.first_index('>')
|
130
|
+
start_index = node.begin+i+1
|
131
|
+
li = text_inside.last_index('<')
|
132
|
+
end_index = node.begin+li-1
|
133
|
+
raise "problem" if start_index>end_index
|
134
|
+
#content = code[start_index,end_index-start_index]
|
135
|
+
[start_index,end_index]
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def add_source_info(node,model,code,artifact)
|
141
|
+
return if model==nil
|
142
|
+
model.language = LANGUAGE
|
143
|
+
model.source = CodeModels::SourceInfo.new
|
144
|
+
model.source.artifact = artifact
|
145
|
+
model.source.position = SourcePosition.from_code_indexes(code,node.begin,node.end)
|
146
|
+
end
|
147
|
+
|
148
|
+
def analyze_content(model,node,code,artifact)
|
149
|
+
node.text_blocks(code).each do |tb|
|
150
|
+
raise "GOTCHA #{node.name} TEXT INSIDE '#{code[(node.begin)..(node.end)]}'" if (tb.value=='</head>')
|
151
|
+
t = Html::Text.new
|
152
|
+
t.value = tb.value
|
153
|
+
|
154
|
+
t.language = LANGUAGE
|
155
|
+
t.source = tb.source
|
156
|
+
t.source.artifact = artifact
|
157
|
+
|
158
|
+
model.addChildren(t)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def node_to_model(node,code,artifact)
|
163
|
+
if node.is_a? Java::NetHtmlparserJericho::Source
|
164
|
+
model = Html::HtmlDocument.new
|
165
|
+
translate_many(code,node,model,:children,node.child_elements,artifact)
|
166
|
+
model
|
167
|
+
elsif node.is_a? Java::NetHtmlparserJericho::Element
|
168
|
+
if node.name=='!doctype'
|
169
|
+
model = Html::DTD.new
|
170
|
+
# I am naughty... and waiting for the Jericho parser to fix
|
171
|
+
# how they parse doctypes
|
172
|
+
model.name = 'html'
|
173
|
+
model
|
174
|
+
elsif node.name=='script'
|
175
|
+
model = Html::Script.new
|
176
|
+
model.name = node.name
|
177
|
+
# something is obfuscating the attributes method... damn...
|
178
|
+
attributes_method = node.java_method(:getAttributes)
|
179
|
+
attributes = attributes_method.call
|
180
|
+
raise "Error, Attributes expected, instead it is '#{attributes.class}'. Node class #{node.class}" unless attributes.is_a?(Java::NetHtmlparserJericho::Attributes)
|
181
|
+
if attributes.get('type') && attributes.get('type').value=='text/ng-template'
|
182
|
+
content_pos = Parser.node_content_pos(node,code)
|
183
|
+
#raise "mismatch" unless content==embedded_artifact.code
|
184
|
+
embedded_artifact = EmbeddedArtifact.new
|
185
|
+
embedded_artifact.host_artifact = artifact
|
186
|
+
si = content_pos[0]
|
187
|
+
while code[si]==' '||code[si]=="\t"||code[si]=="\r"||code[si]=="\n"
|
188
|
+
si+=1
|
189
|
+
end
|
190
|
+
embedded_artifact.position_in_host = SourcePosition.from_code_indexes(code,si,content_pos[1])
|
191
|
+
script_doc = parse_artifact(embedded_artifact)
|
192
|
+
model.addForeign_asts script_doc
|
193
|
+
end
|
194
|
+
else
|
195
|
+
model = Html::Node.new
|
196
|
+
analyze_content(model,node,code,artifact)
|
197
|
+
model.name = node.name
|
198
|
+
translate_many(code,node,model,:children,node.child_elements,artifact)
|
199
|
+
end
|
200
|
+
translate_many(code,node,model,:attributes,artifact)
|
201
|
+
model
|
202
|
+
elsif node.is_a? Java::NetHtmlparserJericho::Attribute
|
203
|
+
model = Html::Attribute.new
|
204
|
+
model.name = node.name
|
205
|
+
model.value = node.value
|
206
|
+
model
|
207
|
+
else
|
208
|
+
raise "Unknown node class: #{node.class}"
|
209
|
+
end
|
210
|
+
|
211
|
+
add_source_info(node,model,code,artifact)
|
212
|
+
check_foreign_parser(node,code,model,artifact)
|
213
|
+
model
|
214
|
+
end
|
215
|
+
|
216
|
+
def check_foreign_parser(node,code,model,artifact)
|
217
|
+
@embedded_parsers[node.class].each do |ep|
|
218
|
+
selector = ep[:selector]
|
219
|
+
embedded_parser = ep[:embedded_parser]
|
220
|
+
embedded_position = selector.call(node,code)
|
221
|
+
if embedded_position
|
222
|
+
unless embedded_position.is_a?(Array)
|
223
|
+
embedded_position = [embedded_position]
|
224
|
+
end
|
225
|
+
embedded_position.each do |ep|
|
226
|
+
embedded_artifact = EmbeddedArtifact.new
|
227
|
+
embedded_artifact.host_artifact = artifact
|
228
|
+
embedded_artifact.position_in_host = ep
|
229
|
+
#puts "<<<#{embedded_code}>>> #{ep}"
|
230
|
+
begin
|
231
|
+
embedded_root = embedded_parser.parse_artifact(embedded_artifact)
|
232
|
+
rescue Exception => e
|
233
|
+
raise "Problem embedded in '#{node}' at #{model.source.position} parsing '#{embedded_artifact.code}', from position #{ep}: #{e.inspect}"
|
234
|
+
end
|
235
|
+
model.addForeign_asts(embedded_root)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def translate_many(code,node,model,dest,node_value=(node.send(dest)),artifact)
|
242
|
+
return unless node_value!=nil
|
243
|
+
#puts "Considering #{model.class}.#{dest} (#{node_value.class})"
|
244
|
+
node_value.each do |el|
|
245
|
+
#puts "\t* #{el.class}"
|
246
|
+
model_el = node_to_model(el,code,artifact)
|
247
|
+
model.send(:"add#{dest.to_s.proper_capitalize}", model_el) if model_el!=nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end # class Parser
|
252
|
+
|
253
|
+
DefaultParser = Parser.new
|
254
|
+
|
255
|
+
def self.parse_artifact(artifact)
|
256
|
+
DefaultParser.parse_artifact(artifact)
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.parse_code(code)
|
260
|
+
parse_file(code,'<code>')
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.parse_file(code,filename)
|
264
|
+
parse_artifact(FileArtifact.new(filename,code))
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.raw_node_tree(code)
|
268
|
+
DefaultParser.raw_node_tree(code)
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'jars/jericho-html-3.3.jar'
|
2
|
+
|
3
|
+
require "codemodels/html/version"
|
4
|
+
require "codemodels/html/metamodel"
|
5
|
+
require "codemodels/html/parser"
|
6
|
+
require "codemodels/html/model_building"
|
7
|
+
require "codemodels/html/language"
|
8
|
+
require "codemodels/html/angular_embedding_rules"
|
Binary file
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>AngularJS puzzle</title>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
6
|
+
<script type="text/javascript" src="js/lib/angular/angular.js"></script>
|
7
|
+
<script type="text/javascript" src="js/puzzle/slidingPuzzle.js"></script>
|
8
|
+
<script type="text/javascript" src="js/puzzle/wordSearchPuzzle.js"></script>
|
9
|
+
<script type="text/javascript" src="js/app.js"></script>
|
10
|
+
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
|
11
|
+
</head>
|
12
|
+
<body ng-app="puzzleApp">
|
13
|
+
<ul id="types">
|
14
|
+
<li ng-repeat="t in types" ng-class="{'selected': t.id == type}">
|
15
|
+
<a ng-href="#/{{t.id}}">{{t.title}}</a>
|
16
|
+
</li>
|
17
|
+
</ul>
|
18
|
+
|
19
|
+
<div ng-include="type"></div>
|
20
|
+
|
21
|
+
<a id="fork" href="https://github.com/pdanis/angular-puzzle" title="Fork me on GitHub"></a>
|
22
|
+
<a id="powered" href="http://angularjs.org" title="powered by AngularJS"><img src="http://www.angularjs.org/img/AngularJS-large.png"/></a>
|
23
|
+
|
24
|
+
<script type="text/ng-template" id="sliding-puzzle">
|
25
|
+
<fieldset id="sliding-simple">
|
26
|
+
<legend>Basic</legend>
|
27
|
+
<sliding-puzzle size="3x3" src="img/angular.png"></sliding-puzzle>
|
28
|
+
</fieldset><br/>
|
29
|
+
|
30
|
+
<fieldset id="sliding-advanced" ng-controller="slidingAdvancedCtrl">
|
31
|
+
<legend>Advanced</legend>
|
32
|
+
<div ng-repeat="puzzle in puzzles">
|
33
|
+
<h2>{{puzzle.title}}</h2>
|
34
|
+
<div class="src">
|
35
|
+
<input type="text" ng-model="puzzle.src"/>
|
36
|
+
</div>
|
37
|
+
<div class="status">
|
38
|
+
moves: <strong>{{puzzle.api.moves}}</strong><br/>
|
39
|
+
solved: <strong>{{puzzle.api.isSolved()}}</strong>
|
40
|
+
</div>
|
41
|
+
<div class="size">
|
42
|
+
rows: <input type="text" ng-model="puzzle.rows" size="2"/>
|
43
|
+
cols: <input type="text" ng-model="puzzle.cols" size="2"/><br/>
|
44
|
+
<button ng-click="puzzle.api.shuffle()">shuffle</button>
|
45
|
+
<button ng-click="puzzle.api.solve()">solve</button>
|
46
|
+
</div>
|
47
|
+
<sliding-puzzle api="puzzle.api" size="{{puzzle.rows}}x{{puzzle.cols}}" src="{{puzzle.src}}"></sliding-puzzle>
|
48
|
+
</div>
|
49
|
+
</fieldset>
|
50
|
+
</script>
|
51
|
+
|
52
|
+
<script type="text/ng-template" id="word-search-puzzle">
|
53
|
+
<div id="word-search" ng-controller="wordSearchCtrl">
|
54
|
+
<ul class="words">
|
55
|
+
<li ng-repeat="word in puzzle.words" ng-class="{'found': word.found}">
|
56
|
+
{{word.name}}
|
57
|
+
</li>
|
58
|
+
</ul>
|
59
|
+
<word-search-puzzle matrix="matrix" words="words" api="puzzle"></word-search-puzzle>
|
60
|
+
<div class="status">
|
61
|
+
<button ng-click="puzzle.solve()" ng-show="!puzzle.solved" class="solve">solve</button>
|
62
|
+
<strong ng-show="puzzle.solved" ng-show="puzzle.solved">Solved!</strong>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</script>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestBasicInfo < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
include CodeModels
|
7
|
+
include CodeModels::Html
|
8
|
+
|
9
|
+
def test_source_line
|
10
|
+
code = %q{<html>
|
11
|
+
<body>
|
12
|
+
<p>ciao
|
13
|
+
come
|
14
|
+
stai?</p>
|
15
|
+
<p>io bene</p>
|
16
|
+
<div><p>
|
17
|
+
<span></span>
|
18
|
+
</p></div>
|
19
|
+
</body>
|
20
|
+
</html>}
|
21
|
+
r = Html.parse_code(code)
|
22
|
+
assert_class HtmlDocument, r
|
23
|
+
span = nil
|
24
|
+
r.traverse {|n| span = n if n.is_a?(Node) && n.name=='span'}
|
25
|
+
assert_not_nil span
|
26
|
+
assert_equal 8,span.source.position.begin_point.line
|
27
|
+
assert_class Node, span.eContainer
|
28
|
+
assert_equal 'p',span.eContainer.name
|
29
|
+
assert_equal 7,span.eContainer.source.position.begin_point.line # p
|
30
|
+
assert_equal 'div',span.eContainer.eContainer.name
|
31
|
+
assert_equal 7,span.eContainer.eContainer.source.position.begin_point.line # div
|
32
|
+
assert_equal 'body',span.eContainer.eContainer.eContainer.name
|
33
|
+
assert_equal 2,span.eContainer.eContainer.eContainer.source.position.begin_point.line # body
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_source_line_of_text_elements
|
37
|
+
code = %q{<html>
|
38
|
+
<body>
|
39
|
+
<p>ciao
|
40
|
+
come
|
41
|
+
stai?</p>
|
42
|
+
<p>io bene</p>
|
43
|
+
<div><p>
|
44
|
+
<span></span>
|
45
|
+
</p></div>
|
46
|
+
</body>
|
47
|
+
</html>}
|
48
|
+
r = Html.parse_code(code)
|
49
|
+
assert_class HtmlDocument, r
|
50
|
+
body = nil
|
51
|
+
r.traverse {|n| body = n if n.is_a?(Node) && n.name=='body'}
|
52
|
+
assert_not_nil body
|
53
|
+
first_p = body.all_children[0]
|
54
|
+
first_p_text = first_p.all_children[0]
|
55
|
+
assert_class Text,first_p_text
|
56
|
+
assert_equal 3,first_p_text.source.position.begin_point.line
|
57
|
+
assert_equal 5,first_p_text.source.position.end_point.line
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_artifact_final_host_is_set_correctly_for_all
|
61
|
+
r = AngularJs.parser_considering_angular_embedded_code.parse_file('test/data/puzzle.html')
|
62
|
+
r.traverse(:also_foreign) do |n|
|
63
|
+
assert_equal 'test/data/puzzle.html',n.source.artifact.final_host.filename, "Node with wrong final_host: #{n}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestBasicParsing < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
include CodeModels
|
7
|
+
include CodeModels::Html
|
8
|
+
|
9
|
+
def test_basic_document
|
10
|
+
code = "<html></html>"
|
11
|
+
r = Html.parse_code(code)
|
12
|
+
assert_class HtmlDocument, r
|
13
|
+
assert_equal 1, r.children.count
|
14
|
+
assert_class Node, r.children[0]
|
15
|
+
assert_equal 'html', r.children[0].name
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_basic_attributes
|
19
|
+
code = "<html id='ciao'></html>"
|
20
|
+
r = Html.parse_code(code)
|
21
|
+
html = r.children[0]
|
22
|
+
assert_equal 1, html.attributes.count
|
23
|
+
assert_equal "id", html.attributes[0].name
|
24
|
+
assert_equal "ciao", html.attributes[0].value
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_basic_text
|
28
|
+
code = "<html>ciao</html>"
|
29
|
+
r = Html.parse_code(code)
|
30
|
+
html = r.children[0]
|
31
|
+
assert_equal 1, html.children.count
|
32
|
+
assert_class Text, html.children[0]
|
33
|
+
assert_equal 'ciao', html.children[0].value
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_basic_dtd
|
37
|
+
code = "<!DOCTYPE html>"
|
38
|
+
r = Html.parse_code(code)
|
39
|
+
dtd = r.children[0]
|
40
|
+
assert_class DTD, dtd
|
41
|
+
assert_equal 'html', dtd.name
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_no_text_blocks
|
45
|
+
code = "<head><script type='text/ng-template' id='sliding-puzzle'>\n<a/>\n</script></head>"
|
46
|
+
r = Html.raw_node_tree(code)
|
47
|
+
assert_equal 0,r.child_elements[0].text_blocks(code).count
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_parse_scripts
|
51
|
+
code = "<html><head><script type='text/ng-template' id='sliding-puzzle'>\n<a/>\n</script></head></html>"
|
52
|
+
r = Html.parse_code(code)
|
53
|
+
script = r.children[0].children[0].children[0]
|
54
|
+
assert_class Script, script
|
55
|
+
assert_class HtmlDocument, script.foreign_asts[0]
|
56
|
+
assert_class Node, script.foreign_asts[0].children[0]
|
57
|
+
assert_equal 'a', script.foreign_asts[0].children[0].name
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_node_content
|
61
|
+
code = "<html><head><script type='text/ng-template' id='sliding-puzzle'>\n<a/>\n</script></head></html>"
|
62
|
+
r = Html.raw_node_tree(code)
|
63
|
+
script = r.child_elements[0].child_elements[0].child_elements[0]
|
64
|
+
assert_equal "<head><script type='text/ng-template' id='sliding-puzzle'>\n<a/>\n</script></head>",Parser.node_content(r,code)
|
65
|
+
assert_equal "\n<a/>\n",Parser.node_content(script,code)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_text_blocks
|
69
|
+
code = "<html><head><div type='text/ng-template' id='sliding-puzzle'>ciao<a/>come</div></head></html>"
|
70
|
+
r = Html.raw_node_tree(code)
|
71
|
+
assert_equal 0,r.child_elements[0].text_blocks(code).count
|
72
|
+
assert_equal 0,r.child_elements[0].child_elements[0].text_blocks(code).count
|
73
|
+
div = r.child_elements[0].child_elements[0].child_elements[0]
|
74
|
+
assert_equal 2,div.text_blocks(code).count
|
75
|
+
assert_equal "ciao",div.text_blocks(code)[0].value
|
76
|
+
assert_equal "come",div.text_blocks(code)[1].value
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/test/"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'codemodels'
|
9
|
+
require 'codemodels/html'
|
10
|
+
|
11
|
+
module TestHelper
|
12
|
+
|
13
|
+
include CodeModels
|
14
|
+
|
15
|
+
def assert_metamodel(name,attrs,refs)
|
16
|
+
assert Html.const_defined?(name), "Metaclass '#{name}' not found"
|
17
|
+
c = Html.const_get name
|
18
|
+
|
19
|
+
assert_all_attrs attrs, c
|
20
|
+
assert_all_refs refs, c
|
21
|
+
c
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_class(expected_class,node)
|
25
|
+
assert node.class==expected_class, "Node expected to have class #{expected_class} instead it has class #{node.class}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def relative_path(path)
|
29
|
+
File.join(File.dirname(__FILE__),path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_all_attrs(expected,c)
|
33
|
+
actual = c.ecore.eAllAttributes
|
34
|
+
assert_equal expected.count,actual.count,"Expected #{expected.count} attrs, found #{actual.count}. They are #{actual.name}"
|
35
|
+
expected.each do |e|
|
36
|
+
assert actual.find {|a| a.name==e}, "Attribute #{e} not found"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def assert_all_refs(expected,c)
|
41
|
+
actual = c.ecore.eAllReferences
|
42
|
+
assert_equal expected.count,actual.count,"Expected #{expected.count} refs, found #{actual.count}. They are #{actual.name}"
|
43
|
+
expected.each do |e|
|
44
|
+
assert actual.find {|a| a.name==e}, "Reference #{e} not found"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assert_ref(c,name,type,many=false)
|
49
|
+
ref = c.ecore.eAllReferences.find {|r| r.name==name}
|
50
|
+
assert ref, "Reference '#{name}' not found"
|
51
|
+
assert_equal type.ecore.name,ref.eType.name
|
52
|
+
assert_equal many, ref.many
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_attr(c,name,type,many=false)
|
56
|
+
att = c.ecore.eAllAttributes.find {|a| a.name==name}
|
57
|
+
assert_equal type.name,att.eType.name
|
58
|
+
assert_equal many, att.many
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_map(exp,map)
|
62
|
+
assert_equal exp.count,map.count, "Expected to have keys: #{exp.keys}, it has #{map}"
|
63
|
+
exp.each do |k,v|
|
64
|
+
assert_equal exp[k],map[k], "Expected #{k} to have #{exp[k]} instances, it has #{map[k.to_s]}. Map: #{map}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_code_map_to(code,exp)
|
69
|
+
r = Html.parse_code(code)
|
70
|
+
map = r.values_map
|
71
|
+
assert_map(exp,map)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestInfoExtraction < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
include CodeModels
|
7
|
+
|
8
|
+
def test_snippet_1
|
9
|
+
code = %q{
|
10
|
+
<html>
|
11
|
+
<body>
|
12
|
+
<p>ciao!</p>
|
13
|
+
</body>
|
14
|
+
</html>
|
15
|
+
}
|
16
|
+
assert_code_map_to(code, {
|
17
|
+
'html' =>1,
|
18
|
+
'body' =>1,
|
19
|
+
'p' => 1,
|
20
|
+
'ciao!'=> 1
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_no_extraneous_values
|
25
|
+
code = IO.read('test/data/puzzle.html')
|
26
|
+
r = Html::AngularJs.parser_considering_angular_embedded_code.parse_code(code)
|
27
|
+
r.traverse(:also_foreign) do |node|
|
28
|
+
node.collect_values_with_count.each do |value,count|
|
29
|
+
node_code = node.source.code
|
30
|
+
unless node_code.include?(value.to_s)
|
31
|
+
fail("Value '#{value}' expected in #{node}. Artifact: #{node.source.artifact}, abspos: #{node.source.position(:absolute)}, code: '#{node_code}'")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'codemodels/js'
|
3
|
+
|
4
|
+
class TestParsingEmbeddedLanguages < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include TestHelper
|
7
|
+
include CodeModels
|
8
|
+
include CodeModels::Html
|
9
|
+
|
10
|
+
|
11
|
+
def test_begin_index
|
12
|
+
code = "<sliding-puzzle api />"
|
13
|
+
assert_equal 0,code.first_index('<')
|
14
|
+
assert_equal 1,code.first_index('s')
|
15
|
+
assert_equal 8,code.first_index('-')
|
16
|
+
assert_equal 20,code.first_index('/')
|
17
|
+
assert_equal 21,code.first_index('>')
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'codemodels/js'
|
3
|
+
|
4
|
+
class TestParsingEmbeddedLanguages < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include TestHelper
|
7
|
+
include CodeModels
|
8
|
+
include CodeModels::Html
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@p = AngularJs.parser_considering_angular_embedded_code
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_source_line
|
15
|
+
code =
|
16
|
+
%q{<html>
|
17
|
+
<body ng-app="puzzleApp">
|
18
|
+
<ul id="types">
|
19
|
+
<li ng-repeat="t in types" ng-class="{'selected': t.id == type}">
|
20
|
+
<a ng-href="#/{{t.id}}">{{t.title}}</a>
|
21
|
+
</li>
|
22
|
+
</ul>
|
23
|
+
</body>
|
24
|
+
</html>}
|
25
|
+
|
26
|
+
r = @p.parse_code(code)
|
27
|
+
li = r.all_children_deep.find {|n| n.is_a?(Node) && n.name=='li'}
|
28
|
+
assert_not_nil li
|
29
|
+
a = li.attributes.find {|a| a.name=='ng-repeat'}
|
30
|
+
assert_not_nil a
|
31
|
+
assert_equal 1,a.foreign_asts.count
|
32
|
+
assert_class CodeModels::Js::InInfixExpression,a.foreign_asts[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_multiple_angular_expressions_in_attr
|
36
|
+
code = %q{<sliding-puzzle api="puzzle.api" size="{{puzzle.rows}}x{{puzzle.cols}}" src="{{puzzle.src}}"></sliding-puzzle>}
|
37
|
+
|
38
|
+
r = @p.parse_code(code)
|
39
|
+
assert_class HtmlDocument,r
|
40
|
+
assert_class Node,r.children[0]
|
41
|
+
att_size = r.children[0].attributes.find {|a| a.name=='size'}
|
42
|
+
att_src = r.children[0].attributes.find {|a| a.name=='src'}
|
43
|
+
assert_equal 2,att_size.foreign_asts.count
|
44
|
+
assert_equal 1,att_src.foreign_asts.count
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_parsing_empty_attr
|
48
|
+
code = %q{<sliding-puzzle api />}
|
49
|
+
|
50
|
+
r = @p.parse_code(code)
|
51
|
+
# it does not crash? It is ok!
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|