Skip to content

Commit 2a9f7e8

Browse files
Merge pull request #24 from MaksymStoianov/max/next
Add comprehensive test suite, examples and new functions
2 parents 7c684d8 + 92ec802 commit 2a9f7e8

15 files changed

+615
-79
lines changed

README.md

Lines changed: 51 additions & 46 deletions
Large diffs are not rendered by default.
368 KB
Loading
-275 KB
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { IllegalArgumentException } from "../../exception";
2+
import { isString, requireNonEmptyString } from "../../lang";
3+
import { parseA1Notation } from "./parseA1Notation";
4+
5+
/**
6+
* ## extractRangeFromA1Notation
7+
*
8+
* Extracts the **range part** from a full A1 notation string (e.g., `'Sheet1!A1:B2'` returns `'A1:B2'`).
9+
*
10+
* @param {string} a1Notation - The A1 notation string (e.g., `'SheetName!A1:B2'`, `'A1:B2'`).
11+
* @returns {string|null} The extracted range string (e.g., `'A1:B2'`), or `null`.
12+
* @throws {Error}
13+
* @throws {IllegalArgumentException}
14+
* @see {@link parseA1Notation}
15+
* @see {@link GridRange}
16+
* @see {@link GoogleAppsScript.Spreadsheet.Range|Range}
17+
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
18+
* @see [Class Range](https://developers.google.com/apps-script/reference/spreadsheet/range)
19+
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
20+
* @since 1.6.0
21+
* @version 1.0.0
22+
* @environment `Google Apps Script`, `Browser`
23+
* @author Maksym Stoianov <stoianov.maksym@gmail.com>
24+
* @license Apache-2.0
25+
*/
26+
export function extractRangeFromA1Notation(a1Notation: string): string | null {
27+
if (arguments.length === 0) {
28+
throw new IllegalArgumentException();
29+
}
30+
31+
const { a1Notation: range } = parseA1Notation(a1Notation);
32+
33+
if (!isString(range)) {
34+
return null;
35+
}
36+
37+
requireNonEmptyString(range);
38+
39+
return range;
40+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { IllegalArgumentException } from "../../exception";
2+
import { isString } from "../../lang";
3+
import { isValidSheetName } from "./isValidSheetName";
4+
import { parseA1Notation } from "./parseA1Notation";
5+
6+
/**
7+
* ## extractSheetNameFromA1Notation
8+
*
9+
* Extracts the sheet name from an A1 notation string (e.g., `Sheet1!A1:B2`).
10+
*
11+
* This function returns the sheet name as a clean string, or `null` if the
12+
* notation contains only the range part (e.g., `A1:B2`).
13+
*
14+
* @param {string} a1Notation - The A1 notation string (e.g., `SheetName!A1`, `Sheet Name'!A:A`).
15+
* @returns {string|null} The extracted sheet name, or `null` if no sheet name is present in the notation.
16+
* @throws {Error}
17+
* @throws {IllegalArgumentException}
18+
* @see {@link parseA1Notation}
19+
* @see {@link GridRange}
20+
* @see {@link GoogleAppsScript.Spreadsheet.Range|Range}
21+
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
22+
* @see [Class Range](https://developers.google.com/apps-script/reference/spreadsheet/range)
23+
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
24+
* @since 1.6.0
25+
* @version 1.0.0
26+
* @environment `Google Apps Script`, `Browser`
27+
* @author Maksym Stoianov <stoianov.maksym@gmail.com>
28+
* @license Apache-2.0
29+
*/
30+
export function extractSheetNameFromA1Notation(
31+
a1Notation: string
32+
): string | null {
33+
if (arguments.length === 0) {
34+
throw new IllegalArgumentException();
35+
}
36+
37+
const { sheetName } = parseA1Notation(a1Notation);
38+
39+
if (!isString(sheetName)) {
40+
return null;
41+
}
42+
43+
if (!isValidSheetName(sheetName)) {
44+
throw new Error("Expected a valid sheet name");
45+
}
46+
47+
return sheetName;
48+
}

src/appsscript/sheet/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export { appendRow } from "./appendRow";
4747
export { appendRows } from "./appendRows";
4848
export { convertRichTextToHtml } from "./convertRichTextToHtml";
4949
export { doGridRangesIntersect } from "./doGridRangesIntersect";
50+
export { extractRangeFromA1Notation } from "./extractRangeFromA1Notation";
51+
export { extractSheetNameFromA1Notation } from "./extractSheetNameFromA1Notation";
5052
export { getColumnIndexByLetter } from "./getColumnIndexByLetter";
5153
export { getColumnLetterByIndex } from "./getColumnLetterByIndex";
5254
export { getSheetById } from "./getSheetById";
@@ -64,5 +66,10 @@ export { highlightHtml } from "./highlightHtml";
6466
export { sortSheets } from "./sortSheets";
6567

6668
export { parseA1Notation } from "./parseA1Notation";
69+
export {
70+
parseA1Notations,
71+
type A1NotationParseOptions
72+
} from "./parseA1Notations";
6773

6874
export { toA1Notation } from "./toA1Notation";
75+
export { updateSheetNameInA1Notation } from "./updateSheetNameInA1Notation";

src/appsscript/sheet/isValidSheetName.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty, isString } from "../../lang";
1+
import { isString, nonEmpty } from "../../lang";
22

