phonenumber_fixed/
formatter.rs

1// Copyright (C) 2017 1aim GmbH
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{borrow::Cow, fmt};
16use crate::{
17  metadata::{DATABASE, Database, Metadata, Format},
18  phone_number::PhoneNumber,
19  consts
20};
21
22/// Formatting modes for phone number.
23#[derive(Copy, Clone, Eq, PartialEq, Debug)]
24pub enum Mode {
25	/// E.164 formatting, no spaces, no decorations.
26	E164,
27
28	/// International formatting, contains country code and country dependent
29	/// formatting.
30	International,
31
32	/// National formatting, no country code and country dependent formatting.
33	National,
34
35	/// RFC3966 formatting, see the RFC.
36	Rfc3966,
37}
38
39/// A formatter for a `PhoneNumber`.
40#[derive(Copy, Clone, Debug)]
41pub struct Formatter<'n, 'd, 'f> {
42	number:   &'n PhoneNumber,
43	database: Option<&'d Database>,
44	mode:     Mode,
45	format:   Option<&'f Format>,
46}
47
48impl<'n, 'd, 'f> Formatter<'n, 'd, 'f> {
49	/// Define a metadata database to use for formatting.
50	pub fn database<'a>(self, database: &'a Database) -> Formatter<'n, 'a, 'f> {
51		Formatter {
52			number:   self.number,
53			database: Some(database),
54			mode:     self.mode,
55			format:   self.format,
56		}
57	}
58
59	/// Define the formatting mode.
60	pub fn mode(mut self, mode: Mode) -> Formatter<'n, 'd, 'f> {
61		self.mode = mode;
62		self
63	}
64
65	/// Define a custom `Format` to use for formatting.
66	pub fn with<'a>(self, format: &'a Format) -> Formatter<'n, 'd, 'a> {
67		Formatter {
68			number:   self.number,
69			database: self.database,
70			mode:     self.mode,
71			format:   Some(format),
72		}
73	}
74}
75
76/// Create a new `Formatter` for the given phone number.
77pub fn format<'n>(number: &'n PhoneNumber) -> Formatter<'n, 'static, 'static> {
78	Formatter {
79		number:   number,
80		database: None,
81		mode:     Mode::E164,
82		format:   None,
83	}
84}
85
86/// Create a new `Formatter` for the given phone number using the given
87/// metadata database.
88pub fn format_with<'d, 'n>(database: &'d Database, number: &'n PhoneNumber) -> Formatter<'n, 'd, 'static> {
89	Formatter {
90		number:   number,
91		database: Some(database),
92		mode:     Mode::E164,
93		format:   None,
94	}
95}
96
97impl<'n, 'd, 'f> fmt::Display for Formatter<'n, 'd, 'f> {
98	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99		let db = self.database.unwrap_or(&*DATABASE);
100
101		// If the country code is invalid, return an error.
102		let meta = try_opt!(Err(fmt::Error);
103			db.by_code(&self.number.country().code()).map(|m|
104				m.into_iter().next().unwrap()));
105
106		let national  = self.number.national().to_string();
107		let formatter = self.format.or_else(|| formatter(&national,
108			if meta.international_formats().is_empty() || self.mode == Mode::National {
109				meta.formats()
110			}
111			else {
112				meta.international_formats()
113			}));
114
115		match self.mode {
116			// Requires no formatting at all, easy life.
117			Mode::E164 => {
118				write!(f, "+{}{}", self.number.country().code(), national)?;
119			}
120
121			// Space separated formatting with national specific rules.
122			Mode::International => {
123				write!(f, "+{} ", self.number.country().code())?;
124
125				if let Some(formatter) = formatter {
126					write!(f, "{}", replace(&national, meta, formatter, None, None))?;
127				}
128				else {
129					write!(f, "{}", national)?;
130				}
131
132				if let Some(ext) = self.number.extension() {
133					write!(f, "{}{}", meta.preferred_extension_prefix()
134						.unwrap_or(" ext. "), ext)?;
135				}
136			}
137
138			Mode::National => {
139				if let Some(formatter) = formatter {
140					let carrier = self.number.carrier().and_then(|c|
141						formatter.domestic_carrier().map(|f| (c, f)));
142
143					if let Some((carrier, format)) = carrier {
144						write!(f, "{}", replace(&national, meta, formatter, Some(format), Some(carrier)))?;
145					}
146					else if let Some(prefix) = formatter.national_prefix() {
147						write!(f, "{}", replace(&national, meta, formatter, Some(prefix), None))?;
148					}
149					else {
150						write!(f, "{}", replace(&national, meta, formatter, None, None))?;
151					}
152				}
153				else {
154					write!(f, "{}", national)?;
155				}
156
157				if let Some(ext) = self.number.extension() {
158					write!(f, "{}{}", meta.preferred_extension_prefix().unwrap_or(" ext. "), ext)?;
159				}
160			}
161
162			Mode::Rfc3966 => {
163				write!(f, "tel:+{}-", self.number.country().code())?;
164
165				if let Some(formatter) = formatter {
166					write!(f, "{}", consts::SEPARATOR_PATTERN.replace_all(
167						&replace(&national, meta, formatter, None, None), "-"))?;
168				}
169				else {
170					write!(f, "{}", national)?;
171				}
172
173				if let Some(ext) = self.number.extension() {
174					write!(f, ";ext={}", ext)?;
175				}
176			}
177		}
178
179		Ok(())
180	}
181}
182
183fn formatter<'a>(number: &str, formats: &'a [Format]) -> Option<&'a Format> {
184	for format in formats {
185		let leading = format.leading_digits();
186
187		if leading.is_empty() || leading.last().unwrap().find(&number).map(|m| m.start() == 0).unwrap_or(false) {
188			if format.pattern().find(&number).map(|m| m.start() == 0 && m.end() == number.len()).unwrap_or(false) {
189				return Some(format);
190			}
191		}
192	}
193
194	None
195}
196
197fn replace(national: &str, meta: &Metadata, formatter: &Format, transform: Option<&str>, carrier: Option<&str>) -> String {
198	formatter.pattern().replace(national, &*if let Some(transform) = transform {
199		let first  = consts::FIRST_GROUP.captures(&formatter.format()).unwrap().get(1).unwrap().as_str();
200		let format = transform.replace(*consts::NP, meta.national_prefix().unwrap_or(""));
201		let format = format.replace(*consts::FG, &*format!("${}", first));
202		let format = format.replace(*consts::CC, carrier.unwrap_or(""));
203
204		consts::FIRST_GROUP.replace(formatter.format(), &*format)
205	}
206	else {
207		Cow::Borrowed(formatter.format())
208	}).into()
209}
210
211#[cfg(test)]
212mod test {
213	use crate::parser;
214	use crate::formatter::Mode;
215	use crate::country;
216
217	#[test]
218	fn us() {
219		assert_eq!("(650) 253-0000",
220			parser::parse(Some(country::US), "+1 6502530000").unwrap()
221				.format().mode(Mode::National).to_string());
222
223		assert_eq!("+1 650-253-0000",
224			parser::parse(Some(country::US), "+1 6502530000").unwrap()
225				.format().mode(Mode::International).to_string());
226
227		assert_eq!("(800) 253-0000",
228			parser::parse(Some(country::US), "+1 8002530000").unwrap()
229				.format().mode(Mode::National).to_string());
230
231		assert_eq!("+1 800-253-0000",
232			parser::parse(Some(country::US), "+1 8002530000").unwrap()
233				.format().mode(Mode::International).to_string());
234
235		assert_eq!("(900) 253-0000",
236			parser::parse(Some(country::US), "+1 9002530000").unwrap()
237				.format().mode(Mode::National).to_string());
238
239		assert_eq!("+1 900-253-0000",
240			parser::parse(Some(country::US), "+1 9002530000").unwrap()
241				.format().mode(Mode::International).to_string());
242
243		assert_eq!("tel:+1-900-253-0000",
244			parser::parse(Some(country::US), "+1 9002530000").unwrap()
245				.format().mode(Mode::Rfc3966).to_string());
246  }
247
248	#[test]
249	fn gb() {
250		assert_eq!("020 7031 3000",
251			parser::parse(Some(country::GB), "+44 2070313000").unwrap()
252				.format().mode(Mode::National).to_string());
253
254		assert_eq!("+44 20 7031 3000",
255			parser::parse(Some(country::GB), "+44 2070313000").unwrap()
256				.format().mode(Mode::International).to_string());
257
258		assert_eq!("020 7031 3000",
259			parser::parse(Some(country::GB), "+44 2070313000").unwrap()
260				.format().mode(Mode::National).to_string());
261
262		assert_eq!("07912 345678",
263			parser::parse(Some(country::GB), "+44 7912345678").unwrap()
264				.format().mode(Mode::National).to_string());
265
266		assert_eq!("+44 7912 345678",
267			parser::parse(Some(country::GB), "+44 7912345678").unwrap()
268				.format().mode(Mode::International).to_string());
269	}
270}