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

#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



102
103
104
105
106
# File 'lib/assert2/xhtml.rb', line 102

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



272
273
274
# File 'lib/assert2/xhtml.rb', line 272

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

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



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

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



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/assert2/xhtml.rb', line 108

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



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/assert2/xhtml.rb', line 276

def build_xpath_too(element)
  path = element.name.sub(/\!$/, '')
  element_kids = element.children.grep(Nokogiri::XML::Element)
  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



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

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



250
251
252
253
254
255
256
# File 'lib/assert2/xhtml.rb', line 250

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?



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

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



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

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



222
223
224
# File 'lib/assert2/xhtml.rb', line 222

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

#depth(e) ⇒ Object



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

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

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



98
99
100
# File 'lib/assert2/xhtml.rb', line 98

def elemental_children(element = @builder.doc)
  element.children.grep(Nokogiri::XML::Element)
end

#get_texts(element) ⇒ Object



245
246
247
248
# File 'lib/assert2/xhtml.rb', line 245

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



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

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?



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/assert2/xhtml.rb', line 174

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



167
168
169
170
# File 'lib/assert2/xhtml.rb', line 167

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

#match_class(attr_name, ref, sam) ⇒ Object



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

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; !!!



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

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!


237
238
239
240
241
242
243
# File 'lib/assert2/xhtml.rb', line 237

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
  ref_text.length == 1 and match_regexp(ref_text.first, sam_text.join)
end

#match_xpath(path, &refer) ⇒ Object



150
151
152
153
154
# File 'lib/assert2/xhtml.rb', line 150

def match_xpath(path, &refer)
  @doc.root.xpath_with_callback path, :refer do |element, index|
    collect_samples(element, index.to_i)
  end
end

#match_xpath_predicate(attr) ⇒ Object

TODO why we have no :css! yet??



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

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)


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

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



300
301
302
# File 'lib/assert2/xhtml.rb', line 300

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

#run_all_xpaths(xpaths) ⇒ Object



139
140
141
142
143
144
145
146
147
148
# File 'lib/assert2/xhtml.rb', line 139

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

#sort_nodesObject



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

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



124
125
126
127
128
129
130
131
# File 'lib/assert2/xhtml.rb', line 124

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

#verbose_spew(attr) ⇒ Object



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

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