Class: BeHtmlWith

Inherits:
Object
  • Object
show all
Defined in:
lib/assert2/xhtml.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope, &block) ⇒ BeHtmlWith

Returns a new instance of BeHtmlWith.



65
66
67
68
69
# File 'lib/assert2/xhtml.rb', line 65

def initialize(scope, &block)
  @scope, @block = scope, block
  @references = []
  @spewed = {}
end

Instance Attribute Details

#builderObject

Returns the value of attribute builder.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def builder
  @builder
end

#docObject

Returns the value of attribute doc.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def doc
  @doc
end

#failure_messageObject

Returns the value of attribute failure_message.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def failure_message
  @failure_message
end

#messageObject

Returns the value of attribute message.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def message
  @message
end

#referenceObject

Returns the value of attribute reference.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def reference
  @reference
end

#referencesObject

Returns the value of attribute references.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def references
  @references
end

#returnableObject

Returns the value of attribute returnable.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def returnable
  @returnable
end

#sampleObject

Returns the value of attribute sample.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def sample
  @sample
end

#scopeObject

Returns the value of attribute scope.



71
72
73
# File 'lib/assert2/xhtml.rb', line 71

def scope
  @scope
end

Instance Method Details

#build_deep_xpath(element) ⇒ Object



110
111
112
113
114
# File 'lib/assert2/xhtml.rb', line 110

def build_deep_xpath(element)
  path = build_xpath(element)
  path.index('not(') == 0 and return '/*[ ' + path + ' ]'
  return '//' + path
end

#build_deep_xpath_too(element) ⇒ Object



283
284
285
# File 'lib/assert2/xhtml.rb', line 283

def build_deep_xpath_too(element)
  return '//' + build_xpath_too(element)
end

#build_predicate(element, conjunction = 'and') ⇒ Object



140
141
142
143
144
# File 'lib/assert2/xhtml.rb', line 140

def build_predicate(element, conjunction = 'and')
  conjunction = " #{ conjunction } "
  element_kids = elemental_children(element)
  return element_kids.map{|child|  build_xpath(child)  }.join(conjunction)
end

#build_xpath(element) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/assert2/xhtml.rb', line 116

def build_xpath(element)
  count = @references.length
  @references << element  #  note we skip the without @reference!
  
  if element.name == 'without!'
    return 'not( ' + build_predicate(element, 'or') + ' )'
  else
    target = translate_tag(element)
    path = "descendant::#{ target }[ refer(., '#{ count }') "
      #  refer() is first so we collect many samples, despite boolean short-circuiting
    path << 'and '  if elemental_children(element).any?
    path << build_predicate(element) + ']'
    return path
  end
end

#build_xpath_too(element) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/assert2/xhtml.rb', line 287

def build_xpath_too(element)
  path = element.name.sub(/\!$/, '')
  element_kids = element.children.grep(Nokogiri::XML::Node)
  path << '[ '
  count = @references.length
  @references << element
  brackets_owed = 0

  if element_kids.length > 0
    child = element_kids[0]
    path << './descendant::' + build_xpath_too(child)
  end

  if element_kids.length > 1
    path << element_kids[1..-1].map{|child|
              '[ ./following-sibling::*[ ./descendant-or-self::' + build_xpath_too(child) + ' ] ]'
             }.join #(' and .')
  end
     path << ' and ' if element_kids.any?

  path << "refer(., '#{ count }') ]"  #  last so boolean short-circuiting optimizes
  return path
end

#build_xpaths(&block) ⇒ Object



90
91
92
93
94
95
96
97
# File 'lib/assert2/xhtml.rb', line 90

def build_xpaths(&block)
  bwock = block || @block || proc{} #  CONSIDER  what to do with no block? validate?
  @builder = Nokogiri::HTML::Builder.new(&bwock)

  elemental_children.map do |child|
    build_deep_xpath(child)
  end
end

#collect_best_sample(samples) ⇒ Object



261
262
263
264
265
266
267
# File 'lib/assert2/xhtml.rb', line 261

def collect_best_sample(samples)
  sample = samples.first or return

  if @best_sample.nil? or depth(@best_sample) > depth(sample)
    @best_sample = sample
  end
end

#collect_samples(elements, index) ⇒ Object

ERGO match text with internal spacies?



168
169
170
171
172
173
174
175
# File 'lib/assert2/xhtml.rb', line 168

def collect_samples(elements, index) # TODO  rename these samples to specimens
  samples = elements.find_all do |element|
              match_attributes_and_text(@references[index], element)
            end
  
  collect_best_sample(samples)
  samples
end

#complain(refered = @builder.doc, sample = @best_sample || @doc.root) ⇒ Object



273
274
275
276
277
278
279
280
281
# File 'lib/assert2/xhtml.rb', line 273

def complain( refered = @builder.doc, 
               sample = @best_sample || @doc.root )
         #  ERGO  use to_xml? or what?
  @failure_message = "#{message}\n".lstrip +
                     "\nCould not find this reference...\n\n" +
                        refered.to_xhtml.sub(/^\<\!DOCTYPE.*/, '') +
                   "\n\n...in this sample...\n\n" +
                        sample.to_xml
end

#deAmpAmp(stwing) ⇒ Object



232
233
234
# File 'lib/assert2/xhtml.rb', line 232

def deAmpAmp(stwing)
  stwing.to_s.gsub('&amp;amp;', '&').gsub('&amp;', '&')
end

#depth(e) ⇒ Object



269
270
271
# File 'lib/assert2/xhtml.rb', line 269

def depth(e)
  e.xpath('ancestor-or-self::*').length
end

#elemental_children(element = @builder.doc) ⇒ Object



