diff --git a/Cargo.toml b/Cargo.toml index a4cb050..97b9129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "latlon" -version = "0.1.0" +version = "0.1.1" authors = ["Ondřej Hruška "] description = "Parse latitude/longitude from many common formats" edition = "2018" diff --git a/README.md b/README.md index 1da26dd..26e16c6 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ let lng : f64 = latlon::parse_lng("E 14°26.94732'").unwrap(); Example of supported formats: -- `40° 26′ 46″ N 79° 58′ 56″ W` -- `N 40° 26′ 46″ W 79° 58′ 56″` +- `40° 26' 46" N 79° 58' 56" W` +- `N 40° 26' 46" W 79° 58' 56"` - `40° 26.767' N 79° 58.933' W` -- `40° 26′ 46″ 79° 58′ 56″`, `40° 26′ 46″, 79° 58′ 56″`, ... +- `40° 26' 46" 79° 58' 56"`, `40° 26' 46", 79° 58' 56"`, ... - `N 40° 26.767' W 79° 58.933'` - `40° 26.767' 79° 58.933'`, `40° 26.767', 79° 58.933'`, ... - `N 40.446° W 79.982°` @@ -37,7 +37,7 @@ Example of supported formats: - Whitespace is optional and ignored, except for formats that would become unparsable. - Degree, minute and second symbols can be omitted. - Comma (`,`) may be used as an alternate decimal separator. -- Unicode quotes (`’`, `”`) may be used in place of apostrophe and double quote (`'`, `"`) +- Unicode quotes (e.g. `’`, `”`) are supported for minutes and seconds. -- The two coordinates can be separated by comma (`,`), semicolon (`;`), whitespace (` `), or nothing +- The two coordinates can be separated by comma (`,`), semicolon (`;`), whitespace, or nothing at all, if not ambiguous. diff --git a/src/lib.rs b/src/lib.rs index 4cfd0f8..a43cdce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,98 +18,98 @@ use crate::errors::ParseErrorInternal; // Two-sided patterns lazy_static! { // 40° 26′ 46″ N 79° 58′ 56″ W - static ref RE_DMS_NS_DMS_EW: Regex = Regex::new(r"(?x) + static ref RE_DMS_NS_DMS_EW: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]?\s* + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]?\s* (N|S) \s* [,;]? \s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]?\s* + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]?\s* (E|W) $ - ").unwrap(); + "#).unwrap(); // N 40° 26′ 46″ W 79° 58′ 56″ - static ref RE_NS_DMS_EW_DMS: Regex = Regex::new(r"(?x) + static ref RE_NS_DMS_EW_DMS: Regex = Regex::new(r#"(?x) ^ (N|S)\s* (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]? + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]? \s* [,;]? \s* (E|W)\s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]? + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]? $ - ").unwrap(); + "#).unwrap(); // 40° 26′ 46″ 79° 58′ 56″ - static ref RE_DMS_DMS: Regex = Regex::new(r"(?x) + static ref RE_DMS_DMS: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]?\s* + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]?\s* [,;]? \s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]? + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]? $ - ").unwrap(); + "#).unwrap(); // 40° 26.767' N 79° 58.933' W - static ref RE_DM_NS_DM_EW: Regex = Regex::new(r"(?x) + static ref RE_DM_NS_DM_EW: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]?\s* + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]?\s* (N|S) \s* [,;]? \s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]?\s* + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]?\s* (E|W) $ - ").unwrap(); + "#).unwrap(); // N 40° 26.767' W 79° 58.933' - static ref RE_NS_DM_EW_DM: Regex = Regex::new(r"(?x) + static ref RE_NS_DM_EW_DM: Regex = Regex::new(r#"(?x) ^ (N|S)\s* (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? \s* [,;]? \s* (E|W)\s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? $ - ").unwrap(); + "#).unwrap(); // 40° 26.767' 79° 58.933' - static ref RE_DM_DM: Regex = Regex::new(r"(?x) + static ref RE_DM_DM: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? \s* [,;]? \s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? $ - ").unwrap(); + "#).unwrap(); // N 40.446° W 79.982° - static ref RE_NS_D_EW_D: Regex = Regex::new(r"(?x) + static ref RE_NS_D_EW_D: Regex = Regex::new(r#"(?x) ^ (N|S)\s* (-?\d{1,2}(?:[.,]\d+)?)°? @@ -119,10 +119,10 @@ lazy_static! { (E|W)\s* (-?\d{1,3}(?:[.,]\d+)?)°? $ - ").unwrap(); + "#).unwrap(); // 40.446° N 79.982° W - static ref RE_D_NS_D_EW: Regex = Regex::new(r"(?x) + static ref RE_D_NS_D_EW: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2}(?:[.,]\d+)?)°?\s* (N|S)\s* @@ -131,96 +131,96 @@ lazy_static! { (-?\d{1,3}(?:[.,]\d+)?)°?\s* (E|W) $ - ").unwrap(); + "#).unwrap(); // 40.446° 79.982° - static ref RE_D_D: Regex = Regex::new(r"(?x) + static ref RE_D_D: Regex = Regex::new(r#"(?x) ^ (-?\d{1,2}(?:[.,]\d+)?)(?:°\s*[,;]?\s*|\s*[,;]\s*|\s+) (-?\d{1,3}(?:[.,]\d+)?)°? $ - ").unwrap(); + "#).unwrap(); } // One-sided patterns lazy_static! { // 40° 26′ 46″ N - static ref RE_DMS_NSEW: Regex = Regex::new(r"(?x) + static ref RE_DMS_NSEW: Regex = Regex::new(r#"(?x) ^ (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]?\s* + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]?\s* (N|S|E|W) $ - ").unwrap(); + "#).unwrap(); // N 40° 26′ 46″ - static ref RE_NSEW_DMS: Regex = Regex::new(r"(?x) + static ref RE_NSEW_DMS: Regex = Regex::new(r#"(?x) ^ (N|S|E|W)\s* (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]? + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]? $ - ").unwrap(); + "#).unwrap(); // 40° 26′ 46″ - static ref RE_DMS: Regex = Regex::new(r"(?x) + static ref RE_DMS: Regex = Regex::new(r#"(?x) ^ (-?\d{1,3})(?:°\s*|\s+) - (\d{1,2})(?:[’′]\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[″”]?\s* + (\d{1,2})(?:[’'′‘‛]\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[″”"“]?\s* $ - ").unwrap(); + "#).unwrap(); // 40° 26.767' N - static ref RE_DM_NSEW: Regex = Regex::new(r"(?x) + static ref RE_DM_NSEW: Regex = Regex::new(r#"(?x) ^ - (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]?\s* + (-?\d{1,3})(?:°\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]?\s* (N|S|E|W) $ - ").unwrap(); + "#).unwrap(); // N 40° 26.767' - static ref RE_NSEW_DM: Regex = Regex::new(r"(?x) + static ref RE_NSEW_DM: Regex = Regex::new(r#"(?x) ^ (N|S|E|W)\s* - (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (-?\d{1,3})(?:°\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? $ - ").unwrap(); + "#).unwrap(); // 40° 26.767' - static ref RE_DM: Regex = Regex::new(r"(?x) + static ref RE_DM: Regex = Regex::new(r#"(?x) ^ - (-?\d{1,2})(?:°\s*|\s+) - (\d{1,2}(?:[.,]\d+)?)[’′]? + (-?\d{1,3})(?:°\s*|\s+) + (\d{1,2}(?:[.,]\d+)?)[’'′‘‛]? $ - ").unwrap(); + "#).unwrap(); // N 40.446° - static ref RE_NSEW_D: Regex = Regex::new(r"(?x) + static ref RE_NSEW_D: Regex = Regex::new(r#"(?x) ^ (N|S|E|W)\s* - (-?\d{1,2}(?:[.,]\d+)?)°? + (-?\d{1,3}(?:[.,]\d+)?)°? $ - ").unwrap(); + "#).unwrap(); // 40.446° N - static ref RE_D_NSEW: Regex = Regex::new(r"(?x) + static ref RE_D_NSEW: Regex = Regex::new(r#"(?x) ^ - (-?\d{1,2}(?:[.,]\d+)?)°?\s* + (-?\d{1,3}(?:[.,]\d+)?)°?\s* (N|S|E|W) $ - ").unwrap(); + "#).unwrap(); // 40.446° - static ref RE_D: Regex = Regex::new(r"(?x) + static ref RE_D: Regex = Regex::new(r#"(?x) ^ - (-?\d{1,2}(?:[.,]\d+)?)°? + (-?\d{1,3}(?:[.,]\d+)?)°? $ - ").unwrap(); + "#).unwrap(); } /// Parsed degrees, minutes, seconds diff --git a/src/tests.rs b/src/tests.rs index 247a4a6..d57215c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -206,6 +206,16 @@ fn dms() { assert_eq!(reference, parse_lng(r#"40°26′46″"#).unwrap(), "compact"); assert_eq!(reference, parse_lng(r#"40 26 46"#).unwrap(), "no symbols"); assert_eq!(ref_neg, parse_lng(r#"-40° 26′ 46″"#).unwrap(), "neg"); + + // Test all the weird quotation marks + + // ’'′‘‛ + // ″”"“ + assert_eq!(reference, parse_lng(r#"40° 26’ 46″"#).unwrap(), "normal"); + assert_eq!(reference, parse_lng(r#"40° 26' 46”"#).unwrap(), "normal"); + assert_eq!(reference, parse_lng(r#"40° 26′ 46""#).unwrap(), "normal"); + assert_eq!(reference, parse_lng(r#"40° 26‘ 46“"#).unwrap(), "normal"); + assert_eq!(reference, parse_lng(r#"40° 26‛ 46″"#).unwrap(), "normal"); } #[test]