Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions hyphen.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Text hyphenation in Javascript.
* Copyright (C) 2024 Yevhen Tiurin (yevhentiurin@gmail.com)
* Copyright (C) 2025 Yevhen Tiurin (yevhentiurin@gmail.com)
* https://github.com/ytiurin/hyphen
*
* Released under the ISC license
Expand Down Expand Up @@ -202,6 +202,31 @@
return hyphenatedText;
}

function getObjectPropertyCaseInsensitive(obj, key) {
const lowerKey = key.toLowerCase();
for (const prop in obj) {
if (prop.toLowerCase() === lowerKey) {
return obj[prop];
}
}
return void 0;
}
function addHyphenMarkersFromExisting(existing, newVariant, hyphenChar) {
let result = "";
let newVariantIndex = 0;
let i = 0;
while (i < existing.length) {
if (existing.slice(i, i + hyphenChar.length) === hyphenChar) {
result += hyphenChar;
i += hyphenChar.length;
} else {
result += newVariant[newVariantIndex] || "";
newVariantIndex++;
i++;
}
}
return result;
}
function start(
text,
levelsTable,
Expand All @@ -211,7 +236,8 @@
hyphenChar,
skipHTML,
minWordLength,
isAsync
isAsync,
caseInsensitiveMode
) {
function done() {
resolveNewText(newText);
Expand All @@ -235,7 +261,21 @@
) {
if (fragments[1]) {
var cacheKey = fragments[1].length ? "~" + fragments[1] : "";
if (cache[cacheKey] === void 0) {
if (
caseInsensitiveMode &&
cache[cacheKey] === void 0 &&
getObjectPropertyCaseInsensitive(cache, cacheKey)
) {
const existingCachedValue = getObjectPropertyCaseInsensitive(
cache,
cacheKey
);
cache[cacheKey] = addHyphenMarkersFromExisting(
existingCachedValue,
fragments[1],
hyphenChar
);
} else if (cache[cacheKey] === void 0) {
cache[cacheKey] = hyphenateWord(
fragments[1],
levelsTable,
Expand Down Expand Up @@ -271,12 +311,14 @@
var SETTING_DEFAULT_HTML = true;
var SETTING_DEFAULT_HYPH_CHAR = "\xAD";
var SETTING_DEFAULT_MIN_WORD_LENGTH = 5;
var SETTING_DEFAULT_CASE_INSENSITIVE = false;
var SETTING_NAME_ASYNC = "async";
var SETTING_NAME_DEBUG = "debug";
var SETTING_NAME_EXCEPTIONS = "exceptions";
var SETTING_NAME_HTML = "html";
var SETTING_NAME_HYPH_CHAR = "hyphenChar";
var SETTING_NAME_MIN_WORD_LENGTH = "minWordLength";
var SETTING_NAME_CASE_INSENSITIVE = "caseInsensitive";
var _global =
typeof global === "object"
? global
Expand Down Expand Up @@ -341,6 +383,11 @@
SETTING_NAME_EXCEPTIONS,
SETTING_DEFAULT_EXCEPTIONS,
validateArray
),
caseInsensitiveMode = keyOrDefault(
options,
SETTING_NAME_CASE_INSENSITIVE,
SETTING_DEFAULT_CASE_INSENSITIVE
);
var cacheKey = hyphenChar + minWordLength;
exceptions[cacheKey] = {};
Expand Down Expand Up @@ -403,7 +450,8 @@
localHyphenChar,
skipHTML,
localMinWordLength,
asyncMode
asyncMode,
caseInsensitiveMode
);
};
}
Expand Down
13 changes: 11 additions & 2 deletions src/create-hyphenator.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ var SETTING_DEFAULT_ASYNC = false,
SETTING_DEFAULT_HTML = true,
SETTING_DEFAULT_HYPH_CHAR = "\u00AD",
SETTING_DEFAULT_MIN_WORD_LENGTH = 5,
SETTING_DEFAULT_CASE_INSENSITIVE = false,
SETTING_NAME_ASYNC = "async",
SETTING_NAME_DEBUG = "debug",
SETTING_NAME_EXCEPTIONS = "exceptions",
SETTING_NAME_HTML = "html",
SETTING_NAME_HYPH_CHAR = "hyphenChar",
SETTING_NAME_MIN_WORD_LENGTH = "minWordLength";
SETTING_NAME_MIN_WORD_LENGTH = "minWordLength",
SETTING_NAME_CASE_INSENSITIVE = "caseInsensitive";


var _global =
typeof global === "object"
Expand Down Expand Up @@ -82,6 +85,11 @@ export function createHyphenator(patternsDefinition, options) {
SETTING_NAME_EXCEPTIONS,
SETTING_DEFAULT_EXCEPTIONS,
validateArray
),
caseInsensitiveMode = keyOrDefault(
options,
SETTING_NAME_CASE_INSENSITIVE,
SETTING_DEFAULT_CASE_INSENSITIVE
);

// Prepare cache
Expand Down Expand Up @@ -156,7 +164,8 @@ export function createHyphenator(patternsDefinition, options) {
localHyphenChar,
skipHTML,
localMinWordLength,
asyncMode
asyncMode,
caseInsensitiveMode
);
};
}
39 changes: 36 additions & 3 deletions src/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,35 @@ import {
} from "./hyphenationVerifier.js";
import { hyphenateWord } from "./hyphenate-word.js";

