Class: Splunk::ResultsListener

Inherits:
Object
  • Object
show all
Defined in:
lib/splunk-sdk-ruby/resultsreader.rb

Overview

ResultsListener is the SAX event handler for ResultsReader.

The authors of Nokogiri decided to make their SAX interface slightly incompatible with that of REXML. For example, REXML uses tag_start and passes attributes as a dictionary, while Nokogiri calls the same thing start_element, and passes attributes as an association list.

This is a classic finite state machine parser. The ‘@states` variable contains a hash with the states as its values. Each hash contains functions giving the behavior of the state machine in that state. The actual methods on the function dispatch to these functions based upon the current state (as stored in `@state`).

The parser initially runs until it has determined if the results are a preview, then calls Fiber.yield to return it. Then it continues and tries to yield a field order, and then any results. (It will always yield a field order, even if it is empty). At the end of a results set, it yields :end_of_results_set.

Instance Method Summary collapse

Constructor Details

#initializeResultsListener

:nodoc:



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 272

def initialize()
  # @fields holds the accumulated list of fields from the fieldOrder
  # element. If there has been no accumulation, it is set to
  # :no_fieldOrder_found. For empty results sets, there is often no
  # fieldOrder element, but we still want to yield an empty Array at the
  # right point, so if we reach the end of a results element and @fields
  # is still :no_fieldOrder_found, we yield an empty array at that point.
  @fields = :no_fieldOrder_found
  @concatenate = false
  @is_preview = nil
  @state = :base
  @states = {
      # Toplevel state.
      :base => {
          :start_element => lambda do |name, attributes|
            if name == "results"
              if !@concatenate
                @is_preview = attributes["preview"] == "1"
                Fiber.yield(@is_preview)
              end
            elsif name == "fieldOrder"
              if !@concatenate
                @state = :field_order
                @fields = []
              end
            elsif name == "result"
              @state = :result
              @current_offset = Integer(attributes["offset"])
              @current_result = Event.new()
            end
          end,
          :end_element => lambda do |name|
            if name == "results" and !@concatenate
              Fiber.yield([]) if @fields == :no_fieldOrder_found

              if !@is_preview # Start concatenating events
                @concatenate = true
              else
                # Reset the fieldOrder
                @fields = :no_fieldOrder_found
                Fiber.yield(:end_of_results_set)
              end
            end
          end
      },
      # Inside a `fieldOrder` element. Recognizes only
      # the `field` element, and returns to the `:base` state
      # when it encounters `</fieldOrder>`.
      :field_order => {
          :start_element => lambda do |name, attributes|
            if name == "field"
              @state = :field_order_field
            end
          end,
          :end_element => lambda do |name|
            if name == "fieldOrder"
              @state = :base
              Fiber.yield(@fields)
            end
          end
      },
      # When the parser in `:field_order` state encounters
      # a `field` element, it jumps to this state to record it.
      # When `</field>` is encountered, jumps back to `:field_order`.
      :field_order_field => {
          :characters => lambda do |text|
            @fields << text.strip
          end,
          :end_element => lambda do |name|
            if name == "field"
              @state = :field_order
            end
          end
      },
      # When the parser has hit the `result` element, it jumps here.
      # When this state hits `</result>`, it calls `Fiber.yield` to
      # send the completed result back, and, when the fiber is
      # resumed, jumps back to the `:base` state.
      :result => {
          :start_element => lambda do |name, attributes|
            if name == "field"
              @current_field = attributes["k"]
              @current_value = nil
            elsif name == "text" || name == "v"
              @state = :field_values
              @current_text = ""
              s = ["v"] + attributes.map do |entry|
                key, value = entry
                # Nokogiri and REXML both drop the namespaces of attributes,
                # and there is no way to recover them. To reconstruct the
                # XML (since we can't get at its raw form) we put in the
                # one instance of a namespace on an attribute that shows up
                # in what Splunk returns. Yes, this is a terribly, ugly
                # kludge.
                if key == "space"
                  prefixed_key = "xml:space"
                else
                  prefixed_key = key
                end
                "#{prefixed_key}=\"#{value}\""
              end
              @current_xml = "<" + s.join(" ") + ">"
            end
          end,
          :end_element => lambda do |name|
            if name == "result"
              Fiber.yield @current_result
              @current_result = nil
              @current_offset = nil
              @state = :base
            elsif name == "field"
              if @current_result.has_key?(@current_field)
                if @current_result[@current_field].is_a?(Array)
                  @current_result[@current_field] << @current_value
                elsif @current_result[@current_field] != nil
                  @current_result[@current_field] =
                      [@current_result[@current_field], @current_value]
                end
              else
                @current_result[@current_field] = @current_value
              end

              if @current_field == "_raw"
                @current_result.raw_xml = @current_xml
              end

              @current_field = nil
              @current_value = nil
            end
          end
      },
      # Parse the values inside a results field.
      :field_values => {
          :end_element => lambda do |name|
            if name == "text" || name == "v"
              if @current_value == nil
                @current_value = @current_text
              elsif @current_value.is_a?(Array)
                @current_value << @current_text
              else
                @current_value = [@current_value, @current_text]
              end

              if name == "v"
                @current_xml << "</v>"
              end

              @current_text = nil
              @state = :result
            elsif name == "sg"
              # <sg> is emitted to delimit text that should be displayed
              # highlighted. We preserve it in field values.
              @current_xml << "</sg>"
            end
          end,
          :start_element => lambda do |name, attributes|
            if name == "sg"
              s = ["sg"] + attributes.sort.map do |entry|
                key, value = entry
                "#{key}=\"#{value}\""
              end
              text = "<" + s.join(" ") + ">"
              @current_xml << text
            end
          end,
          :characters => lambda do |text|
            @current_text << text
            @current_xml << Splunk::escape_string(text)
          end
      }
  }
end

Instance Method Details

#attlistdecl(element_name, attributes, raw_content) ⇒ Object

Unused methods in REXML



505
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 505

def attlistdecl(element_name, attributes, raw_content) end

#cdata(content) ⇒ Object



506
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 506

def cdata(content) end

#cdata_block(string) ⇒ Object

Unused methods in Nokogiri



496
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 496

def cdata_block(string) end

#characters(text) ⇒ Object



471
472
473
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 471

def characters(text)
  text(text)
end

#comment(comment) ⇒ Object



497
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 497

def comment(string) end

#doctype(name, pub_sys, long_name, uri) ⇒ Object



508
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 508

def doctype(name, pub_sys, long_name, uri) end

#doctype_endObject



509
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 509

def doctype_end() end

#elementdecl(content) ⇒ Object



510
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 510

def elementdecl(content) end

#end_documentObject



498
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 498

def end_document() end

#end_element(name) ⇒ Object



463
464
465
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 463

def end_element(name)
  tag_end(name)
end

#end_element_namespace(name, prefix = nil, uri = nil) ⇒ Object



467
468
469
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 467

def end_element_namespace(name, prefix = nil, uri = nil)
  end_element(name)
end

#entity(content) ⇒ Object



511
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 511

def entity(content) end

#entitydecl(content) ⇒ Object



512
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 512

def entitydecl(content) end

#error(string) ⇒ Object



499
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 499

def error(string) end

#instruction(name, instruction) ⇒ Object



513
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 513

def instruction(name, instruction) end

#notationdecl(content) ⇒ Object



514
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 514

def notationdecl(content) end

#start_documentObject



500
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 500

def start_document() end

#start_element(name, attributes) ⇒ Object

Nokogiri methods - all dispatch to the REXML methods.



446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 446

def start_element(name, attributes)
  # attributes is an association list. Turn it into a hash
  # that tag_start can use.
  attribute_dict = {}
  attributes.each do |attribute|
    key = attribute.localname
    value = attribute.value
    attribute_dict[key] = value
  end

  tag_start(name, attribute_dict)
end

#start_element_namespace(name, attributes = [], prefix = nil, uri = nil, ns = []) ⇒ Object



459
460
461
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 459

def start_element_namespace(name, attributes=[], prefix=nil, uri=nil, ns=[])
  start_element(name, attributes)
end

#tag_end(name) ⇒ Object



483
484
485
486
487
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 483

def tag_end(name)
  if @states[@state].has_key?(:end_element)
    @states[@state][:end_element].call(name)
  end
end

#tag_start(name, attributes) ⇒ Object

REXML methods - all dispatch is done here



476
477
478
479
480
481
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 476

def tag_start(name, attributes)
  # attributes is a hash.
  if @states[@state].has_key?(:start_element)
    @states[@state][:start_element].call(name, attributes)
  end
end

#text(text) ⇒ Object



489
490
491
492
493
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 489

def text(text)
  if @states[@state].has_key?(:characters)
    @states[@state][:characters].call(text)
  end
end

#warning(string) ⇒ Object



501
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 501

def warning(string) end

#xmldecl(version, encoding, standalone) ⇒ Object



515
# File 'lib/splunk-sdk-ruby/resultsreader.rb', line 515

def xmldecl(version, encoding, standalone) end