33
/**
44
* ## isValidSheetName
@@ -11,9 +11,9 @@ import { isEmpty, isString } from "../../lang";
1111
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
1212
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
1313
* @since 1.0.0
14-
* @version 1.0.0
14+
* @version 1.0.1
1515
* @environment `Google Apps Script`, `Browser`
1616
*/
1717
export function isValidSheetName(value: unknown): value is string {
18-
return isString(value) && !isEmpty(value);
18+
return isString(value) && nonEmpty(value);
1919
}

src/appsscript/sheet/parseA1Notation.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,17 @@ import type { GridRange } from "./types";
3333
* parseA1Notation("'Sheet name'!A1:B2");
3434
* ```
3535
*
36-
* @param a1Notation - The A1 notation string to parse.
37-
* @returns An object representing the parsed grid range.
36+
* @param {string} a1Notation - The A1 notation string to parse.
37+
* @returns {GridRange} An object representing the parsed grid range.
3838
* @throws {@link IllegalArgumentException}
39+
* @see {@link parseA1Notations}
3940
* @see {@link GridRange}
4041
* @see {@link GoogleAppsScript.Spreadsheet.Range|Range}
4142
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
4243
* @see [Class Range](https://developers.google.com/apps-script/reference/spreadsheet/range)
4344
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
4445
* @since 1.0.0
45-
* @version 1.0.1
46+
* @version 1.1.0
4647
* @environment `Google Apps Script`, `Browser`
4748
* @author Maksym Stoianov <stoianov.maksym@gmail.com>
4849
* @license Apache-2.0
@@ -52,7 +53,7 @@ export function parseA1Notation(a1Notation: string): GridRange {
5253
throw new IllegalArgumentException();
5354
}
5455

55-
const trimmedInput = requireNonEmptyString(a1Notation.trim());
56+
const trimmedInput = requireNonEmptyString(a1Notation).trim();
5657

5758
let sheetName: string | null = null;
5859
let rangeA1Part: string;
@@ -268,32 +269,12 @@ export function parseA1Notation(a1Notation: string): GridRange {
268269
throw new SyntaxError(`"${a1Notation}" is not a valid A1 notation.`);
269270
}
270271

271-
let fullCanonicalA1Notation = "";
272-
if (sheetName !== null) {
273-
const isSimpleSheetName = /^[A-Z_][A-Z0-9_]*$/i.test(sheetName);
274-
275-
if (!isSimpleSheetName) {
276-
if (!sheetName.includes("'")) {
277-
fullCanonicalA1Notation += `'${sheetName}'!`;
278-
} else if (!sheetName.includes('"')) {
279-
fullCanonicalA1Notation += `"${sheetName}"!`;
280-
} else {
281-
fullCanonicalA1Notation += `'${sheetName.replace(/'/g, "''")}'!`;
282-
}
283-
} else {
284-
fullCanonicalA1Notation += `${sheetName}!`;
285-
}
286-
}
287-
fullCanonicalA1Notation += canonicalA1Notation;
288-
289-
const result: GridRange = {
272+
return {
290273
sheetName,
291-
a1Notation: fullCanonicalA1Notation,
274+
a1Notation: canonicalA1Notation,
292275
startRowIndex: currentStartRowIndex,
293276
endRowIndex: finalEndRowIndex,
294277
startColumnIndex: currentStartColumnIndex,
295278
endColumnIndex: finalEndColumnIndex
296279
};
297-
298-
return result;
299280
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { IllegalArgumentException } from "../../exception";
2+
import { isObject, requireNonEmptyString } from "../../lang";
3+
import { parseA1Notation } from "./parseA1Notation";
4+
import { GridRange } from "./types";
5+
6+
export interface A1NotationParseOptions {
7+
includeSheetNames?: boolean;
8+
}
9+
10+
/**
11+
* ## parseA1Notations
12+
*
13+
* Parses a comma-separated string of A1 notations into an array of {@link GridRange} objects.
14+
* Validates the format and sheet name requirements based on the provided options.
15+
*
16+
* @param {string} value - The input string to be parsed.
17+
* @param {A1NotationParseOptions} options - An object with options for parsing.
18+
* @returns {GridRange[]} An array of {@link GridRange} objects.
19+
* @throws {@link Error}
20+
* @throws {@link IllegalArgumentException}
21+
* @see {@link parseA1Notation}
22+
* @see {@link GridRange}
23+
* @see {@link GoogleAppsScript.Spreadsheet.Range|Range}
24+
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
25+
* @see [Class Range](https://developers.google.com/apps-script/reference/spreadsheet/range)
26+
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
27+
* @since 1.6.0
28+
* @version 1.0.0
29+
* @environment `Google Apps Script`, `Browser`
30+
* @author Maksym Stoianov <stoianov.maksym@gmail.com>
31+
* @license Apache-2.0
32+
*/
33+
export function parseA1Notations(
34+
value: string,
35+
{ includeSheetNames }: A1NotationParseOptions
36+
): GridRange[] {
37+
if (arguments.length === 0) {
38+
throw new IllegalArgumentException();
39+
}
40+
41+
const trimmedInput = requireNonEmptyString(value).trim();
42+
43+
if (trimmedInput === "") {
44+
return [];
45+
}
46+
47+
const ranges = trimmedInput
48+
.split(",")
49+
.map(item => item.trim())
50+
.filter(Boolean)
51+
.map(parseA1Notation);
52+
53+
if (!ranges.every(isObject)) {
54+
throw new Error("One or more values are not valid ranges.");
55+
}
56+
57+
for (const range of ranges) {
58+
if (includeSheetNames === true && !range.sheetName) {
59+
throw new Error(
60+
`Missing sheet name in "${range.a1Notation}". Ranges must include a sheet name.`
61+
);
62+
}
63+
64+
if (includeSheetNames === false && range.sheetName) {
65+
throw new Error(
66+
`Sheet names are not allowed. Found a sheet name in "${range.a1Notation}".`
67+
);
68+
}
69+
}
70+
71+
return ranges;
72+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { IllegalArgumentException } from "../../exception";
2+
import { requireNonEmptyString } from "../../lang";
3+
import { isValidSheetName } from "./isValidSheetName";
4+
import { parseA1Notation } from "./parseA1Notation";
5+
6+
/**
7+
* ## updateSheetNameInA1Notation
8+
*
9+
* Updates or sets the sheet name within an A1 notation string, while preserving the range information (e.g., `A1:B2`).
10+
*
11+
* @param {string} a1Notation - The source A1 notation, which may or may not include a sheet name (e.g., `'Sheet Name'!A1:B2` or `A1:B2`).
12+
* @param {string|null} [sheetName] - The new sheet name to set.
13+
* Pass `null` or `undefined` to remove any existing sheet name and return only the range.
14+
* @returns {string} The new A1 notation string with the updated sheet name (e.g., `'New Sheet'!A1:B2` or just `A1:B2`).
15+
* @throws {@link IllegalArgumentException}
16+
* @throws {@link EmptyStringException}
17+
* @see {@link parseA1Notation}
18+
* @see {@link GridRange}
19+
* @see {@link GoogleAppsScript.Spreadsheet.Range|Range}
20+
* @see {@link GoogleAppsScript.Spreadsheet.Sheet|Sheet}
21+
* @see [Class Range](https://developers.google.com/apps-script/reference/spreadsheet/range)
22+
* @see [Class Sheet](https://developers.google.com/apps-script/reference/spreadsheet/sheet)
23+
* @since 1.6.0
24+
* @version 1.0.0
25+
* @environment `Google Apps Script`, `Browser`
26+
* @author Maksym Stoianov <stoianov.maksym@gmail.com>
27+
* @license Apache-2.0
28+
*/
29+
export function updateSheetNameInA1Notation(
30+
a1Notation: string,
31+
sheetName?: string | null
32+
): string {
33+
if (arguments.length === 0) {
34+
throw new IllegalArgumentException();
35+
}
36+
37+
const gridRange = parseA1Notation(a1Notation);
38+
39+
const range = requireNonEmptyString(gridRange.a1Notation);
40+
41+
if (!isValidSheetName(sheetName)) {
42+
return range;
43+
}
44+
45+
const isSimpleSheetName = /^[A-Z_][A-Z0-9_]*$/i.test(sheetName);
46+
47+
if (isSimpleSheetName) {
48+
return `${sheetName}!${range}`;
49+
}
50+
51+
if (!sheetName.includes("'")) {
52+
return `'${sheetName}'!${range}`;
53+
}
54+
55+
if (!sheetName.includes('"')) {
56+
return `"${sheetName}"!${range}`;
57+
}
58+
59+
return `'${sheetName.replace(/'/g, "''")}'!${range}`;
60+
}

0 commit comments

Comments
 (0)