Skip to main content

rust_xlsxwriter/
utility.rs

1// Some utility functions for the `rust_xlsxwriter` module.
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4//
5// Copyright 2022-2026, John McNamara, jmcnamara@cpan.org
6
7//! Utility functions for `rust_xlsxwriter`.
8//!
9//! The `rust_xlsxwriter` library provides a number of utility functions for
10//! dealing with cell ranges, Chrono and Jiff Serde serialization, and other
11//! helper method.
12//!
13//!
14//! # Examples:
15//!
16//! ```
17//! use rust_xlsxwriter::{cell_range, column_number_to_name};
18//!
19//! assert_eq!(column_number_to_name(1), "B");
20//! assert_eq!(column_number_to_name(702), "AAA");
21//!
22//! assert_eq!(cell_range(0, 0, 9, 0), "A1:A10");
23//! assert_eq!(cell_range(1, 2, 8, 2), "C2:C9");
24//! assert_eq!(cell_range(0, 0, 3, 4), "A1:E4");
25//! ```
26
27#![warn(missing_docs)]
28mod tests;
29
30use crate::COL_MAX;
31use crate::MAX_AUTOFIT_WIDTH_PIXELS;
32use crate::ROW_MAX;
33
34#[cfg(feature = "serde")]
35use crate::IntoExcelDateTime;
36
37#[cfg(feature = "serde")]
38use serde::Serializer;
39
40use crate::worksheet::ColNum;
41use crate::worksheet::RowNum;
42use crate::XlsxError;
43
44/// Convert a zero indexed column cell reference to a string like `"A"`.
45///
46/// This is a utility function to convert a zero based column reference to a
47/// string representation. This can be useful when constructing ranges for
48/// formulas.
49///
50/// # Parameters
51///
52/// - `col_num`: The zero indexed column number.
53///
54///
55/// # Examples:
56///
57/// ```
58/// use rust_xlsxwriter::column_number_to_name;
59///
60/// assert_eq!(column_number_to_name(0), "A");
61/// assert_eq!(column_number_to_name(1), "B");
62/// assert_eq!(column_number_to_name(702), "AAA");
63/// ```
64///
65pub fn column_number_to_name(col_num: ColNum) -> String {
66    let mut col_name = String::new();
67
68    let mut col_num = col_num + 1;
69
70    while col_num > 0 {
71        // Set remainder from 1 .. 26
72        let mut remainder = col_num % 26;
73
74        if remainder == 0 {
75            remainder = 26;
76        }
77
78        // Convert the remainder to a character.
79        let col_letter = char::from_u32(64u32 + u32::from(remainder)).unwrap();
80
81        // Accumulate the column letters, right to left.
82        col_name = format!("{col_letter}{col_name}");
83
84        // Get the next order of magnitude.
85        col_num = (col_num - 1) / 26;
86    }
87
88    col_name
89}
90
91/// Convert a column string such as `"A"` to a zero indexed column reference.
92///
93/// This is a utility function to convert a column string representation to a
94/// zero based column reference.
95///
96/// # Parameters
97///
98/// - `column`: A string representing a column reference.
99///
100/// # Examples:
101///
102/// ```
103/// use rust_xlsxwriter::column_name_to_number;
104///
105/// assert_eq!(column_name_to_number("A"), 0);
106/// assert_eq!(column_name_to_number("B"), 1);
107/// assert_eq!(column_name_to_number("AAA"), 702);
108/// ```
109///
110pub fn column_name_to_number(column: &str) -> ColNum {
111    if column.is_empty() {
112        return 0;
113    }
114
115    let mut col_num = 0;
116    for char in column.chars() {
117        col_num = (col_num * 26) + (char as u16 - 'A' as u16 + 1);
118    }
119
120    col_num - 1
121}
122
123/// Convert zero indexed row and column cell numbers to a `A1` style string.
124///
125/// This is a utility function to convert zero indexed row and column cell
126/// values to an `A1` cell reference. This can be useful when constructing
127/// ranges for formulas.
128///
129/// # Parameters
130///
131/// - `row`: The zero indexed row number.
132/// - `col`: The zero indexed column number.
133///
134/// # Examples:
135///
136/// ```
137/// use rust_xlsxwriter::row_col_to_cell;
138///
139/// assert_eq!(row_col_to_cell(0, 0), "A1");
140/// assert_eq!(row_col_to_cell(0, 1), "B1");
141/// assert_eq!(row_col_to_cell(1, 1), "B2");
142/// ```
143///
144pub fn row_col_to_cell(row: RowNum, col: ColNum) -> String {
145    format!("{}{}", column_number_to_name(col), row + 1)
146}
147
148/// Convert zero indexed row and column cell numbers to an absolute `$A$1` style
149/// range string.
150///
151/// This is a utility function to convert zero indexed row and column cell
152/// values to an absolute `$A$1` cell reference. This can be useful when
153/// constructing ranges for formulas.
154///
155/// # Parameters
156///
157/// - `row`: The zero indexed row number.
158/// - `col`: The zero indexed column number.
159///
160/// # Examples:
161///
162/// ```
163/// use rust_xlsxwriter::row_col_to_cell_absolute;
164///
165/// assert_eq!(row_col_to_cell_absolute(0, 0), "$A$1");
166/// assert_eq!(row_col_to_cell_absolute(0, 1), "$B$1");
167/// assert_eq!(row_col_to_cell_absolute(1, 1), "$B$2");
168/// ```
169///
170pub fn row_col_to_cell_absolute(row: RowNum, col: ColNum) -> String {
171    format!("${}${}", column_number_to_name(col), row + 1)
172}
173
174/// Convert zero indexed row and col cell numbers to a `A1:B1` style range
175/// string.
176///
177/// This is a utility function to convert zero based row and column cell values
178/// to an `A1:B1` style range reference.
179///
180/// Note, this function should not be used to create a chart range. Use the
181/// 5-tuple version of [`IntoChartRange`](crate::IntoChartRange) instead.
182///
183/// # Parameters
184///
185/// - `first_row`: The first row of the range. (All zero indexed.)
186/// - `first_col`: The first row of the range.
187/// - `last_row`: The last row of the range.
188/// - `last_col`: The last row of the range.
189///
190/// # Examples:
191///
192/// ```
193/// use rust_xlsxwriter::cell_range;
194///
195/// assert_eq!(cell_range(0, 0, 9, 0), "A1:A10");
196/// assert_eq!(cell_range(1, 2, 8, 2), "C2:C9");
197/// assert_eq!(cell_range(0, 0, 3, 4), "A1:E4");
198/// ```
199///
200/// If the start and end cell are the same then a single cell range is created:
201///
202/// ```
203/// use rust_xlsxwriter::cell_range;
204///
205/// assert_eq!(cell_range(0, 0, 0, 0), "A1");
206/// ```
207///
208pub fn cell_range(
209    first_row: RowNum,
210    first_col: ColNum,
211    last_row: RowNum,
212    last_col: ColNum,
213) -> String {
214    let range1 = row_col_to_cell(first_row, first_col);
215    let range2 = row_col_to_cell(last_row, last_col);
216
217    if range1 == range2 {
218        range1
219    } else {
220        format!("{range1}:{range2}")
221    }
222}
223
224/// Convert zero indexed row and col cell numbers to an absolute `$A$1:$B$1`
225/// style range string.
226///
227/// This is a utility function to convert zero based row and column cell values
228/// to an absolute `$A$1:$B$1` style range reference.
229///
230/// Note, this function should not be used to create a chart range. Use the
231/// 5-tuple version of [`IntoChartRange`](crate::IntoChartRange) instead.
232///
233/// # Parameters
234///
235/// - `first_row`: The first row of the range. (All zero indexed.)
236/// - `first_col`: The first row of the range.
237/// - `last_row`: The last row of the range.
238/// - `last_col`: The last row of the range.
239///
240/// # Examples:
241///
242/// ```
243/// use rust_xlsxwriter::cell_range_absolute;
244///
245/// assert_eq!(cell_range_absolute(0, 0, 9, 0), "$A$1:$A$10");
246/// assert_eq!(cell_range_absolute(1, 2, 8, 2), "$C$2:$C$9");
247/// assert_eq!(cell_range_absolute(0, 0, 3, 4), "$A$1:$E$4");
248/// ```
249///
250/// If the start and end cell are the same then a single cell range is created:
251///
252/// ```
253/// use rust_xlsxwriter::cell_range_absolute;
254///
255/// assert_eq!(cell_range_absolute(0, 0, 0, 0), "$A$1");
256/// ```
257///
258pub fn cell_range_absolute(
259    first_row: RowNum,
260    first_col: ColNum,
261    last_row: RowNum,
262    last_col: ColNum,
263) -> String {
264    let range1 = row_col_to_cell_absolute(first_row, first_col);
265    let range2 = row_col_to_cell_absolute(last_row, last_col);
266
267    if range1 == range2 {
268        range1
269    } else {
270        format!("{range1}:{range2}")
271    }
272}
273
274/// Convert a worksheet name and cell reference to an Excel "Sheet1!A1:B1" style
275/// range string.
276///
277/// This is a utility function to convert a worksheet name zero based column
278/// reference to a string representation. This can be useful when constructing
279/// ranges for formulas.
280///
281/// Note, this function should not be used to create a chart range. Use the
282/// 5-tuple version of [`IntoChartRange`](crate::IntoChartRange) instead.
283///
284/// # Parameters
285///
286/// - `sheet_name`: The worksheet name that the range refers to.
287/// - `first_row`: The first row of the range. (All zero indexed.)
288/// - `first_col`: The first row of the range.
289/// - `last_row`: The last row of the range.
290/// - `last_col`: The last row of the range.
291///
292/// # Examples:
293///
294/// ```
295/// use rust_xlsxwriter::worksheet_range;
296///
297/// // Single cell range.
298/// let range = worksheet_range("Sheet1", 0, 0, 0, 0);
299/// assert_eq!(range, "Sheet1!A1");
300///
301/// // Cell range.
302/// let range = worksheet_range("Sheet1", 0, 0, 9, 0);
303/// assert_eq!(range, "Sheet1!A1:A10");
304///
305/// // Sheetname that requires quoting.
306/// let range = worksheet_range("Sheet 1", 0, 0, 9, 0);
307/// assert_eq!(range, "'Sheet 1'!A1:A10");
308/// ```
309///
310pub fn worksheet_range(
311    sheet_name: &str,
312    first_row: RowNum,
313    first_col: ColNum,
314    last_row: RowNum,
315    last_col: ColNum,
316) -> String {
317    chart_range(sheet_name, first_row, first_col, last_row, last_col)
318}
319
320/// Convert a worksheet name and cell reference to an Excel "Sheet1!$A$1:$B$1"
321/// style absolute range string.
322///
323/// This is a utility function to convert a worksheet name zero based column
324/// reference to a string representation. This can be useful when constructing
325/// ranges for formulas.
326///
327/// Note, this function should not be used to create a chart range. Use the
328/// 5-tuple version of [`IntoChartRange`](crate::IntoChartRange) instead.
329///
330/// # Parameters
331///
332/// - `sheet_name`: The worksheet name that the range refers to.
333/// - `first_row`: The first row of the range. (All zero indexed.)
334/// - `first_col`: The first row of the range.
335/// - `last_row`: The last row of the range.
336/// - `last_col`: The last row of the range.
337///
338/// # Examples:
339///
340/// ```
341/// use rust_xlsxwriter::worksheet_range_absolute;
342///
343/// // Single cell range.
344/// let range = worksheet_range_absolute("Sheet1", 0, 0, 0, 0);
345/// assert_eq!(range, "Sheet1!$A$1");
346///
347/// // Cell range.
348/// let range = worksheet_range_absolute("Sheet1", 0, 0, 9, 0);
349/// assert_eq!(range, "Sheet1!$A$1:$A$10");
350///
351/// // Sheetname that requires quoting.
352/// let range = worksheet_range_absolute("Sheet 1", 0, 0, 9, 0);
353/// assert_eq!(range, "'Sheet 1'!$A$1:$A$10");
354/// ```
355///
356pub fn worksheet_range_absolute(
357    sheet_name: &str,
358    first_row: RowNum,
359    first_col: ColNum,
360    last_row: RowNum,
361    last_col: ColNum,
362) -> String {
363    chart_range_abs(sheet_name, first_row, first_col, last_row, last_col)
364}
365
366/// Serialize a naive/civil date/time to an Excel value.
367///
368/// This is a helper function for serializing [`Chrono`] or [`Jiff`] naive/civil
369/// date/time fields using [Serde](https://serde.rs). "Naive" and "Civil" means
370/// that the dates/times don't have timezone information, like Excel.
371///
372/// The function works for the following types:
373///
374///   - [`chrono::NaiveDateTime`]
375///   - [`chrono::NaiveDate`]
376///   - [`chrono::NaiveTime`]
377///   - [`jiff::civil::Datetime`]
378///   - [`jiff::civil::Date`]
379///   - [`jiff::civil::Time`]
380///
381/// [`Chrono`]: https://docs.rs/chrono/latest/chrono
382/// [`chrono::NaiveDate`]:
383///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html
384/// [`chrono::NaiveTime`]:
385///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html
386/// [`chrono::NaiveDateTime`]:
387///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html
388///
389/// [`Jiff`]: https://docs.rs/jiff/latest/jiff
390/// [`jiff::civil::Datetime`]:
391///     https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html
392/// [`jiff::civil::Date`]:
393///     https://docs.rs/jiff/latest/jiff/civil/struct.Date.html
394/// [`jiff::civil::Time`]:
395///     https://docs.rs/jiff/latest/jiff/civil/struct.Time.html
396///
397/// Support for these types is enabled via the `chrono` and `jiff` cargo
398/// features.
399///
400/// `Option<T>` datetime types can be handled with
401/// [`serialize_option_datetime_to_excel()`].
402///
403/// See [Working with Serde](crate::serializer#working-with-serde) for more
404/// information about serialization with `rust_xlsxwriter`.
405///
406/// # Parameters
407///
408/// - `datetime`: A date/time instance that implements [`IntoExcelDateTime`].
409/// - `serializer`: A type/instance that implements the [`serde`] `Serializer`
410///   trait.
411///
412/// # Errors
413///
414/// - [`XlsxError::SerdeError`] - A wrapped serialization error.
415///
416/// # Examples
417///
418/// Example of a serializable struct with a Chrono Naive value with a helper
419/// function.
420///
421/// ```
422/// # // This code is available in examples/doc_worksheet_serialize_datetime3.rs
423/// #
424/// use chrono::NaiveDate;
425/// use serde::Serialize;
426///
427/// use rust_xlsxwriter::utility::serialize_datetime_to_excel;
428///
429/// fn main() {
430///     #[derive(Serialize)]
431///     struct Student {
432///         full_name: String,
433///
434///         #[serde(serialize_with = "serialize_datetime_to_excel")]
435///         birth_date: NaiveDate,
436///
437///         id_number: u32,
438///     }
439/// }
440/// ```
441///
442#[cfg(feature = "serde")]
443#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
444pub fn serialize_datetime_to_excel<S>(
445    datetime: impl IntoExcelDateTime,
446    serializer: S,
447) -> Result<S::Ok, S::Error>
448where
449    S: Serializer,
450{
451    serializer.serialize_f64(datetime.to_excel_serial_date())
452}
453
454/// Serialize an `Option` naive/civil date/time to an Excel value.
455///
456/// This is a helper function for serializing [`Chrono`] or [`Jiff`] naive/civil
457/// date/time fields using [Serde](https://serde.rs). "Naive" and "Civil" means
458/// that the dates/times don't have timezone information, like Excel.
459///
460/// A helper function is provided for [`Option`] Chrono and Jiff values since it
461/// is common to have `Option<T>` values as a result of deserialization. It also
462/// takes care of the use case where you want a `None` value to be written as a
463/// blank cell with the same cell format as other values of the field type.
464///
465/// The function works for the following `T` types in an `Option<T>`:
466///
467///   - [`chrono::NaiveDateTime`]
468///   - [`chrono::NaiveDate`]
469///   - [`chrono::NaiveTime`]
470///   - [`jiff::civil::Datetime`]
471///   - [`jiff::civil::Date`]
472///   - [`jiff::civil::Time`]
473///
474/// [`Chrono`]: https://docs.rs/chrono/latest/chrono
475/// [`chrono::NaiveDate`]:
476///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDate.html
477/// [`chrono::NaiveTime`]:
478///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html
479/// [`chrono::NaiveDateTime`]:
480///     https://docs.rs/chrono/latest/chrono/naive/struct.NaiveDateTime.html
481///
482/// [`Jiff`]: https://docs.rs/jiff/latest/jiff
483/// [`jiff::civil::Datetime`]:
484///     https://docs.rs/jiff/latest/jiff/civil/struct.DateTime.html
485/// [`jiff::civil::Date`]:
486///     https://docs.rs/jiff/latest/jiff/civil/struct.Date.html
487/// [`jiff::civil::Time`]:
488///     https://docs.rs/jiff/latest/jiff/civil/struct.Time.html
489///
490/// Support for these types is enabled via the `chrono` and `jiff` cargo
491/// features.
492///
493/// Non `Option<T>` types can be handled with [`serialize_datetime_to_excel()`].
494///
495/// See [Working with Serde](crate::serializer#working-with-serde) for more
496/// information about serialization with `rust_xlsxwriter`.
497///
498/// # Parameters
499///
500/// - `datetime`: A date/time instance that implements [`IntoExcelDateTime`]
501///   wrapped in an [`Option`].
502/// - `serializer`: A type/instance that implements the [`serde`] `Serializer`
503///   trait.
504///
505/// # Errors
506///
507/// - [`XlsxError::SerdeError`] - A wrapped serialization error.
508///
509/// # Examples
510///
511/// Example of a serializable struct with an Option Chrono Naive value with a
512/// helper function.
513///
514///
515/// ```
516/// # // This code is available in examples/doc_worksheet_serialize_datetime5.rs
517/// #
518/// use chrono::NaiveDate;
519/// use serde::Serialize;
520///
521/// use rust_xlsxwriter::utility::serialize_option_datetime_to_excel;
522///
523/// fn main() {
524///     #[derive(Serialize)]
525///     struct Student {
526///         full_name: String,
527///
528///         #[serde(serialize_with = "serialize_option_datetime_to_excel")]
529///         birth_date: Option<NaiveDate>,
530///
531///         id_number: u32,
532///     }
533/// }
534/// ```
535///
536#[cfg(feature = "serde")]
537#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
538pub fn serialize_option_datetime_to_excel<S>(
539    datetime: &Option<impl IntoExcelDateTime>,
540    serializer: S,
541) -> Result<S::Ok, S::Error>
542where
543    S: Serializer,
544{
545    match datetime {
546        Some(datetime) => serializer.serialize_f64(datetime.to_excel_serial_date()),
547        None => serializer.serialize_none(),
548    }
549}
550
551/// Serialize a chrono naive date/time to an Excel value.
552///
553/// This is deprecated. Use [`serialize_datetime_to_excel()`] instead.
554///
555/// # Parameters
556///
557/// - `datetime`: A date/time instance that implements [`IntoExcelDateTime`].
558/// - `serializer`: A type/instance that implements the [`serde`] `Serializer`
559///   trait.
560///
561/// # Errors
562///
563/// - [`XlsxError::SerdeError`] - A wrapped serialization error.
564///
565#[cfg(feature = "serde")]
566#[doc(hidden)]
567#[deprecated(since = "0.88.0", note = "use `serialize_datetime_to_excel()` instead")]
568pub fn serialize_chrono_naive_to_excel<S>(
569    datetime: impl IntoExcelDateTime,
570    serializer: S,
571) -> Result<S::Ok, S::Error>
572where
573    S: Serializer,
574{
575    serializer.serialize_f64(datetime.to_excel_serial_date())
576}
577
578/// Serialize an `Option` chrono naive date/time to an Excel value.
579///
580/// This is deprecated. Use [`serialize_option_datetime_to_excel()`] instead.
581///
582/// # Parameters
583///
584/// - `datetime`: A date/time instance that implements [`IntoExcelDateTime`]
585///   wrapped in an [`Option`].
586/// - `serializer`: A type/instance that implements the [`serde`] `Serializer`
587///   trait.
588///
589/// # Errors
590///
591/// - [`XlsxError::SerdeError`] - A wrapped serialization error.
592///
593#[cfg(feature = "serde")]
594#[doc(hidden)]
595#[deprecated(
596    since = "0.88.0",
597    note = "use `serialize_option_datetime_to_excel()` instead"
598)]
599pub fn serialize_chrono_option_naive_to_excel<S>(
600    datetime: &Option<impl IntoExcelDateTime>,
601    serializer: S,
602) -> Result<S::Ok, S::Error>
603where
604    S: Serializer,
605{
606    match datetime {
607        Some(datetime) => serializer.serialize_f64(datetime.to_excel_serial_date()),
608        None => serializer.serialize_none(),
609    }
610}
611
612// Convert zero indexed row and col cell references to a non-absolute chart
613// "Sheet1!A1:B1" style range string.
614pub(crate) fn chart_range(
615    sheet_name: &str,
616    first_row: RowNum,
617    first_col: ColNum,
618    last_row: RowNum,
619    last_col: ColNum,
620) -> String {
621    let sheet_name = quote_sheet_name(sheet_name);
622    let range1 = row_col_to_cell(first_row, first_col);
623    let range2 = row_col_to_cell(last_row, last_col);
624
625    if range1 == range2 {
626        format!("{sheet_name}!{range1}")
627    } else {
628        format!("{sheet_name}!{range1}:{range2}")
629    }
630}
631
632// Convert zero indexed row and col cell references to an absolute chart
633// "Sheet1!$A$1:$B$1" style range string.
634pub(crate) fn chart_range_abs(
635    sheet_name: &str,
636    first_row: RowNum,
637    first_col: ColNum,
638    last_row: RowNum,
639    last_col: ColNum,
640) -> String {
641    let sheet_name = quote_sheet_name(sheet_name);
642    let range1 = row_col_to_cell_absolute(first_row, first_col);
643    let range2 = row_col_to_cell_absolute(last_row, last_col);
644
645    if range1 == range2 {
646        format!("{sheet_name}!{range1}")
647    } else {
648        format!("{sheet_name}!{range1}:{range2}")
649    }
650}
651
652// Convert zero indexed row and col cell references to a range and tuple string
653// suitable for an error message.
654pub(crate) fn chart_error_range(
655    sheet_name: &str,
656    first_row: RowNum,
657    first_col: ColNum,
658    last_row: RowNum,
659    last_col: ColNum,
660) -> String {
661    let sheet_name = quote_sheet_name(sheet_name);
662    let range1 = row_col_to_cell(first_row, first_col);
663    let range2 = row_col_to_cell(last_row, last_col);
664
665    if range1 == range2 {
666        format!("{sheet_name}!{range1}/({first_row}, {first_col})")
667    } else {
668        format!("{sheet_name}!{range1}:{range2}/({first_row}, {first_col}, {last_row}, {last_col})")
669    }
670}
671
672/// Enclose a worksheet name in single quotes as required by Excel.
673///
674/// Worksheet names that are used in Excel range references must be single
675/// quoted if they contain non-word characters or if they look like cell
676/// references. The most common instance of this is when the worksheet name
677/// contains spaces. For example, `Sheet1` would be represented without
678/// change in a formula as `=Sheet1!A1`, whereas `Sheet 1` would be represented
679/// as `='Sheet 1'!A1`.
680///
681/// # Parameters
682///
683/// - `sheetname`: The worksheet name to quote.
684///
685/// # Examples
686///
687/// The following example demonstrates quoting worksheet names.
688///
689/// ```
690/// use rust_xlsxwriter::utility::quote_sheet_name;
691///
692/// // Doesn't need to be quoted.
693/// let result = quote_sheet_name("Sheet1");
694/// assert_eq!(result, "Sheet1");
695///
696/// // Spaces need to be quoted.
697/// let result = quote_sheet_name("Sheet 1");
698/// assert_eq!(result, "'Sheet 1'");
699///
700/// // Special characters need to be quoted.
701/// let result = quote_sheet_name("Sheet-1");
702/// assert_eq!(result, "'Sheet-1'");
703///
704/// // Single quotes need to be escaped with a quote.
705/// let result = quote_sheet_name("Sheet'1");
706/// assert_eq!(result, "'Sheet''1'");
707///
708/// // A1 style cell references don't need to be quoted.
709/// let result = quote_sheet_name("A1");
710/// assert_eq!(result, "'A1'");
711///
712/// // R1C1 style cell references need to be quoted.
713/// let result = quote_sheet_name("RC1");
714/// assert_eq!(result, "'RC1'");
715/// ```
716///
717#[allow(clippy::if_same_then_else)]
718pub fn quote_sheet_name(sheetname: &str) -> String {
719    // Sheetnames used in references should be quoted if they contain any
720    // spaces, special characters or if they look like a A1 or RC cell
721    // reference. The rules are shown inline below.
722    let mut sheetname = sheetname.to_string();
723    let uppercase_sheetname = sheetname.to_uppercase();
724    let mut requires_quoting = false;
725    let col_max = u64::from(COL_MAX);
726    let row_max = u64::from(ROW_MAX);
727
728    // Split sheetnames that look like A1 and R1C1 style cell references into a
729    // leading string and a trailing number.
730    let (string_part, number_part) = split_cell_reference(&sheetname);
731
732    // The number part of the sheet name can have trailing non-digit characters
733    // and still be a valid R1C1 match. However, to test the R1C1 row/col part
734    // we need to extract just the number part.
735    let mut number_parts = number_part.split(|c: char| !c.is_ascii_digit());
736    let rc_number_part = number_parts.next().unwrap_or_default();
737
738    // Ignore strings that are already quoted.
739    if !sheetname.starts_with('\'') {
740        // --------------------------------------------------------------------
741        // Rule 1. Sheet names that contain anything other than \w and "."
742        // characters must be quoted.
743        // --------------------------------------------------------------------
744
745        if !sheetname
746            .chars()
747            .all(|c| c.is_alphanumeric() || c == '_' || c == '.' || is_emoji(c))
748        {
749            requires_quoting = true;
750        }
751        // --------------------------------------------------------------------
752        // Rule 2. Sheet names that start with a digit or "." must be quoted.
753        // --------------------------------------------------------------------
754        else if sheetname.starts_with(|c: char| c.is_ascii_digit() || c == '.' || is_emoji(c)) {
755            requires_quoting = true;
756        }
757        // --------------------------------------------------------------------
758        // Rule 3. Sheet names must not be a valid A1 style cell reference.
759        // Valid means that the row and column range values must also be within
760        // Excel row and column limits.
761        // --------------------------------------------------------------------
762        else if (1..=3).contains(&string_part.len())
763            && number_part.chars().all(|c| c.is_ascii_digit())
764        {
765            let col = column_name_to_number(&string_part);
766            let col = u64::from(col + 1);
767
768            let row = number_part.parse::<u64>().unwrap_or_default();
769
770            if row > 0 && row <= row_max && col <= col_max {
771                requires_quoting = true;
772            }
773        }
774        // --------------------------------------------------------------------
775        // Rule 4. Sheet names must not *start* with a valid RC style cell
776        // reference. Other characters after the valid RC reference are ignored
777        // by Excel. Valid means that the row and column range values must also
778        // be within Excel row and column limits.
779        //
780        // Note: references without trailing characters like R12345 or C12345
781        // are caught by Rule 3. Negative references like R-12345 are caught by
782        // Rule 1 due to dash.
783        // --------------------------------------------------------------------
784
785        // Rule 4a. Check for sheet names that start with R1 style references.
786        else if string_part == "R" {
787            let row = rc_number_part.parse::<u64>().unwrap_or_default();
788
789            if row > 0 && row <= row_max {
790                requires_quoting = true;
791            }
792        }
793        // Rule 4b. Check for sheet names that start with C1 or RC1 style
794        // references.
795        else if string_part == "RC" || string_part == "C" {
796            let col = rc_number_part.parse::<u64>().unwrap_or_default();
797
798            if col > 0 && col <= col_max {
799                requires_quoting = true;
800            }
801        }
802        // Rule 4c. Check for some single R/C references.
803        else if uppercase_sheetname == "R"
804            || uppercase_sheetname == "C"
805            || uppercase_sheetname == "RC"
806        {
807            requires_quoting = true;
808        }
809    }
810
811    if requires_quoting {
812        // Double up any single quotes.
813        sheetname = sheetname.replace('\'', "''");
814
815        // Single quote the sheet name.
816        sheetname = format!("'{sheetname}'");
817    }
818
819    sheetname
820}
821
822// Unquote an Excel single quoted string.
823pub(crate) fn unquote_sheetname(sheetname: &str) -> String {
824    if sheetname.starts_with('\'') && sheetname.ends_with('\'') {
825        let sheetname = sheetname[1..sheetname.len() - 1].to_string();
826        sheetname.replace("''", "'")
827    } else {
828        sheetname.to_string()
829    }
830}
831
832// Match emoji characters when quoting sheetnames. The following were generated from:
833// https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEmoji%3DYes%3A%5D&abb=on&esc=on&g=&i=
834//
835pub(crate) fn is_emoji(c: char) -> bool {
836    if c < '\u{203C}' {
837        // Shortcut for most chars in the lower range. We ignore '#', '*',
838        // '0-9', '©️' and '®️' which are in this range and which are, strictly
839        // speaking, emoji symbols but they are not treated so by Excel in the
840        // context of this check.
841        return false;
842    }
843
844    if c < '\u{01F004}' {
845        return matches!(c,
846            '\u{203C}' | '\u{2049}' | '\u{2122}' | '\u{2139}' | '\u{2194}'..='\u{2199}' |
847            '\u{21A9}' | '\u{21AA}' | '\u{231A}' | '\u{231B}' | '\u{2328}' | '\u{23CF}' |
848            '\u{23E9}'..='\u{23F3}' | '\u{23F8}'..='\u{23FA}' | '\u{24C2}' | '\u{25AA}' |
849            '\u{25AB}' | '\u{25B6}' | '\u{25C0}' | '\u{25FB}'..='\u{25FE}' |
850            '\u{2600}'..='\u{2604}' | '\u{260E}' | '\u{2611}' | '\u{2614}' | '\u{2615}' |
851            '\u{2618}' | '\u{261D}' | '\u{2620}' | '\u{2622}' | '\u{2623}' | '\u{2626}' |
852            '\u{262A}' | '\u{262E}' | '\u{262F}' | '\u{2638}'..='\u{263A}' | '\u{2640}' |
853            '\u{2642}' | '\u{2648}'..='\u{2653}' | '\u{265F}' | '\u{2660}' | '\u{2663}' |
854            '\u{2665}' | '\u{2666}' | '\u{2668}' | '\u{267B}' | '\u{267E}' | '\u{267F}' |
855            '\u{2692}'..='\u{2697}' | '\u{2699}' | '\u{269B}' | '\u{269C}' | '\u{26A0}' |
856            '\u{26A1}' | '\u{26A7}' | '\u{26AA}' | '\u{26AB}' | '\u{26B0}' | '\u{26B1}' |
857            '\u{26BD}' | '\u{26BE}' | '\u{26C4}' | '\u{26C5}' | '\u{26C8}' | '\u{26CE}' |
858            '\u{26CF}' | '\u{26D1}' | '\u{26D3}' | '\u{26D4}' | '\u{26E9}' | '\u{26EA}' |
859            '\u{26F0}'..='\u{26F5}' | '\u{26F7}'..='\u{26FA}' | '\u{26FD}' | '\u{2702}' |
860            '\u{2705}' | '\u{2708}'..='\u{270D}' | '\u{270F}' | '\u{2712}' | '\u{2714}' |
861            '\u{2716}' | '\u{271D}' | '\u{2721}' | '\u{2728}' | '\u{2733}' | '\u{2734}' |
862            '\u{2744}' | '\u{2747}' | '\u{274C}' | '\u{274E}' | '\u{2753}'..='\u{2755}' |
863            '\u{2757}' | '\u{2763}' | '\u{2764}' | '\u{2795}'..='\u{2797}' | '\u{27A1}' |
864            '\u{27B0}' | '\u{27BF}' | '\u{2934}' | '\u{2935}' | '\u{2B05}'..='\u{2B07}' |
865            '\u{2B1B}' | '\u{2B1C}' | '\u{2B50}' | '\u{2B55}' | '\u{3030}' | '\u{303D}' |
866            '\u{3297}' | '\u{3299}'
867        );
868    }
869
870    matches!(c,
871        '\u{01F004}' | '\u{01F0CF}' | '\u{01F170}' | '\u{01F171}' | '\u{01F17E}' | '\u{01F17F}' |
872        '\u{01F18E}' | '\u{01F191}'..='\u{01F19A}' | '\u{01F1E6}'..='\u{01F1FF}' | '\u{01F201}' |
873        '\u{01F202}' | '\u{01F21A}' | '\u{01F22F}' | '\u{01F232}'..='\u{01F23A}' | '\u{01F250}' |
874        '\u{01F251}' | '\u{01F300}'..='\u{01F321}' | '\u{01F324}'..='\u{01F393}' | '\u{01F396}' |
875        '\u{01F397}' | '\u{01F399}'..='\u{01F39B}' | '\u{01F39E}'..='\u{01F3F0}' |
876        '\u{01F3F3}'..='\u{01F3F5}' | '\u{01F3F7}'..='\u{01F4FD}' | '\u{01F4FF}'..='\u{01F53D}' |
877        '\u{01F549}'..='\u{01F54E}' | '\u{01F550}'..='\u{01F567}' | '\u{01F56F}' | '\u{01F570}' |
878        '\u{01F573}'..='\u{01F57A}' | '\u{01F587}' | '\u{01F58A}'..='\u{01F58D}' | '\u{01F590}' |
879        '\u{01F595}' | '\u{01F596}' | '\u{01F5A4}' | '\u{01F5A5}' | '\u{01F5A8}' | '\u{01F5B1}' |
880        '\u{01F5B2}' | '\u{01F5BC}' | '\u{01F5C2}'..='\u{01F5C4}' | '\u{01F5D1}'..='\u{01F5D3}' |
881        '\u{01F5DC}'..='\u{01F5DE}' | '\u{01F5E1}' | '\u{01F5E3}' | '\u{01F5E8}' | '\u{01F5EF}' |
882        '\u{01F5F3}' | '\u{01F5FA}'..='\u{01F64F}' | '\u{01F680}'..='\u{01F6C5}' |
883        '\u{01F6CB}'..='\u{01F6D2}' | '\u{01F6D5}'..='\u{01F6D7}' | '\u{01F6DC}'..='\u{01F6E5}' |
884        '\u{01F6E9}' | '\u{01F6EB}' | '\u{01F6EC}' | '\u{01F6F0}' | '\u{01F6F3}'..='\u{01F6FC}' |
885        '\u{01F7E0}'..='\u{01F7EB}' | '\u{01F7F0}' | '\u{01F90C}'..='\u{01F93A}' |
886        '\u{01F93C}'..='\u{01F945}' | '\u{01F947}'..='\u{01F9FF}' | '\u{01FA70}'..='\u{01FA7C}' |
887        '\u{01FA80}'..='\u{01FA88}' | '\u{01FA90}'..='\u{01FABD}' | '\u{01FABF}'..='\u{01FAC5}' |
888        '\u{01FACE}'..='\u{01FADB}' | '\u{01FAE0}'..='\u{01FAE8}' | '\u{01FAF0}'..='\u{01FAF8}'
889    )
890}
891
892// Split sheetnames that look like A1 and R1C1 style cell references into a
893// leading string and a trailing number.
894pub(crate) fn split_cell_reference(sheetname: &str) -> (String, String) {
895    match sheetname.find(|c: char| c.is_ascii_digit()) {
896        Some(position) => (
897            (sheetname[..position]).to_uppercase(),
898            (sheetname[position..]).to_uppercase(),
899        ),
900        None => (String::new(), String::new()),
901    }
902}
903
904// Check that a range string like "A1" or "A1:B3" are valid. This function
905// assumes that the '$' absolute anchor has already been stripped.
906pub(crate) fn is_valid_range(range: &str) -> bool {
907    if range.is_empty() {
908        return false;
909    }
910
911    // The range should start with a letter and end in a number.
912    if !range.starts_with(|c: char| c.is_ascii_uppercase())
913        || !range.ends_with(|c: char| c.is_ascii_digit())
914    {
915        return false;
916    }
917
918    // The range should only include the characters 'A-Z', '0-9' and ':'
919    if !range
920        .chars()
921        .all(|c: char| c.is_ascii_uppercase() || c.is_ascii_digit() || c == ':')
922    {
923        return false;
924    }
925
926    true
927}
928
929/// Check that a worksheet name is valid in Excel.
930///
931/// This function checks if an worksheet name is valid according to the Excel
932/// rules:
933///
934/// - The name is less than 32 characters.
935/// - The name isn't blank.
936/// - The name doesn't contain any of the characters: `[ ] : * ? / \`.
937/// - The name doesn't start or end with an apostrophe.
938///
939/// The worksheet name "History" isn't allowed in English versions of Excel
940/// since it is a reserved name. However it is allowed in some other language
941/// versions so this function doesn't raise it as an error. Overall it is best
942/// to avoid using it.
943///
944/// The rules for worksheet names in Excel are explained in the [Microsoft
945/// Office documentation].
946///
947/// [Microsoft Office documentation]:
948///     https://support.office.com/en-ie/article/rename-a-worksheet-3f1f7148-ee83-404d-8ef0-9ff99fbad1f9
949///
950/// # Parameters
951///
952/// - `name`: The worksheet name. It must follow the Excel rules, shown above.
953///
954/// # Errors
955///
956/// - [`XlsxError::SheetnameCannotBeBlank`] - Worksheet name cannot be blank.
957/// - [`XlsxError::SheetnameLengthExceeded`] - Worksheet name exceeds Excel's
958///   limit of 31 characters.
959/// - [`XlsxError::SheetnameContainsInvalidCharacter`] - Worksheet name cannot
960///   contain invalid characters: `[ ] : * ? / \`
961/// - [`XlsxError::SheetnameStartsOrEndsWithApostrophe`] - Worksheet name cannot
962///   start or end with an apostrophe.
963///
964/// # Examples
965///
966/// The following example demonstrates testing for a valid worksheet name.
967///
968/// ```
969/// # // This code is available in examples/doc_utility_check_sheet_name.rs
970/// #
971/// # use rust_xlsxwriter::{utility, XlsxError};
972/// #
973/// # fn main() -> Result<(), XlsxError> {
974///     // This worksheet name is valid and doesn't raise an error.
975///     utility::check_sheet_name("2030-01-01")?;
976///
977///     // This worksheet name isn't valid due to the forward slashes.
978///     let result = utility::check_sheet_name("2030/01/01");
979///
980///     assert!(matches!(
981///         result,
982///         Err(XlsxError::SheetnameContainsInvalidCharacter(_))
983///     ));
984/// #
985/// #     Ok(())
986/// # }
987///
988pub fn check_sheet_name(name: &str) -> Result<(), XlsxError> {
989    let error_message = format!("Invalid Excel worksheet name '{name}'");
990    validate_sheetname(name, &error_message)
991}
992
993// Internal function to validate worksheet name.
994pub(crate) fn validate_sheetname(name: &str, message: &str) -> Result<(), XlsxError> {
995    // Check that the sheet name isn't blank.
996    if name.is_empty() {
997        return Err(XlsxError::SheetnameCannotBeBlank(message.to_string()));
998    }
999
1000    // Check that sheet sheetname is <= 31, an Excel limit.
1001    if name.chars().count() > 31 {
1002        return Err(XlsxError::SheetnameLengthExceeded(message.to_string()));
1003    }
1004
1005    // Check that the sheet name doesn't contain any invalid characters.
1006    if name.contains(['*', '?', ':', '[', ']', '\\', '/']) {
1007        return Err(XlsxError::SheetnameContainsInvalidCharacter(
1008            message.to_string(),
1009        ));
1010    }
1011
1012    // Check that the sheet name doesn't start or end with an apostrophe.
1013    if name.starts_with('\'') || name.ends_with('\'') {
1014        return Err(XlsxError::SheetnameStartsOrEndsWithApostrophe(
1015            message.to_string(),
1016        ));
1017    }
1018
1019    Ok(())
1020}
1021
1022// Internal function to validate VBA code names.
1023pub(crate) fn validate_vba_name(name: &str) -> Result<(), XlsxError> {
1024    // Check that the  name isn't blank.
1025    if name.is_empty() {
1026        return Err(XlsxError::VbaNameError(
1027            "VBA name cannot be blank".to_string(),
1028        ));
1029    }
1030
1031    // Check that name is <= 31, an Excel limit.
1032    if name.chars().count() > 31 {
1033        return Err(XlsxError::VbaNameError(
1034            "VBA name exceeds Excel limit of 31 characters: {name}".to_string(),
1035        ));
1036    }
1037
1038    // Check for anything other than letters, numbers, and underscores.
1039    if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
1040        return Err(XlsxError::VbaNameError(
1041            "VBA name contains non-word character: {name}".to_string(),
1042        ));
1043    }
1044
1045    // Check that the name starts with a letter.
1046    if !name.chars().next().unwrap().is_alphabetic() {
1047        return Err(XlsxError::VbaNameError(
1048            "VBA name must start with letter character: {name}".to_string(),
1049        ));
1050    }
1051
1052    Ok(())
1053}
1054
1055/// Calculate the width required to auto-fit a string in a cell.
1056///
1057/// The [`Worksheet::autofit()`](crate::Worksheet::autofit) method can be used
1058/// to auto-fit cell data to the optimal column width. However, in some cases
1059/// you may wish to handle auto-fitting yourself and apply additional logic to
1060/// limit the maximum and minimum ranges.
1061///
1062/// The `cell_autofit_width()` function can be used to perform the required
1063/// calculation. It works by estimating the pixel width of a string based on the
1064/// width of each character. It also adds a 7 pixel padding for the cell
1065/// boundary in the same way that Excel does.
1066///
1067/// You can use the  calculated width in conjunction with the
1068/// [`Worksheet::set_column_autofit_width()`](crate::Worksheet::set_column_autofit_width)
1069/// method, see the example below.
1070///
1071/// Notes:
1072///
1073/// - The width calculation is based on the default Excel font type of Calibri
1074///   and character size of 11. It will not give correct results for other fonts
1075///   or font sizes.
1076///
1077/// - If you are autofitting a header with an autofilter dropdown you should add
1078///   an additional 6 pixels to account for the dropdown symbol.
1079///
1080/// - When dealing with large data sets you can use `cell_autofit_width()` with
1081///   just 50 or 100 rows of data as a performance optimization . This will
1082///   produce a reasonably accurate autofit for the first visible page of data
1083///   without incurring the performance penalty of calculating widths for
1084///   thousands of non-visible strings.
1085///
1086/// # Parameters
1087///
1088/// - `string`: The string reference to calculate the cell width.
1089///
1090/// # Examples
1091///
1092/// The following example demonstrates "auto"-fitting the the width of a column
1093/// in Excel based on the maximum string width. See also the
1094/// [`Worksheet::autofit()`](crate::Worksheet::autofit) command.
1095///
1096/// ```
1097/// # // This code is available in examples/doc_worksheet_set_column_autofit_width.rs
1098/// #
1099/// # use rust_xlsxwriter::{Workbook, XlsxError, cell_autofit_width};
1100/// #
1101/// # fn main() -> Result<(), XlsxError> {
1102/// #     let mut workbook = Workbook::new();
1103/// #
1104/// #     // Add a worksheet to the workbook.
1105/// #     let worksheet = workbook.add_worksheet();
1106/// #
1107///     // Some string data to write.
1108///     let cities = ["Addis Ababa", "Buenos Aires", "Cairo", "Dhaka"];
1109///
1110///     // Write the strings:
1111///     worksheet.write_column(0, 0, cities)?;
1112///
1113///     // Find the maximum column width in pixels.
1114///     let max_width = cities.iter().map(|s| cell_autofit_width(s)).max().unwrap();
1115///
1116///     // Set the column width as if it was auto-fitted.
1117///     worksheet.set_column_autofit_width(0, max_width)?;
1118/// #
1119/// #     workbook.save("worksheet.xlsx")?;
1120/// #
1121/// #     Ok(())
1122/// # }
1123/// ```
1124///
1125/// Output file:
1126///
1127/// <img
1128/// src="https://rustxlsxwriter.github.io/images/worksheet_set_column_autofit_width.png">
1129///
1130pub fn cell_autofit_width(string: &str) -> u32 {
1131    let cell_padding = 7;
1132
1133    pixel_width(string) + cell_padding
1134}
1135
1136// Get the pixel width of a string based on character widths taken from Excel.
1137// Non-ascii characters are given a default width of 8 pixels.
1138#[allow(clippy::match_same_arms)]
1139pub(crate) fn pixel_width(string: &str) -> u32 {
1140    let mut length = 0;
1141
1142    // Limit the autofit width to Excel's limit of 1790 pixels.
1143    if string.chars().count() > 233 {
1144        return MAX_AUTOFIT_WIDTH_PIXELS;
1145    }
1146
1147    for char in string.chars() {
1148        match char {
1149            ' ' | '\'' => length += 3,
1150
1151            ',' | '.' | ':' | ';' | 'I' | '`' | 'i' | 'j' | 'l' => length += 4,
1152
1153            '!' | '(' | ')' | '-' | 'J' | '[' | ']' | 'f' | 'r' | 't' | '{' | '}' => length += 5,
1154
1155            '"' | '/' | 'L' | '\\' | 'c' | 's' | 'z' => length += 6,
1156
1157            '#' | '$' | '*' | '+' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
1158            | '<' | '=' | '>' | '?' | 'E' | 'F' | 'S' | 'T' | 'Y' | 'Z' | '^' | '_' | 'a' | 'g'
1159            | 'k' | 'v' | 'x' | 'y' | '|' | '~' => length += 7,
1160
1161            'B' | 'C' | 'K' | 'P' | 'R' | 'X' | 'b' | 'd' | 'e' | 'h' | 'n' | 'o' | 'p' | 'q'
1162            | 'u' => length += 8,
1163
1164            'A' | 'D' | 'G' | 'H' | 'U' | 'V' => length += 9,
1165
1166            '&' | 'N' | 'O' | 'Q' => length += 10,
1167
1168            '%' | 'w' => length += 11,
1169
1170            'M' | 'm' => length += 12,
1171
1172            '@' | 'W' => length += 13,
1173
1174            _ => length += 8,
1175        }
1176    }
1177
1178    std::cmp::min(length, MAX_AUTOFIT_WIDTH_PIXELS)
1179}
1180
1181// Hash a worksheet password. Based on the algorithm in ECMA-376-4:2016, Office
1182// Open XML File Formats — Transitional Migration Features, Additional
1183// attributes for workbookProtection element (Part 1, §18.2.29).
1184pub(crate) fn hash_password(password: &str) -> u16 {
1185    let mut hash: u16 = 0;
1186    let length = password.len() as u16;
1187
1188    if password.is_empty() {
1189        return 0;
1190    }
1191
1192    for byte in password.as_bytes().iter().rev() {
1193        hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7FFF);
1194        hash ^= u16::from(*byte);
1195    }
1196
1197    hash = ((hash >> 14) & 0x01) | ((hash << 1) & 0x7FFF);
1198    hash ^= length;
1199    hash ^= 0xCE4B;
1200
1201    hash
1202}
1203
1204// Clone and strip the leading '=' from formulas, if present.
1205pub(crate) fn formula_to_string(formula: &str) -> String {
1206    let mut formula = formula.to_string();
1207
1208    if formula.starts_with('=') {
1209        formula.remove(0);
1210    }
1211
1212    formula
1213}
1214
1215// Get default font metrics for a default column width.
1216//
1217// This function returns the font metrics (max_digit_width, padding,
1218// max_col_width) based on the column pixel width for a default font.
1219//
1220// To add support for additional fonts and sizes please open a GitHub request
1221// with an empty sample workbook with one worksheet.
1222//
1223pub(crate) fn default_column_metrics(width: u32) -> Option<(u32, u32, u32)> {
1224    match width {
1225        56 => Some((6, 5, 1533)),
1226        64 => Some((7, 5, 1790)),
1227        72 => Some((8, 5, 2043)),
1228        80 => Some((9, 7, 2300)),
1229        96 => Some((11, 7, 2810)),
1230        104 => Some((12, 7, 3065)),
1231        120 => Some((13, 9, 3323)),
1232        _ => None,
1233    }
1234}
1235
1236// Trait to convert bool to XML "0" or "1".
1237pub(crate) trait ToXmlBoolean {
1238    fn to_xml_bool(self) -> String;
1239}
1240
1241impl ToXmlBoolean for bool {
1242    fn to_xml_bool(self) -> String {
1243        u8::from(self).to_string()
1244    }
1245}