From e5a37a81a53aed1aa7673adb3975c9fce9ce765e Mon Sep 17 00:00:00 2001 From: John McNamara Date: Mon, 1 Jul 2024 00:02:07 +0100 Subject: [PATCH] worksheet: fix repeat_row() issue with quoted sheet names Add a fix for an issue where a worksheet name needed quoting when used with a repeat_row()/Print_Titles defined name. Issue #1078 --- .../test/comparison/test_quote_name05.py | 10 +- .../test/comparison/test_quote_name06.py | 10 +- .../test/comparison/test_quote_name07.py | 11 +- .../test/comparison/test_quote_name08.py | 57 +++++++ .../test/comparison/test_quote_name09.py | 57 +++++++ .../test/comparison/test_quote_name10.py | 57 +++++++ .../test/comparison/test_quote_name11.py | 57 +++++++ .../comparison/xlsx_files/quote_name08.xlsx | Bin 0 -> 9420 bytes .../comparison/xlsx_files/quote_name09.xlsx | Bin 0 -> 9415 bytes .../comparison/xlsx_files/quote_name10.xlsx | Bin 0 -> 9417 bytes .../comparison/xlsx_files/quote_name11.xlsx | Bin 0 -> 9425 bytes .../test/utility/test_quote_sheetname.py | 157 ++++++++++++++++++ xlsxwriter/utility.py | 88 +++++++++- 13 files changed, 488 insertions(+), 16 deletions(-) create mode 100644 xlsxwriter/test/comparison/test_quote_name08.py create mode 100644 xlsxwriter/test/comparison/test_quote_name09.py create mode 100644 xlsxwriter/test/comparison/test_quote_name10.py create mode 100644 xlsxwriter/test/comparison/test_quote_name11.py create mode 100644 xlsxwriter/test/comparison/xlsx_files/quote_name08.xlsx create mode 100644 xlsxwriter/test/comparison/xlsx_files/quote_name09.xlsx create mode 100644 xlsxwriter/test/comparison/xlsx_files/quote_name10.xlsx create mode 100644 xlsxwriter/test/comparison/xlsx_files/quote_name11.xlsx create mode 100644 xlsxwriter/test/utility/test_quote_sheetname.py diff --git a/xlsxwriter/test/comparison/test_quote_name05.py b/xlsxwriter/test/comparison/test_quote_name05.py index e0dca586b..6f96acfaf 100644 --- a/xlsxwriter/test/comparison/test_quote_name05.py +++ b/xlsxwriter/test/comparison/test_quote_name05.py @@ -25,7 +25,9 @@ def test_create_file(self): workbook = Workbook(self.got_filename) - worksheet = workbook.add_worksheet() + sheet_name = "Sheet1" + + worksheet = workbook.add_worksheet(sheet_name) chart = workbook.add_chart({"type": "column"}) chart.axis_ids = [54437760, 59195776] @@ -44,9 +46,9 @@ def test_create_file(self): worksheet.set_portrait() worksheet.vertical_dpi = 200 - chart.add_series({"values": ["Sheet1", 0, 0, 4, 0]}) - chart.add_series({"values": ["Sheet1", 0, 1, 4, 1]}) - chart.add_series({"values": ["Sheet1", 0, 2, 4, 2]}) + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) worksheet.insert_chart("E9", chart) diff --git a/xlsxwriter/test/comparison/test_quote_name06.py b/xlsxwriter/test/comparison/test_quote_name06.py index ac18a8437..a00f3758e 100644 --- a/xlsxwriter/test/comparison/test_quote_name06.py +++ b/xlsxwriter/test/comparison/test_quote_name06.py @@ -25,7 +25,9 @@ def test_create_file(self): workbook = Workbook(self.got_filename) - worksheet = workbook.add_worksheet("Sheet-1") + sheet_name = "Sheet-1" + + worksheet = workbook.add_worksheet(sheet_name) chart = workbook.add_chart({"type": "column"}) chart.axis_ids = [62284544, 83429248] @@ -44,9 +46,9 @@ def test_create_file(self): worksheet.set_portrait() worksheet.vertical_dpi = 200 - chart.add_series({"values": ["Sheet-1", 0, 0, 4, 0]}) - chart.add_series({"values": ["Sheet-1", 0, 1, 4, 1]}) - chart.add_series({"values": ["Sheet-1", 0, 2, 4, 2]}) + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) worksheet.insert_chart("E9", chart) diff --git a/xlsxwriter/test/comparison/test_quote_name07.py b/xlsxwriter/test/comparison/test_quote_name07.py index 725774847..c6735f6b0 100644 --- a/xlsxwriter/test/comparison/test_quote_name07.py +++ b/xlsxwriter/test/comparison/test_quote_name07.py @@ -25,7 +25,9 @@ def test_create_file(self): workbook = Workbook(self.got_filename) - worksheet = workbook.add_worksheet("Sheet'1") + sheet_name = "Sheet'1" + + worksheet = workbook.add_worksheet(sheet_name) chart = workbook.add_chart({"type": "column"}) chart.axis_ids = [48135552, 54701056] @@ -44,9 +46,10 @@ def test_create_file(self): worksheet.set_portrait() worksheet.vertical_dpi = 200 - chart.add_series({"values": ["Sheet'1", 0, 0, 4, 0]}) - chart.add_series({"values": ["Sheet'1", 0, 1, 4, 1]}) - chart.add_series({"values": ["Sheet'1", 0, 2, 4, 2]}) + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) + worksheet.insert_chart("E9", chart) workbook.close() diff --git a/xlsxwriter/test/comparison/test_quote_name08.py b/xlsxwriter/test/comparison/test_quote_name08.py new file mode 100644 index 000000000..0cf30424d --- /dev/null +++ b/xlsxwriter/test/comparison/test_quote_name08.py @@ -0,0 +1,57 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("quote_name08.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file.""" + + workbook = Workbook(self.got_filename) + + sheet_name = "1Sheet" + + worksheet = workbook.add_worksheet(sheet_name) + chart = workbook.add_chart({"type": "column"}) + + chart.axis_ids = [55487104, 84573184] + + data = [ + [1, 2, 3, 4, 5], + [2, 4, 6, 8, 10], + [3, 6, 9, 12, 15], + ] + + worksheet.write_column("A1", data[0]) + worksheet.write_column("B1", data[1]) + worksheet.write_column("C1", data[2]) + + worksheet.repeat_rows(0, 1) + worksheet.set_portrait() + worksheet.vertical_dpi = 200 + + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) + + worksheet.insert_chart("E9", chart) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_quote_name09.py b/xlsxwriter/test/comparison/test_quote_name09.py new file mode 100644 index 000000000..b88ce4708 --- /dev/null +++ b/xlsxwriter/test/comparison/test_quote_name09.py @@ -0,0 +1,57 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("quote_name09.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file.""" + + workbook = Workbook(self.got_filename) + + sheet_name = "Sheet_1" + + worksheet = workbook.add_worksheet(sheet_name) + chart = workbook.add_chart({"type": "column"}) + + chart.axis_ids = [54437760, 59195776] + + data = [ + [1, 2, 3, 4, 5], + [2, 4, 6, 8, 10], + [3, 6, 9, 12, 15], + ] + + worksheet.write_column("A1", data[0]) + worksheet.write_column("B1", data[1]) + worksheet.write_column("C1", data[2]) + + worksheet.repeat_rows(0, 1) + worksheet.set_portrait() + worksheet.vertical_dpi = 200 + + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) + + worksheet.insert_chart("E9", chart) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_quote_name10.py b/xlsxwriter/test/comparison/test_quote_name10.py new file mode 100644 index 000000000..7b0033545 --- /dev/null +++ b/xlsxwriter/test/comparison/test_quote_name10.py @@ -0,0 +1,57 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("quote_name10.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file.""" + + workbook = Workbook(self.got_filename) + + sheet_name = "Sh.eet.1" + + worksheet = workbook.add_worksheet(sheet_name) + chart = workbook.add_chart({"type": "column"}) + + chart.axis_ids = [46905600, 46796800] + + data = [ + [1, 2, 3, 4, 5], + [2, 4, 6, 8, 10], + [3, 6, 9, 12, 15], + ] + + worksheet.write_column("A1", data[0]) + worksheet.write_column("B1", data[1]) + worksheet.write_column("C1", data[2]) + + worksheet.repeat_rows(0, 1) + worksheet.set_portrait() + worksheet.vertical_dpi = 200 + + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) + + worksheet.insert_chart("E9", chart) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/test_quote_name11.py b/xlsxwriter/test/comparison/test_quote_name11.py new file mode 100644 index 000000000..d9486f762 --- /dev/null +++ b/xlsxwriter/test/comparison/test_quote_name11.py @@ -0,0 +1,57 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2023, John McNamara, jmcnamara@cpan.org +# + +from ..excel_comparison_test import ExcelComparisonTest +from ...workbook import Workbook + + +class TestCompareXLSXFiles(ExcelComparisonTest): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + + self.set_filename("quote_name11.xlsx") + + def test_create_file(self): + """Test the creation of a simple XlsxWriter file.""" + + workbook = Workbook(self.got_filename) + + sheet_name = "Sheeté" + + worksheet = workbook.add_worksheet(sheet_name) + chart = workbook.add_chart({"type": "column"}) + + chart.axis_ids = [46720128, 46721664] + + data = [ + [1, 2, 3, 4, 5], + [2, 4, 6, 8, 10], + [3, 6, 9, 12, 15], + ] + + worksheet.write_column("A1", data[0]) + worksheet.write_column("B1", data[1]) + worksheet.write_column("C1", data[2]) + + worksheet.repeat_rows(0, 1) + worksheet.set_portrait() + worksheet.vertical_dpi = 200 + + chart.add_series({"values": [sheet_name, 0, 0, 4, 0]}) + chart.add_series({"values": [sheet_name, 0, 1, 4, 1]}) + chart.add_series({"values": [sheet_name, 0, 2, 4, 2]}) + + worksheet.insert_chart("E9", chart) + + workbook.close() + + self.assertExcelEqual() diff --git a/xlsxwriter/test/comparison/xlsx_files/quote_name08.xlsx b/xlsxwriter/test/comparison/xlsx_files/quote_name08.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..da4328d109b9ec84656670ebecbeea5b5efaa8fa GIT binary patch literal 9420 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 literal 0 HcmV?d00001 diff --git a/xlsxwriter/test/comparison/xlsx_files/quote_name09.xlsx b/xlsxwriter/test/comparison/xlsx_files/quote_name09.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a0310b432f93aa2d27c3aaf200dff44b584c1a41 GIT binary patch literal 9415 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 literal 0 HcmV?d00001 diff --git a/xlsxwriter/test/utility/test_quote_sheetname.py b/xlsxwriter/test/utility/test_quote_sheetname.py new file mode 100644 index 000000000..2ef47fe69 --- /dev/null +++ b/xlsxwriter/test/utility/test_quote_sheetname.py @@ -0,0 +1,157 @@ +############################################################################### +# +# Tests for XlsxWriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (c), 2013-2024, John McNamara, jmcnamara@cpan.org +# + +import unittest +from ...utility import quote_sheetname + + +class TestUtility(unittest.TestCase): + """ + Test xl_cell_to_rowcol_abs() utility function. + + """ + + def test_quote_sheetname(self): + """Test xl_cell_to_rowcol_abs()""" + + # The following unquoted and quoted sheet names were extracted from + # Excel files. + tests = [ + # A sheetname that is already quoted. + ("'Sheet 1'", "'Sheet 1'"), + # ---------------------------------------------------------------- + # Rule 1. + # ---------------------------------------------------------------- + # Some simple variants on standard sheet names. + ("Sheet1", "Sheet1"), + ("Sheet.1", "Sheet.1"), + ("Sheet_1", "Sheet_1"), + ("Sheet-1", "'Sheet-1'"), + ("Sheet 1", "'Sheet 1'"), + ("Sheet#1", "'Sheet#1'"), + ("#Sheet1", "'#Sheet1'"), + # Sheetnames with single quotes. + ("Sheet'1", "'Sheet''1'"), + ("Sheet''1", "'Sheet''''1'"), + # Single special chars that are unquoted in sheetnames. These are + # variants of the first char rule. + ("_", "_"), + (".", "'.'"), + # White space only. + (" ", "' '"), + (" ", "' '"), + # Sheetnames with unicode or emojis. + ("été", "été"), + ("mangé", "mangé"), + ("Sheet😀", "Sheet😀"), + ("Sheet🤌1", "Sheet🤌1"), + ("Sheet⟦1", "'Sheet⟦1'"), # Unicode punctuation. + ("Sheet᠅1", "'Sheet᠅1'"), # Unicode punctuation. + # ---------------------------------------------------------------- + # Rule 2. + # ---------------------------------------------------------------- + # Sheetnames starting with non-word characters. + ("_Sheet1", "_Sheet1"), + (".Sheet1", "'.Sheet1'"), + ("1Sheet1", "'1Sheet1'"), + ("-Sheet1", "'-Sheet1'"), + ("😀Sheet", "'😀Sheet'"), + # Sheetnames that are digits only also start with a non word char. + ("1", "'1'"), + ("2", "'2'"), + ("1234", "'1234'"), + ("12345678", "'12345678'"), + # ---------------------------------------------------------------- + # Rule 3. + # ---------------------------------------------------------------- + # Worksheet names that look like A1 style references (with the + # row/column number in the Excel allowable range). These are case + # insensitive. + ("A0", "A0"), + ("A1", "'A1'"), + ("a1", "'a1'"), + ("XFD", "XFD"), + ("xfd", "xfd"), + ("XFE1", "XFE1"), + ("ZZZ1", "ZZZ1"), + ("XFD1", "'XFD1'"), + ("xfd1", "'xfd1'"), + ("B1048577", "B1048577"), + ("A1048577", "A1048577"), + ("A1048576", "'A1048576'"), + ("B1048576", "'B1048576'"), + ("B1048576a", "B1048576a"), + ("XFD048576", "'XFD048576'"), + ("XFD1048576", "'XFD1048576'"), + ("XFD01048577", "XFD01048577"), + ("XFD01048576", "'XFD01048576'"), + ("A123456789012345678901", "A123456789012345678901"), # Exceeds u64. + # ---------------------------------------------------------------- + # Rule 4. + # ---------------------------------------------------------------- + # Sheet names that *start* with RC style references (with the + # row/column number in the Excel allowable range). These are case + # insensitive. + ("A", "A"), + ("B", "B"), + ("D", "D"), + ("Q", "Q"), + ("S", "S"), + ("c", "'c'"), + ("C", "'C'"), + ("CR", "CR"), + ("CZ", "CZ"), + ("r", "'r'"), + ("R", "'R'"), + ("C8", "'C8'"), + ("rc", "'rc'"), + ("RC", "'RC'"), + ("RCZ", "RCZ"), + ("RRC", "RRC"), + ("R0C0", "R0C0"), + ("R4C", "'R4C'"), + ("R5C", "'R5C'"), + ("rc2", "'rc2'"), + ("RC2", "'RC2'"), + ("RC8", "'RC8'"), + ("bR1C1", "bR1C1"), + ("R1C1", "'R1C1'"), + ("r1c2", "'r1c2'"), + ("rc2z", "'rc2z'"), + ("bR1C1b", "bR1C1b"), + ("R1C1b", "'R1C1b'"), + ("R1C1R", "'R1C1R'"), + ("C16384", "'C16384'"), + ("C16385", "'C16385'"), + ("C16385Z", "C16385Z"), + ("C16386", "'C16386'"), + ("C16384Z", "'C16384Z'"), + ("PC16384Z", "PC16384Z"), + ("RC16383", "'RC16383'"), + ("RC16385Z", "RC16385Z"), + ("R1048576", "'R1048576'"), + ("R1048577C", "R1048577C"), + ("R1C16384", "'R1C16384'"), + ("R1C16385", "'R1C16385'"), + ("RC16384Z", "'RC16384Z'"), + ("R1048576C", "'R1048576C'"), + ("R1048577C1", "R1048577C1"), + ("R1C16384Z", "'R1C16384Z'"), + ("R1048575C1", "'R1048575C1'"), + ("R1048576C1", "'R1048576C1'"), + ("R1048577C16384", "R1048577C16384"), + ("R1048576C16384", "'R1048576C16384'"), + ("R1048576C16385", "'R1048576C16385'"), + ("ZR1048576C16384", "ZR1048576C16384"), + ("C123456789012345678901Z", "C123456789012345678901Z"), # Exceeds u64. + ("R123456789012345678901Z", "R123456789012345678901Z"), # Exceeds u64. + ] + + for sheetname, exp in tests: + got = quote_sheetname(sheetname) + self.assertEqual(got, exp) diff --git a/xlsxwriter/utility.py b/xlsxwriter/utility.py index ee693c828..23acbabb6 100644 --- a/xlsxwriter/utility.py +++ b/xlsxwriter/utility.py @@ -114,10 +114,25 @@ "~": 7, } +# The following is a list of Emojis used to decide if worksheet names require +# quoting since there is (currently) no native support for matching them in +# Python regular expressions. It is probably unnecessary to exclude them since +# the default quoting is safe in Excel even when unnecessary (the reverse isn't +# true). The Emoji list was generated from: +# +# https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AEmoji%3DYes%3A%5D&abb=on&esc=on&g=&i= +# +emojis = "\u00A9\u00AE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299\U0001F004\U0001F0CF\U0001F170\U0001F171\U0001F17E\U0001F17F\U0001F18E\U0001F191-\U0001F19A\U0001F1E6-\U0001F1FF\U0001F201\U0001F202\U0001F21A\U0001F22F\U0001F232-\U0001F23A\U0001F250\U0001F251\U0001F300-\U0001F321\U0001F324-\U0001F393\U0001F396\U0001F397\U0001F399-\U0001F39B\U0001F39E-\U0001F3F0\U0001F3F3-\U0001F3F5\U0001F3F7-\U0001F4FD\U0001F4FF-\U0001F53D\U0001F549-\U0001F54E\U0001F550-\U0001F567\U0001F56F\U0001F570\U0001F573-\U0001F57A\U0001F587\U0001F58A-\U0001F58D\U0001F590\U0001F595\U0001F596\U0001F5A4\U0001F5A5\U0001F5A8\U0001F5B1\U0001F5B2\U0001F5BC\U0001F5C2-\U0001F5C4\U0001F5D1-\U0001F5D3\U0001F5DC-\U0001F5DE\U0001F5E1\U0001F5E3\U0001F5E8\U0001F5EF\U0001F5F3\U0001F5FA-\U0001F64F\U0001F680-\U0001F6C5\U0001F6CB-\U0001F6D2\U0001F6D5-\U0001F6D7\U0001F6DC-\U0001F6E5\U0001F6E9\U0001F6EB\U0001F6EC\U0001F6F0\U0001F6F3-\U0001F6FC\U0001F7E0-\U0001F7EB\U0001F7F0\U0001F90C-\U0001F93A\U0001F93C-\U0001F945\U0001F947-\U0001F9FF\U0001FA70-\U0001FA7C\U0001FA80-\U0001FA88\U0001FA90-\U0001FABD\U0001FABF-\U0001FAC5\U0001FACE-\U0001FADB\U0001FAE0-\U0001FAE8\U0001FAF0-\U0001FAF8" # noqa + # Compile performance critical regular expressions. re_leading = re.compile(r"^\s") re_trailing = re.compile(r"\s$") re_range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)") +re_quote_rule1 = re.compile(rf"[^\w\.{emojis}]") +re_quote_rule2 = re.compile(rf"^[\d\.{emojis}]") +re_quote_rule3 = re.compile(r"^([A-Z]{1,3}\d+)$") +re_quote_rule4_row = re.compile(r"^R(\d+)") +re_quote_rule4_column = re.compile(r"^R?C(\d+)") def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False): @@ -368,8 +383,9 @@ def xl_range_formula(sheetname, first_row, first_col, last_row, last_col): def quote_sheetname(sheetname): """ - Convert a worksheet name to a quoted name if it contains spaces or - special characters. + Sheetnames used in references should be quoted if they contain any spaces, + special characters or if they look like a A1 or RC cell reference. The rules + are shown inline below. Args: sheetname: The worksheet name. String. @@ -378,8 +394,72 @@ def quote_sheetname(sheetname): A quoted worksheet string. """ - - if not sheetname.isalnum() and not sheetname.startswith("'"): + uppercase_sheetname = sheetname.upper() + requires_quoting = False + col_max = 163_84 + row_max = 1048576 + + # Don't quote sheetname if it is already quoted by the user. + if not sheetname.startswith("'"): + + # -------------------------------------------------------------------- + # Rule 1. Sheet names that contain anything other than \w and "." + # characters must be quoted. + # -------------------------------------------------------------------- + if re_quote_rule1.search(sheetname): + requires_quoting = True + + # -------------------------------------------------------------------- + # Rule 2. Sheet names that start with a digit or "." must be quoted. + # -------------------------------------------------------------------- + elif re_quote_rule2.search(sheetname): + requires_quoting = True + + # -------------------------------------------------------------------- + # Rule 3. Sheet names must not be a valid A1 style cell reference. + # Valid means that the row and column range values must also be within + # Excel row and column limits. + # -------------------------------------------------------------------- + elif re_quote_rule3.match(uppercase_sheetname): + match = re_quote_rule3.match(uppercase_sheetname) + cell = match.group(1) + (row, col) = xl_cell_to_rowcol(cell) + + if row >= 0 and row < row_max and col >= 0 and col < col_max: + requires_quoting = True + + # -------------------------------------------------------------------- + # Rule 4. Sheet names must not *start* with a valid RC style cell + # reference. Other characters after the valid RC reference are ignored + # by Excel. Valid means that the row and column range values must also + # be within Excel row and column limits. + # + # Note: references without trailing characters like R12345 or C12345 + # are caught by Rule 3. Negative references like R-12345 are caught by + # Rule 1 due to dash. + # -------------------------------------------------------------------- + + # Rule 4a. Check for sheet names that start with R1 style references. + elif re_quote_rule4_row.match(uppercase_sheetname): + match = re_quote_rule4_row.match(uppercase_sheetname) + row = int(match.group(1)) + + if row > 0 and row <= row_max: + requires_quoting = True + + # Rule 4b. Check for sheet names that start with C1 or RC1 style + elif re_quote_rule4_column.match(uppercase_sheetname): + match = re_quote_rule4_column.match(uppercase_sheetname) + col = int(match.group(1)) + + if col > 0 and col <= col_max: + requires_quoting = True + + # Rule 4c. Check for some single R/C references. + elif uppercase_sheetname in ("R", "C", "RC"): + requires_quoting = True + + if requires_quoting: # Double quote any single quotes. sheetname = sheetname.replace("'", "''")