codemodels-html 0.1.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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
|