Module: Moxml::XmlUtils

Included in:
Adapter::Base, NodeBehavior
Defined in:
lib/moxml/xml_utils.rb,
lib/moxml/xml_utils/encoder.rb

Defined Under Namespace

Classes: Encoder

Constant Summary collapse

VALID_ELEMENT_NAMES =

Real documents reuse a handful of distinct element names; the memo turns the per-create regex into a hash probe after the first sight of a name. Capped — hostile vocabularies fall back to the regex.

{}.compare_by_identity

Instance Method Summary collapse

Instance Method Details

#encode_entities(text, mode = nil) ⇒ Object



8
9
10
# File 'lib/moxml/xml_utils.rb', line 8

def encode_entities(text, mode = nil)
  Encoder.new(text, mode).call
end

#normalize_xml_value(value) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/moxml/xml_utils.rb', line 107

def normalize_xml_value(value)
  case value
  when nil then ""
  when true then "true"
  when false then "false"
  else value.to_s
  end
end

#validate_comment_content(text) ⇒ Object

Raises:



35
36
37
38
39
40
41
42
43
# File 'lib/moxml/xml_utils.rb', line 35

def validate_comment_content(text)
  if text.start_with?("-") || text.end_with?("-")
    raise ValidationError, "XML comment cannot start or end with a hyphen"
  end

  return unless text.include?("--")

  raise ValidationError, "XML comment cannot contain double hyphens (--)"
end

#validate_declaration_encoding(encoding) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/moxml/xml_utils.rb', line 18

def validate_declaration_encoding(encoding)
  return if encoding.nil?

  begin
    Encoding.find(encoding)
  rescue ArgumentError
    raise ValidationError, "Invalid encoding: #{encoding}"
  end
end

#validate_declaration_standalone(standalone) ⇒ Object

Raises:



28
29
30
31
32
33
# File 'lib/moxml/xml_utils.rb', line 28

def validate_declaration_standalone(standalone)
  return if standalone.nil?
  return if ::Moxml::Declaration::ALLOWED_STANDALONE.include?(standalone)

  raise ValidationError, "Invalid standalone value: #{standalone}"
end

#validate_declaration_version(version) ⇒ Object

Raises:



12
13
14
15
16
# File 'lib/moxml/xml_utils.rb', line 12

def validate_declaration_version(version)
  return if ::Moxml::Declaration::ALLOWED_VERSIONS.include?(version)

  raise ValidationError, "Invalid XML version: #{version}"
end

#validate_element_name(name) ⇒ Object

Raises:



51
52
53
54
55
56
57
58
59
60
# File 'lib/moxml/xml_utils.rb', line 51

def validate_element_name(name)
  return if name.is_a?(String) && VALID_ELEMENT_NAMES.key?(name)

  if name.is_a?(String) && name.match?(/^[a-zA-Z_][\w\-.:]*$/)
    VALID_ELEMENT_NAMES[name] = true if VALID_ELEMENT_NAMES.size < 1024
    return
  end

  raise ValidationError, "Invalid XML element name: #{name}"
end

#validate_entity_reference_name(name) ⇒ Object

Raises:



116
117
118
119
120
121
122
# File 'lib/moxml/xml_utils.rb', line 116

def validate_entity_reference_name(name)
  # Entity names follow the same pattern as element names
  # They must start with a letter or underscore, followed by letters, digits, hyphens, underscores, periods, or colons
  return if name.is_a?(String) && name.match?(/^[a-zA-Z_][\w\-.:]*$/)

  raise ValidationError, "Invalid entity reference name: #{name}"
end

#validate_pi_target(target) ⇒ Object

Raises:



62
63
64
65
66
67
# File 'lib/moxml/xml_utils.rb', line 62

def validate_pi_target(target)
  return if target.is_a?(String) && target.match?(/^[a-zA-Z_][\w\-.]*$/)

  raise ValidationError,
        "Invalid XML processing instruction target: #{target}"
end

#validate_prefix(prefix) ⇒ Object

Raises:



101
102
103
104
105
# File 'lib/moxml/xml_utils.rb', line 101

def validate_prefix(prefix)
  return if prefix.match?(/\A[a-zA-Z_][\w.-]*\z/)

  raise ValidationError, "Invalid namespace prefix: #{prefix}"
end

#validate_uri(uri, mode: :strict) ⇒ Object



69
70
71
72
73
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
# File 'lib/moxml/xml_utils.rb', line 69

def validate_uri(uri, mode: :strict)
  # Empty strings are allowed for default namespace undeclaration (xmlns="").
  return if uri.empty?

  # In lenient mode, accept any string as a namespace URI.
  # Only reject strings containing XML-invalid characters (control characters).
  if mode == :lenient
    if uri.match?(/[\x00-\x08\x0B\x0C\x0E-\x1F]/)
      raise ValidationError, "Invalid URI: #{uri}"
    end

    return
  end

  # Namespace names must be valid URI-references per RFC 3986
  # (W3C Namespaces in XML, https://www.w3.org/TR/xml-names/).
  # Use split instead of parse to avoid scheme-specific validation
  # that rejects valid opaque URIs like "mailto:bar".
  if defined?(URI::RFC3986_PARSER)
    URI::RFC3986_PARSER.split(uri)
  elsif uri.match?(/\A[a-zA-Z][a-zA-Z0-9+\-.]*:[^\x00-\x20]*\z/)
    # Minimal URI validation for Opal (no RFC3986_PARSER available)
  else
    # Accept relative references and bare paths
    return unless uri.match?(/[\x00-\x08\x0B\x0C\x0E-\x1F]/)

    raise ValidationError, "Invalid URI: #{uri}"
  end
rescue URI::InvalidURIError
  raise ValidationError, "Invalid URI: #{uri}"
end