| use serde::Deserialize; |
|
|
| use crate::error::{JsonRepairError, Result}; |
|
|
| pub struct JsonRepairParser { |
| chars: Vec<char>, |
| i: usize, |
| output: String, |
| } |
|
|
| impl JsonRepairParser { |
| pub fn new(text: String) -> Self { |
| Self { chars: text.chars().collect(), i: 0, output: String::new() } |
| } |
|
|
| pub fn parse<De: for<'de> Deserialize<'de>>(mut self) -> Result<De> { |
| self.parse_markdown_code_block(&["```", "[```", "{```"]); |
|
|
| let processed = self.parse_value()?; |
| if !processed { |
| return Err(JsonRepairError::UnexpectedEnd { position: self.chars.len() }); |
| } |
|
|
| self.parse_markdown_code_block(&["```", "```]", "```}"]); |
|
|
| let processed_comma = self.parse_character(','); |
| if processed_comma { |
| self.parse_whitespace_and_skip_comments(true); |
| } |
|
|
| if self.is_start_of_value(self.current_char()) && self.ends_with_comma_or_newline() { |
| if !processed_comma { |
| self.output = self.insert_before_last_whitespace(","); |
| } |
| self.parse_newline_delimited_json()?; |
| } else if processed_comma { |
| self.output = self.strip_last_occurrence(","); |
| } |
|
|
| |
| while matches!(self.current_char(), Some('}' | ']')) { |
| self.i += 1; |
| self.parse_whitespace_and_skip_comments(true); |
| } |
|
|
| if self.i >= self.chars.len() { |
| return Ok(serde_json::from_str::<De>(&self.output)?); |
| } |
|
|
| Err(JsonRepairError::UnexpectedCharacter { |
| character: self.current_char().unwrap_or('\0'), |
| position: self.i, |
| }) |
| } |
|
|
| fn current_char(&self) -> Option<char> { |
| self.chars.get(self.i).copied() |
| } |
|
|
| fn parse_value(&mut self) -> Result<bool> { |
| self.parse_whitespace_and_skip_comments(true); |
|
|
| let processed = self.parse_object()? |
| || self.parse_array()? |
| || self.parse_string(false, None)? |
| || self.parse_number()? |
| || self.parse_keywords() |
| || self.parse_unquoted_string(false)? |
| || self.parse_regex()?; |
|
|
| self.parse_whitespace_and_skip_comments(true); |
| Ok(processed) |
| } |
|
|
| fn parse_whitespace_and_skip_comments(&mut self, skip_newline: bool) -> bool { |
| let start = self.i; |
|
|
| self.parse_whitespace(skip_newline); |
| loop { |
| if self.parse_comment() { |
| self.parse_whitespace(skip_newline); |
| } else { |
| break; |
| } |
| } |
|
|
| self.i > start |
| } |
|
|
| fn parse_whitespace(&mut self, skip_newline: bool) -> bool { |
| let mut whitespace = String::new(); |
|
|
| while let Some(ch) = self.current_char() { |
| if self.is_whitespace(ch, skip_newline) { |
| whitespace.push(ch); |
| self.i += 1; |
| } else if self.is_special_whitespace(ch) { |
| whitespace.push(' '); |
| self.i += 1; |
| } else { |
| break; |
| } |
| } |
|
|
| if !whitespace.is_empty() { |
| self.output.push_str(&whitespace); |
| true |
| } else { |
| false |
| } |
| } |
|
|
| fn parse_comment(&mut self) -> bool { |
| |
| if self.current_char() == Some('/') && self.chars.get(self.i + 1) == Some(&'*') { |
| while self.i < self.chars.len() && !self.at_end_of_block_comment() { |
| self.i += 1; |
| } |
| self.i += 2; |
| return true; |
| } |
|
|
| |
| if self.current_char() == Some('/') && self.chars.get(self.i + 1) == Some(&'/') { |
| while self.i < self.chars.len() && self.current_char() != Some('\n') { |
| self.i += 1; |
| } |
| return true; |
| } |
|
|
| false |
| } |
|
|
| fn parse_markdown_code_block(&mut self, blocks: &[&str]) -> bool { |
| if self.skip_markdown_code_block(blocks) { |
| if self.is_function_name_char_start(self.current_char()) { |
| while self.i < self.chars.len() && self.is_function_name_char(self.current_char()) { |
| self.i += 1; |
| } |
| } |
| self.parse_whitespace_and_skip_comments(true); |
| return true; |
| } |
| false |
| } |
|
|
| fn skip_markdown_code_block(&mut self, blocks: &[&str]) -> bool { |
| for block in blocks { |
| let block_chars: Vec<char> = block.chars().collect(); |
| if self.i + block_chars.len() <= self.chars.len() { |
| let slice = &self.chars.get(self.i..self.i + block_chars.len()); |
| if slice == &Some(block_chars.as_slice()) { |
| self.i += block_chars.len(); |
| return true; |
| } |
| } |
| } |
| false |
| } |
|
|
| fn parse_character(&mut self, expected: char) -> bool { |
| if self.current_char() == Some(expected) { |
| self.output.push(expected); |
| self.i += 1; |
| true |
| } else { |
| false |
| } |
| } |
|
|
| fn skip_character(&mut self, expected: char) -> bool { |
| if self.current_char() == Some(expected) { |
| self.i += 1; |
| true |
| } else { |
| false |
| } |
| } |
|
|
| fn skip_ellipsis(&mut self) -> bool { |
| self.parse_whitespace_and_skip_comments(true); |
|
|
| if self.current_char() == Some('.') |
| && self.chars.get(self.i + 1) == Some(&'.') |
| && self.chars.get(self.i + 2) == Some(&'.') |
| { |
| self.i += 3; |
| self.parse_whitespace_and_skip_comments(true); |
| self.skip_character(','); |
| return true; |
| } |
| false |
| } |
|
|
| fn parse_object(&mut self) -> Result<bool> { |
| if self.current_char() != Some('{') { |
| return Ok(false); |
| } |
|
|
| self.output.push('{'); |
| self.i += 1; |
| self.parse_whitespace_and_skip_comments(true); |
|
|
| |
| if self.skip_character(',') { |
| self.parse_whitespace_and_skip_comments(true); |
| } |
|
|
| let mut initial = true; |
| while self.i < self.chars.len() && self.current_char() != Some('}') { |
| let _processed_comma = if !initial { |
| let comma = self.parse_character(','); |
| if !comma { |
| |
| self.output = self.insert_before_last_whitespace(","); |
| } |
| self.parse_whitespace_and_skip_comments(true); |
| comma |
| } else { |
| initial = false; |
| true |
| }; |
|
|
| self.skip_ellipsis(); |
|
|
| let processed_key = |
| self.parse_string(false, None)? || self.parse_unquoted_string(true)?; |
| if !processed_key { |
| if matches!(self.current_char(), Some('}' | '{' | ']' | '[') | None) { |
| |
| self.output = self.strip_last_occurrence(","); |
| } else { |
| return Err(JsonRepairError::ObjectKeyExpected { position: self.i }); |
| } |
| break; |
| } |
|
|
| self.parse_whitespace_and_skip_comments(true); |
| let processed_colon = self.parse_character(':'); |
| let truncated_text = self.i >= self.chars.len(); |
|
|
| if !processed_colon { |
| if self.is_start_of_value(self.current_char()) || truncated_text { |
| |
| self.output = self.insert_before_last_whitespace(":"); |
| } else { |
| return Err(JsonRepairError::ColonExpected { position: self.i }); |
| } |
| } |
|
|
| let processed_value = self.parse_value()?; |
| if !processed_value { |
| if processed_colon || truncated_text { |
| |
| self.output.push_str("null"); |
| } else { |
| return Err(JsonRepairError::ColonExpected { position: self.i }); |
| } |
| } |
| } |
|
|
| if self.current_char() == Some('}') { |
| self.output.push('}'); |
| self.i += 1; |
| } else { |
| |
| self.output = self.insert_before_last_whitespace("}"); |
| } |
|
|
| Ok(true) |
| } |
|
|
| fn parse_array(&mut self) -> Result<bool> { |
| if self.current_char() != Some('[') { |
| return Ok(false); |
| } |
|
|
| self.output.push('['); |
| self.i += 1; |
| self.parse_whitespace_and_skip_comments(true); |
|
|
| |
| if self.skip_character(',') { |
| self.parse_whitespace_and_skip_comments(true); |
| } |
|
|
| let mut initial = true; |
| while self.i < self.chars.len() && self.current_char() != Some(']') { |
| if !initial { |
| let processed_comma = self.parse_character(','); |
| if !processed_comma { |
| |
| self.output = self.insert_before_last_whitespace(","); |
| } |
| } else { |
| initial = false; |
| } |
|
|
| self.skip_ellipsis(); |
|
|
| let processed_value = self.parse_value()?; |
| if !processed_value { |
| |
| self.output = self.strip_last_occurrence(","); |
| break; |
| } |
| } |
|
|
| if self.current_char() == Some(']') { |
| self.output.push(']'); |
| self.i += 1; |
| } else { |
| |
| self.output = self.insert_before_last_whitespace("]"); |
| } |
|
|
| Ok(true) |
| } |
|
|
| fn parse_newline_delimited_json(&mut self) -> Result<()> { |
| let mut initial = true; |
| let mut processed_value = true; |
|
|
| while processed_value { |
| if !initial { |
| let processed_comma = self.parse_character(','); |
| if !processed_comma { |
| |
| self.output = self.insert_before_last_whitespace(","); |
| } |
| } else { |
| initial = false; |
| } |
|
|
| processed_value = self.parse_value()?; |
| } |
|
|
| if !processed_value { |
| |
| self.output = self.strip_last_occurrence(","); |
| } |
|
|
| |
| self.output = format!("[\n{}\n]", self.output); |
| Ok(()) |
| } |
|
|
| fn parse_string( |
| &mut self, |
| stop_at_delimiter: bool, |
| stop_at_index: Option<usize>, |
| ) -> Result<bool> { |
| let skip_escape_chars = self.current_char() == Some('\\'); |
| if skip_escape_chars { |
| |
| self.i += 1; |
| } |
|
|
| if !self.is_quote(self.current_char()) { |
| return Ok(false); |
| } |
|
|
| let quote_char = self.current_char().unwrap(); |
| let is_end_quote = self.get_end_quote_matcher(quote_char); |
|
|
| let i_before = self.i; |
| let o_before = self.output.len(); |
|
|
| let mut str_content = String::from("\""); |
| self.i += 1; |
|
|
| loop { |
| if self.i >= self.chars.len() { |
| |
| let i_prev = self.prev_non_whitespace_index(self.i - 1); |
| if !stop_at_delimiter && self.is_delimiter(self.chars.get(i_prev).copied()) { |
| |
| self.i = i_before; |
| self.output.truncate(o_before); |
| return self.parse_string(true, None); |
| } |
|
|
| |
| str_content = self.insert_before_last_whitespace_str(&str_content, "\""); |
| self.output.push_str(&str_content); |
| return Ok(true); |
| } |
|
|
| if let Some(stop_idx) = stop_at_index |
| && self.i == stop_idx |
| { |
| str_content = self.insert_before_last_whitespace_str(&str_content, "\""); |
| self.output.push_str(&str_content); |
| return Ok(true); |
| } |
|
|
| let ch = self.current_char().unwrap(); |
|
|
| if is_end_quote(ch) { |
| |
| let i_quote = self.i; |
| let o_quote = str_content.len(); |
| str_content.push('"'); |
| self.i += 1; |
| self.output.push_str(&str_content); |
|
|
| self.parse_whitespace_and_skip_comments(false); |
|
|
| if stop_at_delimiter |
| || self.i >= self.chars.len() |
| || self.is_delimiter(self.current_char()) |
| || self.is_quote(self.current_char()) |
| || self.is_digit(self.current_char()) |
| { |
| |
| self.parse_concatenated_string()?; |
| return Ok(true); |
| } |
|
|
| let i_prev_char = self.prev_non_whitespace_index(i_quote - 1); |
| let prev_char = self.chars.get(i_prev_char).copied(); |
|
|
| if prev_char == Some(',') { |
| |
| self.i = i_before; |
| self.output.truncate(o_before); |
| return self.parse_string(false, Some(i_prev_char)); |
| } |
|
|
| if self.is_delimiter(prev_char) { |
| |
| self.i = i_before; |
| self.output.truncate(o_before); |
| return self.parse_string(true, None); |
| } |
|
|
| |
| self.output.truncate(o_before); |
| self.i = i_quote + 1; |
| let prefix = str_content.get(..o_quote).unwrap_or(""); |
| str_content = format!("{}\\\"", prefix); |
| } else if stop_at_delimiter && self.is_unquoted_string_delimiter(ch) { |
| |
|
|
| |
| let url_candidate = if self.chars.get(self.i - 1) == Some(&':') |
| && i_before + 1 < self.chars.len() |
| && self.i + 2 <= self.chars.len() |
| { |
| self.chars.get(i_before + 1..self.i + 2) |
| } else { |
| None |
| }; |
| if url_candidate.is_some_and(|s| self.is_url_start(s)) { |
| while self.i < self.chars.len() |
| && self.is_url_char(self.current_char().unwrap()) |
| { |
| str_content.push(self.current_char().unwrap()); |
| self.i += 1; |
| } |
| } |
|
|
| |
| str_content = self.insert_before_last_whitespace_str(&str_content, "\""); |
| self.output.push_str(&str_content); |
| self.parse_concatenated_string()?; |
| return Ok(true); |
| } else if ch == '\\' { |
| |
| if let Some(next_ch) = self.chars.get(self.i + 1) { |
| match next_ch { |
| '"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't' => { |
| let escaped: String = self |
| .chars |
| .get(self.i..self.i + 2) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| str_content.push_str(&escaped); |
| self.i += 2; |
| } |
| ',' if skip_escape_chars => { |
| |
| |
| str_content = |
| self.insert_before_last_whitespace_str(&str_content, "\""); |
| self.output.push_str(&str_content); |
| self.output.push(','); |
| self.i += 2; |
| self.parse_whitespace_and_skip_comments(true); |
| return Ok(true); |
| } |
| 'u' => { |
| let mut j = 2; |
| while j < 6 && self.i + j < self.chars.len() { |
| let hex_ch = self.chars.get(self.i + j).copied().unwrap_or('\0'); |
| if !self.is_hex(hex_ch) { |
| break; |
| } |
| j += 1; |
| } |
|
|
| if j == 6 { |
| let unicode_seq: String = self |
| .chars |
| .get(self.i..self.i + 6) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| str_content.push_str(&unicode_seq); |
| self.i += 6; |
| } else if self.i + j >= self.chars.len() { |
| |
| self.i = self.chars.len(); |
| } else { |
| |
| |
| let invalid_chars: String = self |
| .chars |
| .get(self.i..self.i + j.min(6)) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| return Err(JsonRepairError::InvalidUnicodeCharacter { |
| chars: invalid_chars, |
| position: self.i, |
| }); |
| } |
| } |
| _ => { |
| |
| str_content.push(*next_ch); |
| self.i += 2; |
| } |
| } |
| } else { |
| self.i += 1; |
| } |
| } else { |
| |
| if ch == '"' && self.chars.get(self.i - 1) != Some(&'\\') { |
| |
| str_content.push_str("\\\""); |
| self.i += 1; |
| } else if self.is_control_character(ch) { |
| |
| str_content.push_str(&self.get_control_character_escape(ch)); |
| self.i += 1; |
| } else { |
| if !self.is_valid_string_character(ch) { |
| return Err(JsonRepairError::InvalidCharacter { |
| character: ch, |
| position: self.i, |
| }); |
| } |
| str_content.push(ch); |
| self.i += 1; |
| } |
| } |
|
|
| if skip_escape_chars { |
| |
| if self.current_char() == Some('\\') { |
| self.i += 1; |
| } |
| } |
| } |
| } |
|
|
| fn parse_concatenated_string(&mut self) -> Result<bool> { |
| let mut processed = false; |
|
|
| self.parse_whitespace_and_skip_comments(true); |
| while self.current_char() == Some('+') { |
| processed = true; |
| self.i += 1; |
| self.parse_whitespace_and_skip_comments(true); |
|
|
| |
| self.output = self.strip_last_occurrence_with_remaining("\"", true); |
| let start = self.output.len(); |
| let parsed_str = self.parse_string(false, None)?; |
| if parsed_str { |
| |
| self.output = self.remove_at_index(start, 1); |
| } else { |
| |
| self.output = self.insert_before_last_whitespace("\""); |
| } |
| } |
|
|
| Ok(processed) |
| } |
|
|
| fn parse_number(&mut self) -> Result<bool> { |
| let start = self.i; |
|
|
| if self.current_char() == Some('-') { |
| self.i += 1; |
| if self.at_end_of_number() { |
| self.repair_number_ending_with_numeric_symbol(start); |
| return Ok(true); |
| } |
| if !self.is_digit(self.current_char()) { |
| self.i = start; |
| return Ok(false); |
| } |
| } |
|
|
| while self.is_digit(self.current_char()) { |
| self.i += 1; |
| } |
|
|
| if self.current_char() == Some('.') { |
| self.i += 1; |
| if self.at_end_of_number() { |
| self.repair_number_ending_with_numeric_symbol(start); |
| return Ok(true); |
| } |
| if !self.is_digit(self.current_char()) { |
| self.i = start; |
| return Ok(false); |
| } |
| while self.is_digit(self.current_char()) { |
| self.i += 1; |
| } |
| } |
|
|
| if matches!(self.current_char(), Some('e' | 'E')) { |
| self.i += 1; |
| if matches!(self.current_char(), Some('-' | '+')) { |
| self.i += 1; |
| } |
| if self.at_end_of_number() { |
| self.repair_number_ending_with_numeric_symbol(start); |
| return Ok(true); |
| } |
| if !self.is_digit(self.current_char()) { |
| self.i = start; |
| return Ok(false); |
| } |
| while self.is_digit(self.current_char()) { |
| self.i += 1; |
| } |
| } |
|
|
| if !self.at_end_of_number() { |
| self.i = start; |
| return Ok(false); |
| } |
|
|
| if self.i > start { |
| let num: String = self |
| .chars |
| .get(start..self.i) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| let has_invalid_leading_zero = |
| num.len() > 1 && num.starts_with('0') && self.is_digit(num.chars().nth(1)); |
|
|
| if has_invalid_leading_zero { |
| self.output.push_str(&serde_json::to_string(&num).unwrap()); |
| } else { |
| self.output.push_str(&num); |
| } |
| return Ok(true); |
| } |
|
|
| Ok(false) |
| } |
|
|
| fn parse_keywords(&mut self) -> bool { |
| self.parse_keyword("true", "true") |
| || self.parse_keyword("false", "false") |
| || self.parse_keyword("null", "null") |
| || self.parse_keyword("True", "true") |
| || self.parse_keyword("False", "false") |
| || self.parse_keyword("None", "null") |
| } |
|
|
| fn parse_keyword(&mut self, name: &str, value: &str) -> bool { |
| let name_chars: Vec<char> = name.chars().collect(); |
| if self.i + name_chars.len() <= self.chars.len() { |
| let slice = self.chars.get(self.i..self.i + name_chars.len()); |
| if slice == Some(name_chars.as_slice()) { |
| self.output.push_str(value); |
| self.i += name_chars.len(); |
| return true; |
| } |
| } |
| false |
| } |
|
|
| fn parse_unquoted_string(&mut self, is_key: bool) -> Result<bool> { |
| let start = self.i; |
|
|
| if self.is_function_name_char_start(self.current_char()) { |
| while self.i < self.chars.len() && self.is_function_name_char(self.current_char()) { |
| self.i += 1; |
| } |
|
|
| let mut j = self.i; |
| while j < self.chars.len() { |
| let ch = self.chars.get(j).copied().unwrap_or('\0'); |
| if !self.is_whitespace(ch, true) { |
| break; |
| } |
| j += 1; |
| } |
|
|
| if j < self.chars.len() { |
| let ch_at_j = self.chars.get(j).copied().unwrap_or('\0'); |
| if ch_at_j == '(' { |
| |
| self.i = j + 1; |
| self.parse_value()?; |
| if self.current_char() == Some(')') { |
| self.i += 1; |
| if self.current_char() == Some(';') { |
| self.i += 1; |
| } |
| } |
| return Ok(true); |
| } |
| } |
| } |
|
|
| |
| self.i = start; |
|
|
| while self.i < self.chars.len() { |
| let ch = self.current_char().unwrap(); |
| if self.is_unquoted_string_delimiter(ch) |
| || self.is_quote(Some(ch)) |
| || (is_key && ch == ':') |
| { |
| break; |
| } |
| self.i += 1; |
| } |
|
|
| |
| if self.i > 0 |
| && self.chars.get(self.i - 1) == Some(&':') |
| && start < self.chars.len() |
| && self.i + 2 <= self.chars.len() |
| && self.is_url_start(self.chars.get(start..self.i + 2).unwrap_or(&[])) |
| { |
| while self.i < self.chars.len() && self.is_url_char(self.current_char().unwrap()) { |
| self.i += 1; |
| } |
| } |
|
|
| if self.i > start { |
| |
| while self.i > start |
| && self.is_whitespace(self.chars.get(self.i - 1).copied().unwrap_or('\0'), true) |
| { |
| self.i -= 1; |
| } |
|
|
| let symbol: String = self |
| .chars |
| .get(start..self.i) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
|
|
| if symbol == "undefined" { |
| self.output.push_str("null"); |
| } else if is_key { |
| |
| self.output |
| .push_str(&serde_json::to_string(&symbol).unwrap()); |
| } else { |
| |
| if symbol.parse::<f64>().is_ok() { |
| |
| self.output.push_str(&symbol); |
| } else { |
| |
| self.output |
| .push_str(&serde_json::to_string(&symbol).unwrap()); |
| } |
| } |
|
|
| if self.current_char() == Some('"') { |
| |
| self.i += 1; |
| } |
|
|
| return Ok(true); |
| } |
|
|
| Ok(false) |
| } |
|
|
| fn parse_regex(&mut self) -> Result<bool> { |
| if self.current_char() == Some('/') { |
| let start = self.i; |
| self.i += 1; |
|
|
| while self.i < self.chars.len() |
| && (self.current_char() != Some('/') |
| || (self.i > 0 && self.chars.get(self.i - 1) == Some(&'\\'))) |
| { |
| self.i += 1; |
| } |
|
|
| |
| if self.i < self.chars.len() { |
| self.i += 1; |
| } |
|
|
| let regex: String = self |
| .chars |
| .get(start..self.i) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| self.output |
| .push_str(&serde_json::to_string(®ex).unwrap()); |
| return Ok(true); |
| } |
| Ok(false) |
| } |
|
|
| |
| fn is_whitespace(&self, ch: char, skip_newline: bool) -> bool { |
| if skip_newline { |
| ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r' |
| } else { |
| ch == ' ' || ch == '\t' || ch == '\r' |
| } |
| } |
|
|
| fn is_special_whitespace(&self, ch: char) -> bool { |
| matches!(ch as u32, 0xa0 | 0x2000..=0x200a | 0x202f | 0x205f | 0x3000) |
| } |
|
|
| fn is_quote(&self, ch: Option<char>) -> bool { |
| matches!( |
| ch, |
| Some('"' | '\'' | '`' | '\u{2018}' | '\u{2019}' | '\u{201c}' | '\u{201d}' | '\u{00b4}') |
| ) |
| } |
|
|
| fn is_digit(&self, ch: Option<char>) -> bool { |
| matches!(ch, Some('0'..='9')) |
| } |
|
|
| fn is_hex(&self, ch: char) -> bool { |
| ch.is_ascii_hexdigit() |
| } |
|
|
| fn is_function_name_char_start(&self, ch: Option<char>) -> bool { |
| matches!(ch, Some('a'..='z' | 'A'..='Z' | '_' | '$')) |
| } |
|
|
| fn is_function_name_char(&self, ch: Option<char>) -> bool { |
| matches!(ch, Some('a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '$')) |
| } |
|
|
| fn is_delimiter(&self, ch: Option<char>) -> bool { |
| matches!( |
| ch, |
| Some(',' | ':' | '[' | ']' | '/' | '{' | '}' | '(' | ')' | '\n' | '+') |
| ) |
| } |
|
|
| fn is_unquoted_string_delimiter(&self, ch: char) -> bool { |
| matches!(ch, ',' | '[' | ']' | '/' | '{' | '}' | '\n' | '+') |
| } |
|
|
| fn is_start_of_value(&self, ch: Option<char>) -> bool { |
| self.is_quote(ch) |
| || matches!( |
| ch, |
| Some('[' | '{' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-') |
| ) |
| } |
|
|
| fn is_control_character(&self, ch: char) -> bool { |
| matches!(ch, '\n' | '\r' | '\t' | '\u{08}' | '\u{0c}') |
| } |
|
|
| fn is_valid_string_character(&self, ch: char) -> bool { |
| ch >= '\u{0020}' |
| } |
|
|
| fn is_url_start(&self, chars: &[char]) -> bool { |
| if chars.len() < 7 { |
| return false; |
| } |
| let s: String = chars.iter().collect(); |
| s.starts_with("http://") |
| || s.starts_with("https://") |
| || s.starts_with("ftp://") |
| || s.starts_with("mailto:") |
| || s.starts_with("file://") |
| || s.starts_with("data:") |
| || s.starts_with("irc://") |
| } |
|
|
| fn is_url_char(&self, ch: char) -> bool { |
| ch.is_ascii_alphanumeric() |
| || matches!( |
| ch, |
| '-' | '.' |
| | '_' |
| | '~' |
| | ':' |
| | '/' |
| | '?' |
| | '#' |
| | '@' |
| | '!' |
| | '$' |
| | '&' |
| | '\'' |
| | '(' |
| | ')' |
| | '*' |
| | '+' |
| | ';' |
| | '=' |
| ) |
| } |
|
|
| fn get_end_quote_matcher(&self, quote_char: char) -> fn(char) -> bool { |
| match quote_char { |
| '"' => |ch| ch == '"', |
| '\'' => |ch| ch == '\'', |
| '\u{2018}' => |ch| matches!(ch, '\u{2018}' | '\u{2019}'), |
| '\u{2019}' => |ch| matches!(ch, '\u{2018}' | '\u{2019}'), |
| '\u{201c}' => |ch| matches!(ch, '\u{201c}' | '\u{201d}'), |
| '\u{201d}' => |ch| matches!(ch, '\u{201c}' | '\u{201d}'), |
| '`' => |ch| matches!(ch, '`' | '\u{00b4}' | '\''), |
| '\u{00b4}' => |ch| matches!(ch, '`' | '\u{00b4}' | '\''), |
| _ => |ch| ch == '"', |
| } |
| } |
|
|
| fn get_control_character_escape(&self, ch: char) -> String { |
| match ch { |
| '\u{08}' => "\\b".to_string(), |
| '\u{0c}' => "\\f".to_string(), |
| '\n' => "\\n".to_string(), |
| '\r' => "\\r".to_string(), |
| '\t' => "\\t".to_string(), |
| _ => ch.to_string(), |
| } |
| } |
|
|
| fn at_end_of_block_comment(&self) -> bool { |
| self.current_char() == Some('*') && self.chars.get(self.i + 1) == Some(&'/') |
| } |
|
|
| fn prev_non_whitespace_index(&self, start: usize) -> usize { |
| let mut prev = start; |
| while prev > 0 |
| && prev < self.chars.len() |
| && self.is_whitespace(self.chars.get(prev).copied().unwrap_or('\0'), true) |
| { |
| prev = prev.saturating_sub(1); |
| } |
| prev |
| } |
|
|
| fn at_end_of_number(&self) -> bool { |
| self.i >= self.chars.len() |
| || self.is_delimiter(self.current_char()) |
| || self.is_whitespace(self.current_char().unwrap_or('\0'), true) |
| } |
|
|
| fn repair_number_ending_with_numeric_symbol(&mut self, start: usize) { |
| let num: String = self |
| .chars |
| .get(start..self.i) |
| .map(|s| s.iter().collect()) |
| .unwrap_or_default(); |
| self.output.push_str(&format!("{num}0")); |
| } |
|
|
| fn ends_with_comma_or_newline(&self) -> bool { |
| let regex = regex::Regex::new(r"[,\n][ \t\r]*$").unwrap(); |
| regex.is_match(&self.output) |
| } |
|
|
| fn strip_last_occurrence(&self, text_to_strip: &str) -> String { |
| self.strip_last_occurrence_with_remaining(text_to_strip, false) |
| } |
|
|
| fn strip_last_occurrence_with_remaining( |
| &self, |
| text_to_strip: &str, |
| strip_remaining: bool, |
| ) -> String { |
| if let Some(index) = self.output.rfind(text_to_strip) { |
| let mut result = self.output.get(..index).unwrap_or("").to_string(); |
| if !strip_remaining { |
| result.push_str(self.output.get(index + text_to_strip.len()..).unwrap_or("")); |
| } |
| result |
| } else { |
| self.output.clone() |
| } |
| } |
|
|
| fn insert_before_last_whitespace(&self, text_to_insert: &str) -> String { |
| let chars: Vec<char> = self.output.chars().collect(); |
| let mut index = chars.len(); |
|
|
| if index == 0 || !self.is_whitespace(chars.get(index - 1).copied().unwrap_or('\0'), true) { |
| return format!("{}{}", self.output, text_to_insert); |
| } |
|
|
| while index > 0 && self.is_whitespace(chars.get(index - 1).copied().unwrap_or('\0'), true) { |
| index -= 1; |
| } |
|
|
| |
| let byte_index = self |
| .output |
| .char_indices() |
| .nth(index) |
| .map_or(self.output.len(), |(i, _)| i); |
|
|
| format!( |
| "{}{}{}", |
| self.output.get(..byte_index).unwrap_or(""), |
| text_to_insert, |
| self.output.get(byte_index..).unwrap_or("") |
| ) |
| } |
|
|
| fn insert_before_last_whitespace_str(&self, text: &str, text_to_insert: &str) -> String { |
| let chars: Vec<char> = text.chars().collect(); |
| let mut index = chars.len(); |
|
|
| if index == 0 || !self.is_whitespace(chars.get(index - 1).copied().unwrap_or('\0'), true) { |
| return format!("{text}{text_to_insert}"); |
| } |
|
|
| while index > 0 && self.is_whitespace(chars.get(index - 1).copied().unwrap_or('\0'), true) { |
| index -= 1; |
| } |
|
|
| |
| let byte_index = text |
| .char_indices() |
| .nth(index) |
| .map_or(text.len(), |(i, _)| i); |
|
|
| format!( |
| "{}{}{}", |
| text.get(..byte_index).unwrap_or(""), |
| text_to_insert, |
| text.get(byte_index..).unwrap_or("") |
| ) |
| } |
|
|
| fn remove_at_index(&self, start: usize, count: usize) -> String { |
| let chars: Vec<char> = self.output.chars().collect(); |
| let mut result = String::new(); |
| for (i, ch) in chars.iter().enumerate() { |
| if i < start || i >= start + count { |
| result.push(*ch); |
| } |
| } |
| result |
| } |
| } |
|
|
| pub fn json_repair<De: for<'de> Deserialize<'de>>(text: &str) -> Result<De> { |
| let parser = JsonRepairParser::new(text.to_string()); |
| parser.parse() |
| } |
|
|