function getObjectPropertyCaseInsensitive(obj, key) {
const lowerKey = key.toLowerCase();
for (const prop in obj) {
if (prop.toLowerCase() === lowerKey) {
return obj[prop];
}
}
return undefined;
}

function addHyphenMarkersFromExisting(existing, newVariant, hyphenChar) {
let result = '';
let newVariantIndex = 0;
let i = 0;

while (i < existing.length) {
// Check if the next chunk matches the separator
if (existing.slice(i, i + hyphenChar.length) === hyphenChar) {
result += hyphenChar;
i += hyphenChar.length;
} else {
result += newVariant[newVariantIndex] || '';
newVariantIndex++;
i++;
}
}
return result;
}

export function start(
text,
levelsTable,
Expand All @@ -15,7 +44,8 @@ export function start(
hyphenChar,
skipHTML,
minWordLength,
isAsync
isAsync,
caseInsensitiveMode
) {
function done() {
DEV: allTime = new Date() - allTime;
Expand Down Expand Up @@ -56,15 +86,18 @@ export function start(

function nextTick() {
var loopStart = new Date();

while (
(!isAsync || new Date() - loopStart < 10) &&
(fragments = readText(text))
) {
if (fragments[1]) {
var cacheKey = fragments[1].length ? "~" + fragments[1] : "";

if (cache[cacheKey] === undefined) {
if (caseInsensitiveMode && cache[cacheKey] === undefined && getObjectPropertyCaseInsensitive(cache, cacheKey)) {
// Use existing cached hyphenate rule for different case variant
const existingCachedValue = getObjectPropertyCaseInsensitive(cache, cacheKey);
cache[cacheKey] = addHyphenMarkersFromExisting(existingCachedValue, fragments[1], hyphenChar);
} else if (cache[cacheKey] === undefined) {
cache[cacheKey] = hyphenateWord(
fragments[1],
levelsTable,
Expand Down
63 changes: 62 additions & 1 deletion tests/user-exceptions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const patterns = require("../patterns/en-us.js");

let hyphenate;

beforeAll(() => {
beforeEach(() => {
hyphenate = createHyphenator(patterns, {
hyphenChar: "-",
exceptions: ["h-e-l-l-o"]
Expand Down Expand Up @@ -48,4 +48,65 @@ describe("User exceptions", () => {

expect(hyphenate(text)).toBe(predictable);
});

test("Factory function option is case sensitive by default", () => {
const textLower = "hello";
const textCapitalized = "Hello";
const textUpper = "HELLO"
const predictableLower = "h-e-l-l-o";
const predictableCapitalized = "Hel-lo";
const predictableUPPER = "HEL-LO";
expect(hyphenate(textLower)).toBe(predictableLower);
expect(hyphenate(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenate(textUpper)).toBe(predictableUPPER);
expect(hyphenate(textLower)).toBe(predictableLower);
expect(hyphenate(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenate(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenate(textUpper)).toBe(predictableUPPER);
expect(hyphenate(textLower)).toBe(predictableLower);
});

test("Factory function option works case insensitive", () => {
const hyphenateWithCaseInsenstive = createHyphenator(patterns, {
hyphenChar: "-",
exceptions: ["h-e-l-l-o"],
caseInsensitive: true,
});
const textLower = "hello";
const textCapitalized = "Hello";
const textUpper = "HELLO"
const predictableLower = "h-e-l-l-o";
const predictableCapitalized = "H-e-l-l-o";
const predictableUPPER = "H-E-L-L-O";
expect(hyphenateWithCaseInsenstive(textLower)).toBe(predictableLower);
expect(hyphenateWithCaseInsenstive(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithCaseInsenstive(textUpper)).toBe(predictableUPPER);
expect(hyphenateWithCaseInsenstive(textLower)).toBe(predictableLower);
expect(hyphenateWithCaseInsenstive(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithCaseInsenstive(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithCaseInsenstive(textUpper)).toBe(predictableUPPER);
expect(hyphenateWithCaseInsenstive(textLower)).toBe(predictableLower);
});

test("Factory function option works case insensitive and case insenstive works with multi character hyphens", () => {
const hyphenateWithMultiCharHyphen = createHyphenator(patterns, {
hyphenChar: "- ",
exceptions: ["h-e-l-l-o"],
caseInsensitive: true,
});
const textLower = "hello";
const textCapitalized = "Hello";
const textUpper = "HELLO"
const predictableLower = "h- e- l- l- o";
const predictableCapitalized = "H- e- l- l- o";
const predictableUPPER = "H- E- L- L- O";
expect(hyphenateWithMultiCharHyphen(textLower)).toBe(predictableLower);
expect(hyphenateWithMultiCharHyphen(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithMultiCharHyphen(textUpper)).toBe(predictableUPPER);
expect(hyphenateWithMultiCharHyphen(textLower)).toBe(predictableLower);
expect(hyphenateWithMultiCharHyphen(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithMultiCharHyphen(textCapitalized)).toBe(predictableCapitalized);
expect(hyphenateWithMultiCharHyphen(textUpper)).toBe(predictableUPPER);
expect(hyphenateWithMultiCharHyphen(textLower)).toBe(predictableLower);
});
});