rxing/client/result/
ResultParser.rs

1/*
2 * Copyright 2007 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// package com.google.zxing.client.result;
18
19// import com.google.zxing.RXingResult;
20
21// import java.io.UnsupportedEncodingException;
22// import java.net.URLDecoder;
23// import java.util.ArrayList;
24// import java.util.HashMap;
25// import java.util.List;
26// import java.util.Map;
27// import java.util.regex.Pattern;
28
29use std::collections::HashMap;
30
31use regex::Regex;
32use urlencoding::decode;
33
34use once_cell::sync::Lazy;
35
36use crate::{common::Result, exceptions::Exceptions, RXingResult};
37
38use super::{
39    AddressBookAUResultParser, AddressBookDoCoMoResultParser, BizcardResultParser,
40    BookmarkDoCoMoResultParser, EmailAddressResultParser, EmailDoCoMoResultParser,
41    ExpandedProductResultParser, GeoResultParser, ISBNResultParser, ParsedClientResult,
42    ProductResultParser, SMSMMSResultParser, SMSTOMMSTOResultParser, SMTPResultParser,
43    TelResultParser, TextParsedRXingResult, URIResultParser, URLTOResultParser, VCardResultParser,
44    VEventResultParser, VINResultParser, WifiResultParser,
45};
46
47/*
48 * <p>Abstract class representing the result of decoding a barcode, as more than
49 * a String -- as some type of structured data. This might be a subclass which represents
50 * a URL, or an e-mail address. {@link #parseRXingResult(RXingResult)} will turn a raw
51 * decoded string into the most appropriate type of structured representation.</p>
52 *
53 * <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
54 * on exception-based mechanisms during parsing.</p>
55 *
56 * @author Sean Owen
57 */
58// pub trait RXingResultParser {
59//     // const PARSERS: [&'static str; 20] = [
60//     //     "BookmarkDoCoMoRXingResultParser",
61//     //     "AddressBookDoCoMoRXingResultParser",
62//     //     "EmailDoCoMoRXingResultParser",
63//     //     "AddressBookAURXingResultParser",
64//     //     "VCardRXingResultParser",
65//     //     "BizcardRXingResultParser",
66//     //     "VEventRXingResultParser",
67//     //     "EmailAddressRXingResultParser",
68//     //     "SMTPRXingResultParser",
69//     //     "TelRXingResultParser",
70//     //     "SMSMMSRXingResultParser",
71//     //     "SMSTOMMSTORXingResultParser",
72//     //     "GeoRXingResultParser",
73//     //     "WifiRXingResultParser",
74//     //     "URLTORXingResultParser",
75//     //     "URIRXingResultParser",
76//     //     "ISBNRXingResultParser",
77//     //     "ProductRXingResultParser",
78//     //     "ExpandedProductRXingResultParser",
79//     //     "VINRXingResultParser",
80//     // ];
81
82//     /**
83//      * Attempts to parse the raw {@link RXingResult}'s contents as a particular type
84//      * of information (email, URL, etc.) and return a {@link ParsedRXingResult} encapsulating
85//      * the result of parsing.
86//      *
87//      * @param theRXingResult the raw {@link RXingResult} to parse
88//      * @return {@link ParsedRXingResult} encapsulating the parsing result
89//      */
90//     fn parse(&self, theRXingResult: &RXingResult) -> Option<ParsedClientResult>;
91// }
92
93pub type ParserFunction = dyn Fn(&RXingResult) -> Option<ParsedClientResult>;
94
95static DIGITS: Lazy<Regex> = Lazy::new(|| Regex::new("\\d+").unwrap());
96
97// const DIGITS: &'static str = "\\d+"; //= Pattern.compile("\\d+");
98const AMPERSAND: &str = "&"; // private static final Pattern AMPERSAND = Pattern.compile("&");
99const EQUALS: &str = "="; //private static final Pattern EQUALS = Pattern.compile("=");
100const BYTE_ORDER_MARK: &str = "\u{feff}"; //private static final String BYTE_ORDER_MARK = "\ufeff";
101
102// const EMPTY_STR_ARRAY: &'static str = "";
103
104pub fn getMassagedText(result: &RXingResult) -> String {
105    result
106        .getText()
107        .trim_start_matches(BYTE_ORDER_MARK)
108        .to_owned()
109    // if text.starts_with(BYTE_ORDER_MARK) {
110    //     text = &text[1..];
111    // }
112    // text.to_owned()
113}
114
115pub fn parse_result_with_parsers(
116    the_rxing_result: &RXingResult,
117    parsers: &[&ParserFunction],
118) -> ParsedClientResult {
119    for parser in parsers {
120        let result = parser(the_rxing_result);
121        if let Some(res) = result {
122            return res;
123        }
124    }
125    parseRXingResult(the_rxing_result)
126}
127
128pub fn parse_result_with_parser<F: Fn(&RXingResult) -> Option<ParsedClientResult>>(
129    the_rxing_result: &RXingResult,
130    parser: F,
131) -> Option<ParsedClientResult> {
132    parser(the_rxing_result)
133}
134
135pub fn parseRXingResult(the_rxing_result: &RXingResult) -> ParsedClientResult {
136    let PARSERS: [&ParserFunction; 20] = [
137        &BookmarkDoCoMoResultParser::parse,
138        &AddressBookDoCoMoResultParser::parse,
139        &EmailDoCoMoResultParser::parse,
140        &AddressBookAUResultParser::parse,
141        &VCardResultParser::parse,
142        &BizcardResultParser::parse,
143        &VEventResultParser::parse,
144        &EmailAddressResultParser::parse,
145        &SMTPResultParser::parse,
146        &TelResultParser::parse,
147        &SMSMMSResultParser::parse,
148        &SMSTOMMSTOResultParser::parse,
149        &GeoResultParser::parse,
150        &WifiResultParser::parse,
151        &URLTOResultParser::parse,
152        &URIResultParser::parse,
153        &ISBNResultParser::parse,
154        &ProductResultParser::parse,
155        &ExpandedProductResultParser::parse,
156        &VINResultParser::parse,
157    ];
158
159    for parser in PARSERS {
160        let result = parser(the_rxing_result);
161        if let Some(res) = result {
162            return res;
163        }
164    }
165    //   ParsedRXingResult result = parser.parse(theRXingResult);
166    //   if (result != null) {
167    //     return result;
168    //   }
169    // }
170
171    ParsedClientResult::TextResult(TextParsedRXingResult::new(
172        the_rxing_result.getText().to_owned(),
173        String::default(),
174    ))
175}
176
177pub fn maybe_append_string(value: &str, result: &mut String) {
178    if !value.is_empty() {
179        if !result.is_empty() {
180            result.push('\n');
181        }
182        result.push_str(value);
183    }
184}
185
186pub fn maybe_append_multiple(value: &[String], result: &mut String) {
187    for s in value {
188        // for (String s : value) {
189        if !s.is_empty() {
190            if !result.is_empty() {
191                result.push('\n');
192            }
193            result.push_str(s);
194        }
195    }
196}
197
198#[inline(always)]
199pub fn maybeWrap(value: Option<String>) -> Option<Vec<String>> {
200    if value.is_none() {
201        None
202    } else {
203        Some(vec![value.unwrap()])
204    }
205}
206
207pub fn unescapeBackslash(escaped: &str) -> String {
208    let backslash = escaped.find('\\');
209    if backslash.is_none() {
210        return escaped.to_owned();
211    }
212    let max = escaped.chars().count();
213    let backslash = backslash.unwrap_or(0);
214    let mut unescaped = escaped.chars().take(backslash).collect::<String>();
215    unescaped.reserve(max - 1);
216    let mut nextIsEscaped = false;
217    for c in escaped.chars().skip(backslash) {
218        if nextIsEscaped || c != '\\' {
219            unescaped.push(c);
220            nextIsEscaped = false;
221        } else {
222            nextIsEscaped = true;
223        }
224    }
225
226    unescaped
227}
228
229#[inline(always)]
230pub fn parseHexDigit(c: char) -> Option<u32> {
231    c.to_digit(16)
232    // let Some(v) = c.to_digit(16) else {
233    //     return -1
234    // };
235    // v as i32
236    // if c.is_ascii_digit() {
237    //     return (c as u8 - b'0') as i32;
238    // }
239    // if ('a'..='f').contains(&c) {
240    //     return 10 + (c as u8 - b'a') as i32;
241    // }
242    // if ('A'..='F').contains(&c) {
243    //     return 10 + (c as u8 - b'A') as i32;
244    // }
245    // -1
246}
247
248#[inline(always)]
249pub fn isStringOfDigits(value: &str, length: usize) -> bool {
250    !value.is_empty() && length > 0 && length == value.len() && DIGITS.is_match(value)
251}
252
253pub fn isSubstringOfDigits(value: &str, offset: usize, length: usize) -> bool {
254    if value.is_empty() || length == 0 {
255        return false;
256    }
257    let max = offset + length;
258
259    let sub_seq: String = value.chars().skip(offset).take(length).collect(); //&value[offset..max];
260
261    let is_a_match = if let Some(mtch) = DIGITS.find(&sub_seq) {
262        mtch.start() == 0 && mtch.end() == sub_seq.chars().count()
263    } else {
264        false
265    };
266
267    value.len() >= max && is_a_match
268}
269
270pub fn parseNameValuePairs(uri: &str) -> Option<HashMap<String, String>> {
271    let paramStart = uri.find('?');
272    paramStart?;
273    let mut result = HashMap::with_capacity(3);
274    let paramStart = paramStart.unwrap_or(0);
275
276    let sub_str = &uri[paramStart + 1..]; // This is likely ok because we're looking for a specific single byte charaacter
277    let list = sub_str.split(AMPERSAND);
278    for keyValue in list {
279        appendKeyValue(keyValue, &mut result);
280    }
281    Some(result)
282}
283
284pub fn appendKeyValue(keyValue: &str, result: &mut HashMap<String, String>) {
285    let keyValueTokens = keyValue.split(EQUALS); //Self::EQUALS.split(keyValue, 2);
286
287    let kvp: Vec<&str> = keyValueTokens.take(2).collect();
288    if let [key, value] = kvp[..] {
289        let p_value = urlDecode(value).unwrap_or_else(|_| String::default());
290        result.insert(key.to_owned(), p_value);
291    }
292
293    // if keyValueTokens.len() == 2 {
294    //   let key = keyValueTokens[0];
295    //   let value = keyValueTokens[1];
296    //   try {
297    //     value = Self::urlDecode(value);
298    //     result.put(key, value);
299    //   } catch (IllegalArgumentException iae) {
300    //     // continue; invalid data such as an escape like %0t
301    //   }
302    // }
303}
304
305pub fn urlDecode(encoded: &str) -> Result<String> {
306    if let Ok(decoded) = decode(encoded) {
307        Ok(decoded.to_string())
308    } else {
309        Err(Exceptions::illegal_state_with(
310            "UnsupportedEncodingException",
311        ))
312    }
313}
314
315pub fn matchPrefixedField(
316    prefix: &str,
317    rawText: &str,
318    endChar: char,
319    trim: bool,
320) -> Option<Vec<String>> {
321    let mut matches = Vec::new();
322    let mut i = 0;
323    let max = rawText.len();
324    while i < max {
325        i = if let Some(loc) = rawText[i..].find(prefix) {
326            loc + i
327        } else {
328            break;
329        };
330        //   i = rawText.indexOf(prefix, i);
331        //   if (i < 0) {
332        //     break;
333        //   }
334        i += prefix.chars().count(); // Skip past this prefix we found to start
335        let start = i; // Found the start of a match here
336        let mut more = true;
337        while more {
338            if let Some(next_index) = rawText[i..].find(endChar) {
339                i += next_index;
340            } else {
341                // No terminating end character? uh, done. Set i such that loop terminates and break
342                i = rawText.chars().count();
343                more = false;
344                continue;
345            }
346
347            if countPrecedingBackslashes(rawText, i) % 2 != 0 {
348                // semicolon was escaped (odd count of preceding backslashes) so continue
349                i += 1;
350            } else {
351                // found a match
352                let mut element = unescapeBackslash(&rawText[start..i]);
353                if trim {
354                    element = element.trim().to_owned();
355                }
356                if !element.is_empty() {
357                    matches.push(element);
358                }
359                i += 1;
360                more = false;
361            }
362
363            // i = rawText.indexOf(endChar, i);
364            // if i < 0 {
365            //   // No terminating end character? uh, done. Set i such that loop terminates and break
366            //   i = rawText.len();
367            //   more = false;
368            // } else if countPrecedingBackslashes(rawText, i) % 2 != 0 {
369            //   // semicolon was escaped (odd count of preceding backslashes) so continue
370            //   i+=1;
371            // } else {
372            //   // found a match
373            //   let element = unescapeBackslash(&rawText[start..start+i]);
374            //   if (trim) {
375            //     element = element.trim();
376            //   }
377            //   if (!element.isEmpty()) {
378            //     matches.add(element);
379            //   }
380            //   i+=1;
381            //   more = false;
382            // }
383        }
384    }
385    if matches.is_empty() {
386        return None;
387    }
388
389    Some(matches)
390}
391
392pub fn countPrecedingBackslashes(s: &str, pos: usize) -> u32 {
393    let mut count = 0;
394    let cached_s = s.chars().collect::<Vec<_>>();
395    for i in (0..pos).rev() {
396        // for (int i = pos - 1; i >= 0; i--) {
397        if cached_s[i] == '\\' {
398            count += 1;
399        } else {
400            break;
401        }
402    }
403    count
404}
405
406pub fn matchSinglePrefixedField(
407    prefix: &str,
408    rawText: &str,
409    endChar: char,
410    trim: bool,
411) -> Option<String> {
412    let matches = matchPrefixedField(prefix, rawText, endChar, trim);
413    matches.map(|m| m[0].clone())
414    // return matches == null ? null : matches[0];
415}
416
417pub fn match_docomo_prefixed_field(prefix: &str, raw_text: &str) -> Option<Vec<String>> {
418    matchPrefixedField(prefix, raw_text, ';', true)
419}
420
421pub fn match_single_docomo_prefixed_field(
422    prefix: &str,
423    raw_text: &str,
424    trim: bool,
425) -> Option<String> {
426    matchSinglePrefixedField(prefix, raw_text, ';', trim)
427}
428
429#[cfg(test)]
430mod tests {
431    use crate::{
432        client::result::{
433            OtherParsedResult, ParsedClientResult, ParsedRXingResult, TextParsedRXingResult,
434        },
435        RXingResult,
436    };
437
438    use super::parse_result_with_parser;
439
440    #[test]
441    fn test_single_parser() {
442        let result: RXingResult = RXingResult::new(
443            "text",
444            vec![12, 23, 54, 23],
445            Vec::new(),
446            crate::BarcodeFormat::EAN_13,
447        );
448        let p_res = parse_result_with_parser(&result, |_| {
449            Some(ParsedClientResult::TextResult(TextParsedRXingResult::new(
450                String::from("parsed with parser"),
451                String::from("en/us"),
452            )))
453        })
454        .unwrap();
455        assert_eq!(p_res.to_string(), "parsed with parser");
456    }
457
458    #[test]
459    fn test_other_parser() {
460        let result: RXingResult = RXingResult::new(
461            "text",
462            vec![12, 23, 54, 23],
463            Vec::new(),
464            crate::BarcodeFormat::EAN_13,
465        );
466        let p_res = parse_result_with_parser(&result, |v| {
467            Some(ParsedClientResult::Other(OtherParsedResult::new(Box::new(
468                v.getRawBytes().to_vec(),
469            ))))
470        })
471        .unwrap();
472
473        assert_eq!(p_res.getDisplayRXingResult(), "Any { .. }");
474
475        if let ParsedClientResult::Other(opr) = p_res {
476            if let Some(d) = opr.get_data().downcast_ref::<Vec<u8>>() {
477                assert_eq!(d, result.getRawBytes());
478            } else {
479                panic!("did not get vec<u8>");
480            }
481        } else {
482            panic!("did not get ParsedClientResult::Other");
483        }
484    }
485}