Module: Lutaml::Xml::DeclarationHandler

Included in:
Adapter::BaseAdapter
Defined in:
lib/lutaml/xml/declaration_handler.rb

Overview

DeclarationHandler provides XML declaration extraction from input XML.

Extraction methods detect and parse XML declarations from input strings. Generation is handled by moxml’s document model — no manual string assembly.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extract_attribute(content, attr_name) ⇒ String?

Extract an attribute value from declaration content Uses simple string parsing to avoid regex ReDoS

Parameters:

  • content (String)

    the declaration content (between <?xml and ?>)

  • attr_name (String)

    the attribute name to find

Returns:

  • (String, nil)

    the attribute value or nil if not found



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/lutaml/xml/declaration_handler.rb', line 57

def self.extract_attribute(content, attr_name)
  # Find attribute name followed by =
  name_start = content.index("#{attr_name}=")
  return nil unless name_start

  # Get the position after attr=
  pos = name_start + attr_name.length + 1

  # Skip any whitespace
  pos += 1 while pos < content.length && content[pos] == " "

  return nil if pos >= content.length

  # Check quote character
  quote = content[pos]
  return nil unless ['"', "'"].include?(quote)

  # Find closing quote
  end_quote = content.index(quote, pos + 1)
  return nil unless end_quote

  # Extract value between quotes
  content[(pos + 1)...end_quote]
end

.extract_xml_declaration(xml) ⇒ Hash

Extract XML declaration information from input string

Detects if input had an XML declaration and extracts version/encoding/standalone. This is used for round-trip preservation of declarations.

Parameters:

  • xml (String)

    the XML string to parse

Returns:

  • (Hash)

    declaration info { version:, encoding:, standalone:, had_declaration: }



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/lutaml/xml/declaration_handler.rb', line 15

def self.extract_xml_declaration(xml)
  # Use string operations instead of regex to avoid ReDoS vulnerability
  # This approach is O(n) with no backtracking

  # Strip leading whitespace
  trimmed = xml.lstrip

  # Fast prefix check - no regex needed
  return { had_declaration: false } unless trimmed.start_with?("<?xml")

  # Find the end of the declaration (?>)
  # Limit search to first 100 chars to avoid scanning entire document
  search_region = trimmed[0, 100]
  end_pos = search_region.index("?>", 5)
  return { had_declaration: false } unless end_pos

  # Extract content between <?xml and ?>
  decl_content = trimmed[5...end_pos]

  # Extract version (defaults to "1.0")
  version = extract_attribute(decl_content, "version") || "1.0"

  # Extract encoding (may be absent)
  encoding = extract_attribute(decl_content, "encoding")

  # Extract standalone (may be absent)
  standalone = extract_attribute(decl_content, "standalone")

  {
    version: version,
    encoding: encoding,
    standalone: standalone,
    had_declaration: true,
  }
end

Instance Method Details

#should_include_declaration?(options, xml_declaration = nil) ⇒ Boolean

Determine if XML declaration should be included in output

Supports multiple modes:

  • false: omit declaration

  • true: force include with defaults

  • :preserve: include if input had one

  • String: custom version string

Parameters:

  • options (Hash)

    serialization options

  • xml_declaration (Hash) (defaults to: nil)

    extracted declaration info from input

Returns:

  • (Boolean)

    true if declaration should be included



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/lutaml/xml/declaration_handler.rb', line 93

def should_include_declaration?(options, xml_declaration = nil)
  xml_declaration ||= @xml_declaration

  if options.key?(:declaration)
    case options[:declaration]
    when false
      false
    when true
      true
    when :preserve
      xml_declaration&.dig(:had_declaration) || false
    when String
      true
    else
      xml_declaration&.dig(:had_declaration) || false
    end
  else
    xml_declaration&.dig(:had_declaration) || false
  end
end