99
100
101
102
103
104
105
106
107
108
# File 'lib/assert2/xhtml.rb', line 99

def elemental_children(element = @builder.doc)
  element_kids = element.children.grep(Nokogiri::XML::Node)

  element_kids = element_kids.reject{|k| 
                   k.class == Nokogiri::XML::Text ||
                   k.class == Nokogiri::XML::DTD
                   }  #  CONSIDER  rebuild to use not abuse the text nodage!

  return element_kids
end

#get_texts(element) ⇒ Object



256
257
258
259
# File 'lib/assert2/xhtml.rb', line 256

def get_texts(element)
 element.children.grep(Nokogiri::XML::Text).
    map{|x|x.to_s.strip}.select{|x|x.any?}
end

#match_attribute(attr) ⇒ Object



225
226
227
228
229
230
# File 'lib/assert2/xhtml.rb', line 225

def match_attribute(attr)
  ref = deAmpAmp(attr.value)
  sam = deAmpAmp(@sample[attr.name])
  ref == sam or match_regexp(ref, sam) or 
    match_class(attr.name, ref, sam)
end

#match_attributesObject

TODO uh, indenting mebbe?



184
185
186
187
188
189
190
191
192
193
194
# File 'lib/assert2/xhtml.rb', line 184

def match_attributes
  sort_nodes.each do |attr|
    case attr.name
      when 'verbose!' ;  verbose_spew(attr)
      when 'xpath!'   ;  match_xpath_predicate(attr) or return false
      else            ;  match_attribute(attr)       or return false
    end
  end

  return true
end

#match_attributes_and_text(reference, sample) ⇒ Object



177
178
179
180
# File 'lib/assert2/xhtml.rb', line 177

def match_attributes_and_text(reference, sample)
  @reference, @sample = reference, sample
  match_attributes and match_text
end

#match_class(attr_name, ref, sam) ⇒ Object



241
242
243
244
# File 'lib/assert2/xhtml.rb', line 241

def match_class(attr_name, ref, sam)
  attr_name == 'class' and
    " #{ sam } ".index(" #{ ref } ")
end

#match_regexp(reference, sample) ⇒ Object

ERGO await a fix in Nokogiri, and hope nobody actually means &amp;amp; !!!



236
237
238
239
# File 'lib/assert2/xhtml.rb', line 236

def match_regexp(reference, sample)
  reference =~ /\(\?.*\)/ and   #  the irony _is_ lost on us...
    Regexp.new(reference) =~ sample
end

#match_text(ref = @reference, sam = @sample) ⇒ Object

NOTE if you call it a class, but ref contains

something fruity, you are on your own!


247
248
249
250
251
252
253
254
# File 'lib/assert2/xhtml.rb', line 247

def match_text(ref = @reference, sam = @sample)
  ref_text = get_texts(ref)
  ref_text.empty? and return true
  sam_text = get_texts(sam)
  (ref_text - sam_text).empty? and return true
  got = (ref_text.length == 1 and match_regexp(ref_text.first, sam_text.join))
  return got
end

#match_xpath(path, &refer) ⇒ Object



157
158
159
160
161
162
163
164
# File 'lib/assert2/xhtml.rb', line 157

def match_xpath(path, &refer)
  nodes = @doc.root.xpath_with_callback path, :refer do |element, index|
    collect_samples(element, index.to_i)
  end
   
  @returnable ||= nodes.first  #  TODO  be_with_html must get on board too
  return nodes
end

#match_xpath_predicate(attr) ⇒ Object

TODO why we have no :css! yet??



216
217
218
219
220
221
222
223
# File 'lib/assert2/xhtml.rb', line 216

def match_xpath_predicate(attr)
  @sample.parent.xpath("*[ #{ attr.value } ]").each do |m|
    m.path == @sample.path and 
      return true
  end

  return false
end

#matches?(stwing, &block) ⇒ Boolean

Returns:

  • (Boolean)


81
82
83
84
85
86
87
88
# File 'lib/assert2/xhtml.rb', line 81

def matches?(stwing, &block)
  @block ||= block  #  ERGO  test that ||= - preferrably with a real RSpec suite!

  @scope.wrap_expectation self do
    @doc = Nokogiri::HTML(stwing)
    return run_all_xpaths(build_xpaths)
  end
end

#negative_failure_messageObject



311
312
313
# File 'lib/assert2/xhtml.rb', line 311

def negative_failure_message
  "please don't negate - use without!"
end

#run_all_xpaths(xpaths) ⇒ Object



146
147
148
149
150
151
152
153
154
155
# File 'lib/assert2/xhtml.rb', line 146

def run_all_xpaths(xpaths)
  xpaths.each do |path|
    if match_xpath(path).empty?
      complain
      return false
    end
  end
  
  return true
end

#sort_nodesObject



196
197
198
199
200
201
202
# File 'lib/assert2/xhtml.rb', line 196

def sort_nodes
  @reference.attribute_nodes.sort_by do |q|
    { 'verbose!' => 0,  #  put this first, so it always runs, even if attributes don't match
      'xpath!' => 2  #  put this last, so if attributes don't match, it does not waste time
      }.fetch(q.name, 1)
  end 
end

#translate_tag(element) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/assert2/xhtml.rb', line 132

def translate_tag(element)
  if element.name == 'any!'
    '*'
  else
    element.name.sub(/\!$/, '')
  end
end

#verbose_spew(attr) ⇒ Object



204
205
206
207
208
209
210
211
212
# File 'lib/assert2/xhtml.rb', line 204

def verbose_spew(attr)
  if attr.value == 'true' and @spewed[yo_path = @sample.path] == nil
    puts
    puts '-' * 60
    p yo_path
    puts @sample.to_xhtml
    @spewed[yo_path] = true
  end
end