Vin result port and passes

This commit is contained in:
Henry Schimke
2022-09-10 11:41:18 -05:00
parent cf14fb6742
commit 6a059c5a4a
4 changed files with 365 additions and 249 deletions

View File

@@ -37,7 +37,7 @@ use super::{
BookmarkDoCoMoResultParser, EmailAddressResultParser, EmailDoCoMoResultParser, GeoResultParser, BookmarkDoCoMoResultParser, EmailAddressResultParser, EmailDoCoMoResultParser, GeoResultParser,
ISBNResultParser, ParsedClientResult, ParsedRXingResult, ProductResultParser, ISBNResultParser, ParsedClientResult, ParsedRXingResult, ProductResultParser,
SMSMMSResultParser, SMTPResultParser, TelResultParser, TextParsedRXingResult, URIResultParser, SMSMMSResultParser, SMTPResultParser, TelResultParser, TextParsedRXingResult, URIResultParser,
URLTOResultParser, WifiResultParser, URLTOResultParser, VINResultParser, WifiResultParser,
}; };
/** /**
@@ -104,7 +104,7 @@ pub fn getMassagedText(result: &RXingResult) -> String {
} }
pub fn parseRXingResult(theRXingResult: &RXingResult) -> ParsedClientResult { pub fn parseRXingResult(theRXingResult: &RXingResult) -> ParsedClientResult {
let PARSERS: [&ParserFunction; 13] = [ let PARSERS: [&ParserFunction; 14] = [
&BookmarkDoCoMoResultParser::parse, &BookmarkDoCoMoResultParser::parse,
// new AddressBookDoCoMoRXingResultParser(), // new AddressBookDoCoMoRXingResultParser(),
&EmailDoCoMoResultParser::parse, &EmailDoCoMoResultParser::parse,
@@ -124,7 +124,7 @@ pub fn parseRXingResult(theRXingResult: &RXingResult) -> ParsedClientResult {
&ISBNResultParser::parse, &ISBNResultParser::parse,
&ProductResultParser::parse, &ProductResultParser::parse,
// new ExpandedProductRXingResultParser(), // new ExpandedProductRXingResultParser(),
// new VINRXingResultParser(), &VINResultParser::parse,
]; ];
for parser in PARSERS { for parser in PARSERS {

View File

@@ -14,96 +14,110 @@
* limitations under the License. * limitations under the License.
*/ */
// package com.google.zxing.client.result; // package com.google.zxing.client.result;
use super::{ParsedRXingResult, ParsedRXingResultType};
/** /**
* Represents a parsed result that encodes a Vehicle Identification Number (VIN). * Represents a parsed result that encodes a Vehicle Identification Number (VIN).
*/ */
pub struct VINParsedRXingResult { pub struct VINParsedRXingResult {
vin: String,
private final String vin; world_manufacturer_id: String,
private final String worldManufacturerID; vehicle_descriptor_section: String,
private final String vehicleDescriptorSection; vehicle_identifier_section: String,
private final String vehicleIdentifierSection; country_code: String,
private final String countryCode; vehicle_attributes: String,
private final String vehicleAttributes; model_year: u32,
private final int modelYear; plant_code: char,
private final char plantCode; sequentialNumber: String,
private final String sequentialNumber; }
impl ParsedRXingResult for VINParsedRXingResult {} impl ParsedRXingResult for VINParsedRXingResult {
impl VINParsedRXingResult{ fn getType(&self) -> super::ParsedRXingResultType {
ParsedRXingResultType::VIN
public VINParsedRXingResult(String vin, }
String worldManufacturerID,
String vehicleDescriptorSection, fn getDisplayRXingResult(&self) -> String {
String vehicleIdentifierSection, let mut result = String::with_capacity(50);
String countryCode, result.push_str(&self.world_manufacturer_id);
String vehicleAttributes, result.push(' ');
int modelYear, result.push_str(&self.vehicle_descriptor_section);
char plantCode, result.push(' ');
String sequentialNumber) { result.push_str(&self.vehicle_identifier_section);
super(ParsedRXingResultType.VIN); result.push('\n');
this.vin = vin; if !self.country_code.is_empty() {
this.worldManufacturerID = worldManufacturerID; result.push_str(&self.country_code);
this.vehicleDescriptorSection = vehicleDescriptorSection; result.push(' ');
this.vehicleIdentifierSection = vehicleIdentifierSection; }
this.countryCode = countryCode; result.push_str(&self.model_year.to_string());
this.vehicleAttributes = vehicleAttributes; result.push(' ');
this.modelYear = modelYear; result.push(self.plant_code);
this.plantCode = plantCode; result.push(' ');
this.sequentialNumber = sequentialNumber; result.push_str(&self.sequentialNumber);
} result.push('\n');
public String getVIN() { result
return vin; }
} }
impl VINParsedRXingResult {
public String getWorldManufacturerID() { pub fn new(
return worldManufacturerID; vin: String,
} worldManufacturerID: String,
vehicleDescriptorSection: String,
public String getVehicleDescriptorSection() { vehicleIdentifierSection: String,
return vehicleDescriptorSection; countryCode: String,
} vehicleAttributes: String,
modelYear: u32,
public String getVehicleIdentifierSection() { plantCode: char,
return vehicleIdentifierSection; sequentialNumber: String,
} ) -> Self {
Self {
public String getCountryCode() { vin,
return countryCode; world_manufacturer_id: worldManufacturerID,
} vehicle_descriptor_section: vehicleDescriptorSection,
vehicle_identifier_section: vehicleIdentifierSection,
public String getVehicleAttributes() { country_code: countryCode,
return vehicleAttributes; vehicle_attributes: vehicleAttributes,
} model_year: modelYear,
plant_code: plantCode,
public int getModelYear() { sequentialNumber,
return modelYear; }
} }
public char getPlantCode() { pub fn getVIN(&self) -> &str {
return plantCode; &self.vin
} }
public String getSequentialNumber() { pub fn getWorldManufacturerID(&self) -> &str {
return sequentialNumber; &self.world_manufacturer_id
} }
@Override pub fn getVehicleDescriptorSection(&self) -> &str {
public String getDisplayRXingResult() { &self.vehicle_descriptor_section
StringBuilder result = new StringBuilder(50); }
result.append(worldManufacturerID).append(' ');
result.append(vehicleDescriptorSection).append(' '); pub fn getVehicleIdentifierSection(&self) -> &str {
result.append(vehicleIdentifierSection).append('\n'); &self.vehicle_identifier_section
if (countryCode != null) { }
result.append(countryCode).append(' ');
} pub fn getCountryCode(&self) -> &str {
result.append(modelYear).append(' '); &self.country_code
result.append(plantCode).append(' '); }
result.append(sequentialNumber).append('\n');
return result.toString(); pub fn getVehicleAttributes(&self) -> &str {
} &self.vehicle_attributes
}
pub fn getModelYear(&self) -> u32 {
self.model_year
}
pub fn getPlantCode(&self) -> char {
self.plant_code
}
pub fn getSequentialNumber(&self) -> &str {
&self.sequentialNumber
}
} }

View File

@@ -14,57 +14,107 @@
* limitations under the License. * limitations under the License.
*/ */
// package com.google.zxing.client.result;
package com.google.zxing.client.result; // import com.google.zxing.BarcodeFormat;
// import com.google.zxing.RXingResult;
import com.google.zxing.BarcodeFormat; // import org.junit.Assert;
import com.google.zxing.RXingResult; // import org.junit.Test;
import org.junit.Assert;
import org.junit.Test;
/** /**
* Tests {@link VINParsedRXingResult}. * Tests {@link VINParsedRXingResult}.
*/ */
public final class VINParsedRXingResultTestCase extends Assert { // public final class VINParsedRXingResultTestCase extends Assert {
use crate::{
client::result::{ParsedClientResult, ParsedRXingResult, ParsedRXingResultType},
BarcodeFormat, RXingResult,
};
@Test use super::ResultParser;
public void testNotVIN() {
RXingResult fakeRXingResult = new RXingResult("1M8GDM9A1KP042788", null, null, BarcodeFormat.CODE_39);
ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult);
assertEquals(ParsedRXingResultType.TEXT, result.getType());
fakeRXingResult = new RXingResult("1M8GDM9AXKP042788", null, null, BarcodeFormat.CODE_128);
result = RXingResultParser.parseRXingResult(fakeRXingResult);
assertEquals(ParsedRXingResultType.TEXT, result.getType());
}
@Test
public void testVIN() {
doTest("1M8GDM9AXKP042788", "1M8", "GDM9AX", "KP042788", "US", "GDM9A", 1989, 'P', "042788");
doTest("I1M8GDM9AXKP042788", "1M8", "GDM9AX", "KP042788", "US", "GDM9A", 1989, 'P', "042788");
doTest("LJCPCBLCX11000237", "LJC", "PCBLCX", "11000237", "CN", "PCBLC", 2001, '1', "000237");
}
private static void doTest(String contents,
String wmi,
String vds,
String vis,
String country,
String attributes,
int year,
char plant,
String sequential) {
RXingResult fakeRXingResult = new RXingResult(contents, null, null, BarcodeFormat.CODE_39);
ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult);
assertSame(ParsedRXingResultType.VIN, result.getType());
VINParsedRXingResult vinRXingResult = (VINParsedRXingResult) result;
assertEquals(wmi, vinRXingResult.getWorldManufacturerID());
assertEquals(vds, vinRXingResult.getVehicleDescriptorSection());
assertEquals(vis, vinRXingResult.getVehicleIdentifierSection());
assertEquals(country, vinRXingResult.getCountryCode());
assertEquals(attributes, vinRXingResult.getVehicleAttributes());
assertEquals(year, vinRXingResult.getModelYear());
assertEquals(plant, vinRXingResult.getPlantCode());
assertEquals(sequential, vinRXingResult.getSequentialNumber());
}
#[test]
fn testNotVIN() {
let fakeRXingResult = RXingResult::new(
"1M8GDM9A1KP042788",
Vec::new(),
Vec::new(),
BarcodeFormat::CODE_39,
);
let result = ResultParser::parseRXingResult(&fakeRXingResult);
assert_eq!(ParsedRXingResultType::TEXT, result.getType());
let fakeRXingResult = RXingResult::new(
"1M8GDM9AXKP042788",
Vec::new(),
Vec::new(),
BarcodeFormat::CODE_128,
);
let result = ResultParser::parseRXingResult(&fakeRXingResult);
assert_eq!(ParsedRXingResultType::TEXT, result.getType());
}
#[test]
fn testVIN() {
doTest(
"1M8GDM9AXKP042788",
"1M8",
"GDM9AX",
"KP042788",
"US",
"GDM9A",
1989,
'P',
"042788",
);
doTest(
"I1M8GDM9AXKP042788",
"1M8",
"GDM9AX",
"KP042788",
"US",
"GDM9A",
1989,
'P',
"042788",
);
doTest(
"LJCPCBLCX11000237",
"LJC",
"PCBLCX",
"11000237",
"CN",
"PCBLC",
2001,
'1',
"000237",
);
}
fn doTest(
contents: &str,
wmi: &str,
vds: &str,
vis: &str,
country: &str,
attributes: &str,
year: u32,
plant: char,
sequential: &str,
) {
let fakeRXingResult =
RXingResult::new(contents, Vec::new(), Vec::new(), BarcodeFormat::CODE_39);
let result = ResultParser::parseRXingResult(&fakeRXingResult);
assert_eq!(ParsedRXingResultType::VIN, result.getType());
if let ParsedClientResult::VINResult(vinRXingResult) = result {
// let vinRXingResult = (VINParsedRXingResult) result;
assert_eq!(wmi, vinRXingResult.getWorldManufacturerID());
assert_eq!(vds, vinRXingResult.getVehicleDescriptorSection());
assert_eq!(vis, vinRXingResult.getVehicleIdentifierSection());
assert_eq!(country, vinRXingResult.getCountryCode());
assert_eq!(attributes, vinRXingResult.getVehicleAttributes());
assert_eq!(year, vinRXingResult.getModelYear());
assert_eq!(plant, vinRXingResult.getPlantCode());
assert_eq!(sequential, vinRXingResult.getSequentialNumber());
} else {
panic!("Expected VINResult");
}
} }

