Module: Janeway::NormalizedPath

Defined in:
lib/janeway/normalized_path.rb

Overview

Converts index and name selector values to normalized path components.

This implements the normalized path description in RFC 9535: This does a lot of escaping, and much of it is the inverse of Lexer code that does un-escaping.

Constant Summary collapse

NORMAL_UNESCAPED_RANGES =

Characters that do not need escaping, defined by hexadecimal range

[(0x20..0x26), (0x28..0x5B), (0x5D..0xD7FF), (0xE000..0x10FFFF)].freeze
NORMAL_UNESCAPED_REGEX =

Fast-path check for escape: a single anchored regex covering the same ranges as NORMAL_UNESCAPED_RANGES. Avoids allocating a chars array and scanning four Range objects per character on the common all-normal case.

Regexp.new(
  '\A[' +
  NORMAL_UNESCAPED_RANGES.map { |r| "\\u{#{r.min.to_s(16)}}-\\u{#{r.max.to_s(16)}}" }.join +
  ']*\z'
).freeze

Class Method Summary collapse

Class Method Details

.escape(str) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/janeway/normalized_path.rb', line 44

def self.escape(str)
  # Common case, all chars are normal — one regex match, no allocations.
  return str if NORMAL_UNESCAPED_REGEX.match?(str)

  # Some escaping must be done
  str.chars.map { |char| escape_char(char) }.join
end

.escape_char(char) ⇒ String

Escape or hex-encode the given character

Parameters:

  • char (String)

    single character, possibly multi-byte

Returns:

  • (String)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/janeway/normalized_path.rb', line 55

def self.escape_char(char)
  # Character ranges defined by https://www.rfc-editor.org/rfc/rfc9535.html#section-2.7-8
  case char.ord
  # normal-unescaped range
  when 0x20..0x26, 0x28..0x5B, 0x5D..0xD7FF, 0xE000..0x10FFFF then char # unescaped

  # normal-escapable range
  when 0x08 then '\\b' # backspace
  when 0x0C then '\\f' # form feed
  when 0x0A then '\\n' # line feed / newline
  when 0x0D then '\\r' # carriage return
  when 0x09 then '\\t' # horizontal tab
  when 0x27 then '\\\'' # apostrophe
  when 0x5C then '\\\\' # backslash

  else # normal-hexchar range
    hex_encode_char(char)
  end
end

.hex_encode_char(char) ⇒ String

Hex-encode the given character

Parameters:

  • char (String)

    single character, possibly multi-byte

Returns:

  • (String)


78
79
80
# File 'lib/janeway/normalized_path.rb', line 78

def self.hex_encode_char(char)
  format('\\u00%02x', char.ord)
end

.normalize(value) ⇒ Object



23
24
25
26
27
28
29
30
# File 'lib/janeway/normalized_path.rb', line 23

def self.normalize(value)
  case value
  when String then normalize_name(value)
  when Integer then normalize_index(value)
  else
    raise "Cannot normalize #{value.inspect}"
  end
end

.normalize_index(index) ⇒ String

Returns eg. "[1]".

Parameters:

  • index (Integer)

    index selector value

Returns:

  • (String)

    eg. "[1]"



34
35
36
# File 'lib/janeway/normalized_path.rb', line 34

def self.normalize_index(index)
  "[#{index}]"
end

.normalize_name(name) ⇒ String

Returns eg. "['']".

Parameters:

  • name (Integer)

    name selector value

Returns:

  • (String)

    eg. "['']"



40
41
42
# File 'lib/janeway/normalized_path.rb', line 40

def self.normalize_name(name)
  "['#{escape(name)}']"
end