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



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

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

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



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

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



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

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



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

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?



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

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



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

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



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

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

#depth(e) ⇒ Object



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

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



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

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



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

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?



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

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



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

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

#match_class(attr_name, ref, sam) ⇒ Object



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

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



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

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!


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

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



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

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??



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

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



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

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

#run_all_xpaths(xpaths) ⇒ Object



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

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

#sort_nodesObject



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

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
# File 'lib/assert2/xhtml.rb', line 124

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

#verbose_spew(attr) ⇒ Object



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

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