Class: SvgConform::SaxValidationHandler

Inherits:
Nokogiri::XML::SAX::Document
  • Object
show all
Defined in:
lib/svg_conform/sax_validation_handler.rb

Overview

SAX event handler for streaming SVG validation Processes XML events and dispatches to requirements

Requirement classification

The handler classifies requirements into two categories:

  1. Immediate validation requirements (14) - validate during parsing

    • These requirements implement validate_sax_element and validate each element as it is encountered
    • No state is maintained between elements
  2. Deferred validation requirements (3) - collect data, validate at end

    • These requirements implement collect_sax_data and validate_sax_complete
    • State is stored in requirement-specific State classes via context.state_for
    • Validation happens after parsing is complete, when all data is available

State management

Each handler instance has its own cache and fresh requirement instances, ensuring no state leakage between validations.

Profile reuse for multiple validations:

  • Profile is created once with requirement instances (configuration from YAML)
  • Each validation creates a fresh SaxValidationHandler with fresh ValidationContext
  • ValidationContext creates fresh State instances for deferred requirements
  • No state pollution occurs across validations

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(profile) ⇒ SaxValidationHandler

Returns a new instance of SaxValidationHandler.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/svg_conform/sax_validation_handler.rb', line 44

def initialize(profile)
  @profile = profile
  @element_stack = [] # Track parent-child hierarchy
  @path_stack = [] # Current element path
  @position_counters = [] # Stack of sibling counters per level
  @parse_errors = []
  @result = nil # Will be set in end_document

  # Create instance-level cache (no shared state between handlers)
  @classification_cache = ClassificationCache.new

  # Use profile requirements directly (config is stateless)
  # Validation state lives in context, not in requirements
  @requirements = @profile.requirements || []

  # Create validation context (without document reference for SAX)
  @context = create_sax_context

  # Classify requirements into immediate vs deferred (use instance cache)
  @immediate_requirements, @deferred_requirements = classify_requirements_with_cache
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



42
43
44
# File 'lib/svg_conform/sax_validation_handler.rb', line 42

def context
  @context
end

#resultObject (readonly)

Get result (will be nil until end_document called)



157
158
159
# File 'lib/svg_conform/sax_validation_handler.rb', line 157

def result
  @result
end

Instance Method Details

#add_parse_error(error) ⇒ Object

Handle parse errors



147
148
149
150
151
152
153
154
# File 'lib/svg_conform/sax_validation_handler.rb', line 147

def add_parse_error(error)
  @context.add_error(
    node: nil,
    message: "Parse error: #{error.message}",
    requirement_id: "parse_error",
    severity: :error,
  )
end

#characters(string) ⇒ Object

SAX Event: Text content



119
120
121
122
123
# File 'lib/svg_conform/sax_validation_handler.rb', line 119

def characters(string)
  return if @element_stack.empty?

  @element_stack.last.text_content << string
end

#end_documentObject

SAX Event: Document complete



126
127
128
129
130
131
132
133
134
# File 'lib/svg_conform/sax_validation_handler.rb', line 126

def end_document
  # Run deferred validation
  @deferred_requirements.each do |req|
    req.validate_sax_complete(@context)
  end

  # Create result
  @result = ValidationResult.new(nil, @profile, @context)
end

#end_element(_name) ⇒ Object

SAX Event: Element end tag



112
113
114
115
116
# File 'lib/svg_conform/sax_validation_handler.rb', line 112

def end_element(_name)
  @element_stack.pop
  @path_stack.pop
  @position_counters.pop
end

#error(error_message) ⇒ Object

SAX Event: Parse error



137
138
139
# File 'lib/svg_conform/sax_validation_handler.rb', line 137

def error(error_message)
  @parse_errors << error_message
end

#start_documentObject

SAX Event: Document start



67
68
69
70
71
# File 'lib/svg_conform/sax_validation_handler.rb', line 67

def start_document
  # No need to reset state - each validation has fresh requirement instances
  # Initialize root level counters
  @position_counters.push({})
end

#start_element(name, attributes = []) ⇒ Object

SAX Event: Element start tag



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/svg_conform/sax_validation_handler.rb', line 74

def start_element(name, attributes = [])
  attrs = attributes.to_h

  # Calculate position among siblings at current level
  current_counters = @position_counters.last || {}
  current_counters[name] ||= 0
  current_counters[name] += 1
  position = current_counters[name]

  # Build element proxy
  element = ElementProxy.new(
    name: name,
    attributes: attrs,
    position: position,
    path: @path_stack.dup,
    parent: @element_stack.last,
  )

  # Push to stacks
  @element_stack.push(element)
  @path_stack.push("#{name}[#{position}]")
  @position_counters.push({}) # New level for this element's children

  # Validate with immediate requirements
  @immediate_requirements.each do |req|
    req.validate_sax_element(element, @context)
  end

  # Deferred requirements may need to collect data
  @deferred_requirements.each do |req|
    if req.respond_to?(:collect_sax_data)
      req.collect_sax_data(element,
                           @context)
    end
  end
end

#warning(warning_message) ⇒ Object

SAX Event: Warning



142
143
144
# File 'lib/svg_conform/sax_validation_handler.rb', line 142

def warning(warning_message)
  # Can log warnings if needed
end