canon 0.3.61 → 0.3.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5b90b7fc1d45d8f8e58e804f3f67dae934cc225639f3196c64f43cdd84ae005
4
- data.tar.gz: c6615312bb2ac6eba00700c60b7dfeeb18277b8677532f98f91e4ad24a29c562
3
+ metadata.gz: c5ab88d04204cba8fb555ca3ebae0091a6a9d22fbb5e06e58974195e6bace30d
4
+ data.tar.gz: 3f952b1cb790351630ba1cb659e69492e67ff34ede453c98624877df4bb9b5f5
5
5
  SHA512:
6
- metadata.gz: 75e2773c9597d616988157bdd40f4df3478dfd5695c1b9c7b9fc3709a6b043e22e98d3a1d9f2221ad06f7d4bb249331b1356606dce4acf10ce4bdfd8c254d209
7
- data.tar.gz: 2877cc2367ac8da5ef68ea9b2ba2cc94883754a47aaf7dba51aef1050d67b9f3d95027238a539e119c92704fa696bdd8de8412bf178098a00e03061fbbb48888
6
+ metadata.gz: 6128ca89822cbf059a28e3f753d42a56e3ab40fd5ed98bb4cca9a467ae1124387b443747d0d480eb0da1bfd8358bc17e870dc4709e12c857b427bd35c56479c4
7
+ data.tar.gz: 1f6100fa01d92715eac814bb0538172b7ebc993cddbeaea34855631a76880cbf5b791403ba581c9b446a7903784aa1217542bc7c45ea18174b3e27eec2d7fb1d
@@ -107,8 +107,14 @@ module Canon
107
107
  # case-insensitive (e.g., "UTF-8" equals "utf-8").
108
108
  # The standalone declaration in XML 1.1 is also case-insensitive.
109
109
  CASE_INSENSITIVE_ATTRS = %w[encoding standalone].freeze
110
- QUOTE_CHARS = ["\"", "'"].freeze
111
- SKIP_CHARS = [" ", "="].freeze
110
+ # Byte-level scan sets — the tokenizer reads via getbyte, whose
111
+ # Integer results allocate nothing (String#[] mints a 1-char
112
+ # string per character scanned).
113
+ NAME_END_BYTES = [32, 47, 62].freeze # space, /, >
114
+ SKIP_BYTES = [32, 61].freeze # space, =
115
+ QUOTE_BYTES = [34, 39].freeze # ", '
116
+ SPACE_BYTE = 32
117
+ EMPTY_CONTENT_RE = /\A[ \t\r\n\f\v]*\z/
112
118
 
113
119
  # Normalize attribute order within XML tags so that
114
120
  # <elem b="2" a="1"> compares equal to <elem a="1" b="2">.
@@ -121,19 +127,23 @@ module Canon
121
127
  i = 0
122
128
 
123
129
  while i < text.length
124
- if text[i] == "<"
125
- # Handle processing instruction <?...?>, comment <!--...-->,
126
- # and regular tags
127
- new_i, tag_output = process_tag(text, i)
128
- if new_i
129
- result << tag_output
130
- i = new_i
131
- next
132
- end
130
+ lt = text.index("<", i)
131
+ if lt.nil?
132
+ result << text[i..]
133
+ break
134
+ end
135
+ result << text[i...lt] if lt > i
136
+
137
+ # Handle processing instruction <?...?>, comment <!--...-->,
138
+ # and regular tags
139
+ new_i, tag_output = process_tag(text, lt)
140
+ if new_i
141
+ result << tag_output
142
+ i = new_i
143
+ else
144
+ result << "<"
145
+ i = lt + 1
133
146
  end
134
-
135
- result << text[i]
136
- i += 1
137
147
  end
138
148
 
139
149
  result
@@ -178,57 +188,59 @@ module Canon
178
188
  # @param tag_content [String] Content between < and >
179
189
  # @return [Hash, nil] { name: String, attrs: Array<{name:, value:}> }
180
190
  def self.tokenize_tag_content(tag_content)
181
- return nil if tag_content.strip.empty?
191
+ return nil if tag_content.match?(EMPTY_CONTENT_RE)
182
192
 
183
193
  i = 0
194
+ length = tag_content.length
184
195
  # Find tag name (first non-whitespace word)
185
- i += 1 while i < tag_content.length && tag_content[i] == " "
186
- return nil if i >= tag_content.length
196
+ i += 1 while i < length && tag_content.getbyte(i) == SPACE_BYTE
197
+ return nil if i >= length
187
198
 
188
199
  name_start = i
189
- i += 1 while i < tag_content.length && tag_content[i] != " " &&
190
- tag_content[i] != "/" && tag_content[i] != ">"
200
+ i += 1 while i < length &&
201
+ !NAME_END_BYTES.include?(tag_content.getbyte(i))
191
202
  tag_name = tag_content[name_start...i]
192
203
 