View File

@@ -23,7 +23,9 @@
use regex::Regex; use regex::Regex;
use crate::{RXingResult, BarcodeFormat, exceptions::Exceptions}; use crate::{
client::result::VINParsedRXingResult, exceptions::Exceptions, BarcodeFormat, RXingResult,
};
use super::ParsedClientResult; use super::ParsedClientResult;
@@ -33,81 +35,110 @@ use super::ParsedClientResult;
* @author Sean Owen * @author Sean Owen
*/ */
pub fn parse(result: &RXingResult) -> Option<ParsedClientResult> { pub fn parse(result: &RXingResult) -> Option<ParsedClientResult> {
if result.getBarcodeFormat() != &BarcodeFormat::CODE_39 { if result.getBarcodeFormat() != &BarcodeFormat::CODE_39 {
return None; return None;
} }
let ioq_matcher = Regex::new(IOQ).unwrap(); let ioq_matcher = Regex::new(IOQ).unwrap();
let az09_matcher = Regex::new(AZ09).unwrap(); let az09_matcher = Regex::new(AZ09).unwrap();
let rawText = result.getText(); let rawText_res = result.getText().trim();
ioq_matcher.replace_all(result.getText(), "").trim(); let rawText = ioq_matcher.replace_all(rawText_res, "").to_string();
// rawText = IOQ.matcher(rawText).replaceAll("").trim(); // rawText = IOQ.matcher(rawText).replaceAll("").trim();
if let None = az09_matcher.find(rawText){ if let None = az09_matcher.find(&rawText) {
return None; return None;
} }
// if !AZ09.matcher(rawText).matches() { // if !AZ09.matcher(rawText).matches() {
// return null; // return null;
// } // }
let check_cs = checkChecksum(rawText); let check_cs = checkChecksum(&rawText).unwrap_or(false);
if !check_cs { if !check_cs {
return None; return None;
} }
let wmi = &rawText[..3]; let wmi = &rawText[..3];
try { // try {
// if (!checkChecksum(rawText)) { // if (!checkChecksum(rawText)) {
// return null; // return null;
// } // }
// String wmi = rawText.substring(0, 3); // String wmi = rawText.substring(0, 3);
return new VINParsedRXingResult(rawText, let country_code = countryCode(wmi).unwrap_or("");
wmi, let model_year = modelYear(rawText.chars().nth(9).unwrap_or('_'));
rawText.substring(3, 9), if model_year.is_err() {
rawText.substring(9, 17), return None;
countryCode(wmi), }
rawText.substring(3, 8), Some(ParsedClientResult::VINResult(VINParsedRXingResult::new(
modelYear(rawText.charAt(9)), rawText.to_owned(),
rawText.charAt(10), wmi.to_owned(),
rawText.substring(11)); rawText[3..9].to_owned(),
} catch (IllegalArgumentException iae) { rawText[9..17].to_owned(),
return null; country_code.to_owned(),
} rawText[3..8].to_owned(),
model_year.unwrap(),
rawText.chars().nth(10).unwrap(),
rawText[11..].to_owned(),
)))
// return new VINParsedRXingResult(rawText,
// wmi,
// rawText.substring(3, 9),
// rawText.substring(9, 17),
// countryCode(wmi),
// rawText.substring(3, 8),
// modelYear(rawText.charAt(9)),
// rawText.charAt(10),
// rawText.substring(11));
// } catch (IllegalArgumentException iae) {
// return null;
// }
} }
const IOQ : &'static str = "[IOQ]"; const IOQ: &'static str = "[IOQ]";
const AZ09 :&'static str = "[A-Z0-9]{17}"; const AZ09: &'static str = "[A-Z0-9]{17}";
fn checkChecksum( vin: &str) -> Result<bool,Exceptions> { fn checkChecksum(vin: &str) -> Result<bool, Exceptions> {
let mut sum = 0; let mut sum = 0;
for i in 0..vin.len() { for i in 0..vin.len() {
// for (int i = 0; i < vin.length(); i++) { // for (int i = 0; i < vin.length(); i++) {
sum += vinPositionWeight(i + 1)? * vinCharValue(vin.chars().nth(i).unwrap())?; sum += vinPositionWeight(i + 1)? as u32 * vinCharValue(vin.chars().nth(i).unwrap())?;
} }
let checkToChar = vin.chars().nth(8).unwrap(); let checkToChar = vin.chars().nth(8).unwrap();
let expectedCheckChar = checkChar((sum % 11) as u8)?; let expectedCheckChar = checkChar((sum % 11) as u8)?;
Ok( checkToChar == expectedCheckChar) Ok(checkToChar == expectedCheckChar)
} }
fn vinCharValue( c:char)->Result<u32,Exceptions> { fn vinCharValue(c: char) -> Result<u32, Exceptions> {
if c >= 'A' && c <= 'I' { match c {
return Ok((c as u8 as u32 - 'A' as u8 as u32) + 1); 'A'..='I' => Ok((c as u8 as u32 - 'A' as u8 as u32) + 1),
'J'..='R' => Ok((c as u8 as u32 - 'J' as u8 as u32) + 1),
'S'..='Z' => Ok((c as u8 as u32 - 'S' as u8 as u32) + 2),
'0'..='9' => Ok(c as u8 as u32 - '0' as u8 as u32),
_ => Err(Exceptions::IllegalArgumentException(
"vin char out of range".to_owned(),
)),
} }
if c >= 'J' && c <= 'R' { // if c >= 'A' && c <= 'I' {
return Ok((c as u8 as u32 - 'J' as u8 as u32) + 1); // return Ok((c as u8 as u32 - 'A' as u8 as u32) + 1);
} // }
if c >= 'S' && c <= 'Z' { // if c >= 'J' && c <= 'R' {
return Ok((c as u8 as u32- 'S' as u8 as u32) + 2); // return Ok((c as u8 as u32 - 'J' as u8 as u32) + 1);
} // }
if c >= '0' && c <= '9' { // if c >= 'S' && c <= 'Z' {
return Ok(c as u8 as u32 - '0' as u8 as u32); // return Ok((c as u8 as u32 - 'S' as u8 as u32) + 2);
} // }
Err( Exceptions::IllegalArgumentException("vin char out of range".to_owned())) // if c >= '0' && c <= '9' {
} // return Ok(c as u8 as u32 - '0' as u8 as u32);
// }
// Err(Exceptions::IllegalArgumentException(
// "vin char out of range".to_owned(),
// ))
}
fn vinPositionWeight( position:usize) -> Result<usize, Exceptions>{ fn vinPositionWeight(position: usize) -> Result<usize, Exceptions> {
match position { match position {
1..=7 => Ok(9-position), 1..=7 => Ok(9 - position),
8 => Ok(10), 8 => Ok(10),
9 => Ok(0), 9 => Ok(0),
10..=17 => Ok(19-position), 10..=17 => Ok(19 - position),
_ => Err(Exceptions::IllegalArgumentException("vin position weight out of bounds".to_owned())), _ => Err(Exceptions::IllegalArgumentException(
"vin position weight out of bounds".to_owned(),
)),
} }
// if position >= 1 && position <= 7 { // if position >= 1 && position <= 7 {
// return 9 - position; // return 9 - position;
@@ -122,62 +153,83 @@ pub fn parse(result: &RXingResult) -> Option<ParsedClientResult> {
// return 19 - position; // return 19 - position;
// } // }
// throw new IllegalArgumentException(); // throw new IllegalArgumentException();
} }
fn checkChar( remainder:u8) -> Result<char,Exceptions> { fn checkChar(remainder: u8) -> Result<char, Exceptions> {
if remainder < 10 { match remainder {
return Ok( ('0' as u8 + remainder) as char ); 0..=9 => Ok(('0' as u8 + remainder) as char),
10 => Ok('X'),
_ => Err(Exceptions::IllegalArgumentException(
"remainder too high".to_owned(),
)),
} }
if remainder == 10 { // if remainder < 10 {
return Ok('X'); // return Ok(('0' as u8 + remainder) as char);
} // }
Err(Exceptions::IllegalArgumentException("remainder too high".to_owned())) // if remainder == 10 {
} // return Ok('X');
// }
// Err(Exceptions::IllegalArgumentException(
// "remainder too high".to_owned(),
// ))
}
fn modelYear( c:char) -> Result<u32,Exceptions> { fn modelYear(c: char) -> Result<u32, Exceptions> {
if (c >= 'E' && c <= 'H') { match c {
return (c - 'E') + 1984; 'E'..='H' => Ok((c as u8 as u32 - 'E' as u8 as u32) + 1984),
'J'..='N' => Ok((c as u8 as u32 - 'J' as u8 as u32) + 1988),
'P' => Ok(1993),
'R'..='T' => Ok((c as u8 as u32 - 'R' as u8 as u32) + 1994),
'V'..='Y' => Ok((c as u8 as u32 - 'V' as u8 as u32) + 1997),
'1'..='9' => Ok((c as u8 as u32 - '1' as u8 as u32) + 2001),
'A'..='D' => Ok((c as u8 as u32 - 'A' as u8 as u32) + 2010),
_ => Err(Exceptions::IllegalArgumentException(String::from(
"model year argument out of range",
))),
} }
if (c >= 'J' && c <= 'N') { // if c >= 'E' && c <= 'H' {
return (c - 'J') + 1988; // return (c - 'E') + 1984;
} // }
if (c == 'P') { // if c >= 'J' && c <= 'N' {
return 1993; // return (c - 'J') + 1988;
} // }
if (c >= 'R' && c <= 'T') { // if c == 'P' {
return (c - 'R') + 1994; // return 1993;
} // }
if (c >= 'V' && c <= 'Y') { // if c >= 'R' && c <= 'T' {
return (c - 'V') + 1997; // return (c - 'R') + 1994;
} // }
if (c >= '1' && c <= '9') { // if c >= 'V' && c <= 'Y' {
return (c - '1') + 2001; // return (c - 'V') + 1997;
} // }
if (c >= 'A' && c <= 'D') { // if c >= '1' && c <= '9' {
return (c - 'A') + 2010; // return (c - '1') + 2001;
} // }
throw new IllegalArgumentException(); // if c >= 'A' && c <= 'D' {
} // return (c - 'A') + 2010;
// }
// throw new IllegalArgumentException();
}
fn countryCode( wmi : &str) -> Option<&'static str> { fn countryCode(wmi: &str) -> Option<&'static str> {
let c1 = wmi.chars().nth(0).unwrap(); let c1 = wmi.chars().nth(0).unwrap();
let c2 = wmi.chars().nth(1).unwrap(); let c2 = wmi.chars().nth(1).unwrap();
match c1 { match c1 {
'1' | '4'| '5' => Some("US"), '1' | '4' | '5' => Some("US"),
'2' => Some("CA"), '2' => Some("CA"),
'3' if c2 >= 'A' && c2 <= 'W' => Some("MX"), '3' if c2 >= 'A' && c2 <= 'W' => Some("MX"),
'9' if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) => Some("BR"), '9' if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) => Some("BR"),
'J' if (c2 >= 'A' && c2 <= 'T') => Some("JP"), 'J' if (c2 >= 'A' && c2 <= 'T') => Some("JP"),
'K' if (c2 >= 'L' && c2 <= 'R') => Some("KO"), 'K' if (c2 >= 'L' && c2 <= 'R') => Some("KO"),
'L' => Some("CN"), 'L' => Some("CN"),
'M' if (c2 >= 'A' && c2 <= 'E') => Some("IN"), 'M' if (c2 >= 'A' && c2 <= 'E') => Some("IN"),
'S' if (c2 >= 'A' && c2 <= 'M') => Some("UK"), 'S' if (c2 >= 'A' && c2 <= 'M') => Some("UK"),
'S' if (c2 >= 'N' && c2 <= 'T') => Some("DE"), 'S' if (c2 >= 'N' && c2 <= 'T') => Some("DE"),
'V' if (c2 >= 'F' && c2 <= 'R') => Some("FR"), 'V' if (c2 >= 'F' && c2 <= 'R') => Some("FR"),
'V' if (c2 >= 'S' && c2 <= 'W') => Some("ES"), 'V' if (c2 >= 'S' && c2 <= 'W') => Some("ES"),
'W' => Some("DE"), 'W' => Some("DE"),
'X' if (c2 == '0' || (c2 >= '3' && c2 <= '9')) => Some("RU"), 'X' if (c2 == '0' || (c2 >= '3' && c2 <= '9')) => Some("RU"),
'Z' if (c2 >= 'A' && c2 <= 'R') => Some("IT"), 'Z' if (c2 >= 'A' && c2 <= 'R') => Some("IT"),
_ => None _ => None,
}
} }
}