193
204
  # Skip whitespace
194
- i += 1 while i < tag_content.length && tag_content[i] == " "
205
+ i += 1 while i < length && tag_content.getbyte(i) == SPACE_BYTE
195
206
 
196
207
  # Parse attributes
197
208
  attrs = []
198
- while i < tag_content.length
209
+ while i < length
199
210
  # Skip whitespace
200
- i += 1 while i < tag_content.length && tag_content[i] == " "
201
- break if i >= tag_content.length
211
+ i += 1 while i < length && tag_content.getbyte(i) == SPACE_BYTE
212
+ break if i >= length
202
213
 
203
214
  # Read attribute name
204
215
  attr_start = i
205
- i += 1 while i < tag_content.length && tag_content[i] != "=" &&
206
- tag_content[i] != " " && tag_content[i] != "/" &&
207
- tag_content[i] != ">"
208
- break if i >= tag_content.length || i == attr_start
216
+ i += 1 while i < length &&
217
+ !NAME_END_BYTES.include?(tag_content.getbyte(i)) &&
218
+ tag_content.getbyte(i) != 61
219
+ break if i >= length || i == attr_start
209
220
 
210
221
  attr_name = tag_content[attr_start...i]
211
222
 
212
223
  # Skip whitespace and =
213
- i += 1 while i < tag_content.length &&
214
- SKIP_CHARS.include?(tag_content[i])
215
- break if i >= tag_content.length
224
+ i += 1 while i < length &&
225
+ SKIP_BYTES.include?(tag_content.getbyte(i))
226
+ break if i >= length
216
227
 
217
228
  # Read quoted value
218
- quote = tag_content[i]
219
- break unless QUOTE_CHARS.include?(quote)
229
+ quote = tag_content.getbyte(i)
230
+ break unless QUOTE_BYTES.include?(quote)
220
231
 
221
232
  i += 1
222
233
  value_start = i
223
- while i < tag_content.length && tag_content[i] != quote
234
+ while i < length && tag_content.getbyte(i) != quote
224
235
  i += 1
225
236
  end
226
- break if i >= tag_content.length
237
+ break if i >= length
227
238
 
228
239
  attr_value = tag_content[value_start...i]
229
240
  i += 1 # skip closing quote
230
241
 
231
- attrs << { name: attr_name, value: "#{quote}#{attr_value}#{quote}" }
242
+ quote_char = quote.chr
243
+ attrs << { name: attr_name, value: "#{quote_char}#{attr_value}#{quote_char}" }
232
244
  end
233
245
 
234
246
  { name: tag_name, attrs: attrs }
@@ -241,9 +253,9 @@ module Canon
241
253
  # @param i [Integer] Position of '<'
242
254
  # @return [Array(Integer, String), nil] [new_position, tag_string] or nil
243
255
  def self.process_tag(text, pos)
244
- if text[pos + 1] == "?"
256
+ if text.getbyte(pos + 1) == 63 # ?
245
257
  process_processing_instruction(text, pos)
246
- elsif text[pos + 1] == "!" && text[(pos + 2)...(pos + 4)] == "--"
258
+ elsif text.getbyte(pos + 1) == 33 && text[(pos + 2)...(pos + 4)] == "--" # !
247
259
  process_comment(text, pos)
248
260
  else
249
261
  process_regular_tag(text, pos)
@@ -642,6 +642,23 @@ module Canon
642
642
  #
643
643
  # @param token [String] The token to apply visualization to
644
644
  # @param color [Symbol, nil] Optional color to apply
645
+ # Whole-string gsub through the map — the previous
646
+ # chars.map.fetch.join minted two arrays and one 1-char string
647
+ # per character of every displayed line.
648
+ def visualize(token)
649
+ return token if @visualization_map.empty?
650
+
651
+ token.gsub(visualization_union, @visualization_map)
652
+ end
653
+
654
+ # Longer keys first so "\r\n" wins over "\r"/"\n" at the
655
+ # same position.
656
+ def visualization_union
657
+ @visualization_union ||= Regexp.union(
658
+ @visualization_map.keys.sort_by(&:length).reverse,
659
+ )
660
+ end
661
+
645
662
  # @return [String] Visualized and optionally colored token
646
663
  def apply_visualization(token, color = nil)
647
664
  return "" if token.nil?
@@ -649,9 +666,7 @@ module Canon
649
666
  visual = if @character_visualization == :content_only
650
667
  visualize_content_only(token.to_s)
651
668
  else
652
- token.to_s.chars.map do |char|
653
- @visualization_map.fetch(char, char)
654
- end.join
669
+ visualize(token.to_s)
655
670
  end
656
671
 
657
672
  if color && @use_color
data/lib/canon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.61"
4
+ VERSION = "0.3.62"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.61
4
+ version: 0.3.62
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-19 00:00:00.000000000 Z
11
+ date: 2026-09-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs