完成世界书、骰子、apiconfig页面处理

This commit is contained in:
2026-04-30 01:35:10 +08:00
parent a3e3711b2b
commit ba9b925c32
4602 changed files with 785225 additions and 23 deletions

View File

@@ -0,0 +1,32 @@
/** Joiner in emoji sequences */
declare const joinerEmoji = 8205;
/** Emoji as icon */
declare const vs16Emoji = 65039;
/** Keycap, preceeded by mandatory VS16 for full emoji */
declare const keycapEmoji = 8419;
/**
* Variations, UTF-32
*
* First value in array is minimum, second value is maximum+1
*/
type EmojiComponentType = 'skin-tone' | 'hair-style';
type Range = [number, number];
declare const emojiComponents: Record<EmojiComponentType, Range>;
/**
* Minimum UTF-32 number
*/
declare const minUTF32 = 65536;
/**
* Codes for UTF-32 characters presented as UTF-16
*
* startUTF32Pair1 <= code < startUTF32Pair2 -> code for first character in pair
* startUTF32Pair2 <= code < endUTF32Pair -> code for second character in pair
*/
declare const startUTF32Pair1 = 55296;
declare const startUTF32Pair2 = 56320;
declare const endUTF32Pair = 57344;
/**
* Emoji version as string
*/
declare const emojiVersion = "17.0";
export { EmojiComponentType, emojiComponents, emojiVersion, endUTF32Pair, joinerEmoji, keycapEmoji, minUTF32, startUTF32Pair1, startUTF32Pair2, vs16Emoji };

View File

@@ -0,0 +1,32 @@
import { IconifyJSON } from "@iconify/types";
/** Parsed icon */
interface PreparedEmojiIcon {
/** Icon name */
icon: string;
/** Emoji sequence as string */
sequence: string;
}
/**
* Parse
*/
interface PreparedEmojiResult {
/** List of icons */
icons: PreparedEmojiIcon[];
/** Regular expression */
regex: string;
}
/**
* Prepare emoji for icons list
*
* Test data should be fetched from 'https://unicode.org/Public/emoji/17.0/emoji-test.txt'
* It is used to detect missing emojis and optimise regular expression
*/
declare function prepareEmojiForIconsList(icons: Record<string, string>, rawTestData?: string): PreparedEmojiResult;
/**
* Prepare emoji for an icon set
*
* Test data should be fetched from 'https://unicode.org/Public/emoji/15.1/emoji-test.txt'
* It is used to detect missing emojis and optimise regular expression
*/
declare function prepareEmojiForIconSet(iconSet: IconifyJSON, rawTestData?: string): PreparedEmojiResult;
export { PreparedEmojiIcon, PreparedEmojiResult, prepareEmojiForIconSet, prepareEmojiForIconsList };

View File

@@ -0,0 +1,74 @@
/**
* Regex in item
*/
interface BaseEmojiItemRegex {
type: 'utf16' | 'sequence' | 'set' | 'optional';
regex: string;
group: boolean;
length: number;
}
interface EmojiItemRegexWithNumbers {
numbers?: number[];
}
interface UTF16EmojiItemRegex extends BaseEmojiItemRegex, Required<EmojiItemRegexWithNumbers> {
type: 'utf16';
group: true;
}
type SequenceEmojiItemRegexItem = UTF16EmojiItemRegex | SetEmojiItemRegex | OptionalEmojiItemRegex;
interface SequenceEmojiItemRegex extends BaseEmojiItemRegex, EmojiItemRegexWithNumbers {
type: 'sequence';
items: SequenceEmojiItemRegexItem[];
}
type SetEmojiItemRegexItem = UTF16EmojiItemRegex | SequenceEmojiItemRegex | OptionalEmojiItemRegex;
interface SetEmojiItemRegex extends BaseEmojiItemRegex, EmojiItemRegexWithNumbers {
type: 'set';
sets: SetEmojiItemRegexItem[];
}
type OptionalEmojiItemRegexItem = UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex;
interface OptionalEmojiItemRegex extends BaseEmojiItemRegex {
type: 'optional';
item: OptionalEmojiItemRegexItem;
group: true;
}
type EmojiItemRegex = UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex | OptionalEmojiItemRegex;
/**
* Wrap regex in group
*/
declare function wrapRegexInGroup(regex: string): string;
/**
* Update UTF16 item, return regex
*/
declare function updateUTF16EmojiRegexItem(item: UTF16EmojiItemRegex): string;
/**
* Create UTF-16 regex
*/
declare function createUTF16EmojiRegexItem(numbers: number[]): UTF16EmojiItemRegex;
/**
* Update sequence regex. Does not update group
*/
declare function updateSequenceEmojiRegexItem(item: SequenceEmojiItemRegex): string;
/**
* Create sequence regex
*/
declare function createSequenceEmojiRegexItem(sequence: EmojiItemRegex[], numbers?: number[]): SequenceEmojiItemRegex;
/**
* Update set regex and group
*/
declare function updateSetEmojiRegexItem(item: SetEmojiItemRegex): string;
/**
* Create set regex
*/
declare function createSetEmojiRegexItem(set: EmojiItemRegex[]): SetEmojiItemRegex;
/**
* Update optional regex
*/
declare function updateOptionalEmojiRegexItem(item: OptionalEmojiItemRegex): string;
/**
* Create optional item
*/
declare function createOptionalEmojiRegexItem(item: EmojiItemRegex): OptionalEmojiItemRegex;
/**
* Clone item
*/
declare function cloneEmojiRegexItem<T extends BaseEmojiItemRegex>(item: T, shallow?: boolean): T;
export { EmojiItemRegex, OptionalEmojiItemRegex, SequenceEmojiItemRegex, SetEmojiItemRegex, SetEmojiItemRegexItem, UTF16EmojiItemRegex, cloneEmojiRegexItem, createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem, updateOptionalEmojiRegexItem, updateSequenceEmojiRegexItem, updateSetEmojiRegexItem, updateUTF16EmojiRegexItem, wrapRegexInGroup };

View File

@@ -0,0 +1,204 @@
/**
* Convert number to string
*/
function toString(number) {
if (number < 255) {
if (number > 32 && number < 127) {
const char = String.fromCharCode(number);
if (number > 47 && number < 58 || number > 64 && number < 91 || number > 94 && number < 123) return char;
return "\\" + char;
}
return "\\x" + (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
}
return "\\u" + number.toString(16).toUpperCase();
}
/**
* Typescript stuff
*/
function assertNever(v) {}
/**
* Wrap regex in group
*/
function wrapRegexInGroup(regex) {
return "(?:" + regex + ")";
}
/**
* Update UTF16 item, return regex
*/
function updateUTF16EmojiRegexItem(item) {
const numbers = item.numbers;
if (numbers.length === 1) {
const num = numbers[0];
return item.regex = toString(num);
}
numbers.sort((a, b) => a - b);
const chars = [];
let range = null;
const addRange = () => {
if (range) {
const { start, last, numbers: numbers$1 } = range;
range = null;
if (last > start + 1) chars.push(toString(start) + "-" + toString(last));
else for (let i = 0; i < numbers$1.length; i++) chars.push(toString(numbers$1[i]));
}
};
for (let i = 0; i < numbers.length; i++) {
const num = numbers[i];
if (range) {
if (range.last === num) continue;
if (range.last === num - 1) {
range.numbers.push(num);
range.last = num;
continue;
}
}
addRange();
range = {
start: num,
last: num,
numbers: [num]
};
}
addRange();
if (!chars.length) throw new Error("Unexpected empty range");
return item.regex = "[" + chars.join("") + "]";
}
/**
* Create UTF-16 regex
*/
function createUTF16EmojiRegexItem(numbers) {
const result = {
type: "utf16",
regex: "",
numbers,
length: 1,
group: true
};
updateUTF16EmojiRegexItem(result);
return result;
}
/**
* Update sequence regex. Does not update group
*/
function updateSequenceEmojiRegexItem(item) {
return item.regex = item.items.map((childItem) => {
if (!childItem.group && childItem.type === "set") return wrapRegexInGroup(childItem.regex);
return childItem.regex;
}).join("");
}
/**
* Create sequence regex
*/
function createSequenceEmojiRegexItem(sequence, numbers) {
let items = [];
sequence.forEach((item) => {
if (item.type === "sequence") items = items.concat(item.items);
else items.push(item);
});
if (!items.length) throw new Error("Empty sequence");
const result = {
type: "sequence",
items,
regex: "",
length: items.reduce((length, item) => item.length + length, 0),
group: false
};
if (sequence.length === 1) {
const firstItem = sequence[0];
result.group = firstItem.group;
if (firstItem.type !== "optional") {
const numbers$1 = firstItem.numbers;
if (numbers$1) result.numbers = numbers$1;
}
}
if (numbers) result.numbers = numbers;
updateSequenceEmojiRegexItem(result);
return result;
}
/**
* Update set regex and group
*/
function updateSetEmojiRegexItem(item) {
if (item.sets.length === 1) {
const firstItem = item.sets[0];
item.group = firstItem.group;
return item.regex = firstItem.regex;
}
item.group = false;
return item.regex = item.sets.map((childItem) => childItem.regex).join("|");
}
/**
* Create set regex
*/
function createSetEmojiRegexItem(set) {
let sets = [];
let numbers = [];
set.forEach((item) => {
if (item.type === "set") sets = sets.concat(item.sets);
else sets.push(item);
if (numbers) if (item.type === "optional" || !item.numbers) numbers = null;
else numbers = [...numbers, ...item.numbers];
});
sets.sort((a, b) => {
if (a.length === b.length) return a.regex.localeCompare(b.regex);
return b.length - a.length;
});
const result = {
type: "set",
sets,
regex: "",
length: sets.reduce((length, item) => length ? Math.min(length, item.length) : item.length, 0),
group: false
};
if (numbers) result.numbers = numbers;
if (set.length === 1) result.group = set[0].group;
updateSetEmojiRegexItem(result);
return result;
}
/**
* Update optional regex
*/
function updateOptionalEmojiRegexItem(item) {
const childItem = item.item;
return item.regex = (childItem.group ? childItem.regex : wrapRegexInGroup(childItem.regex)) + "?";
}
/**
* Create optional item
*/
function createOptionalEmojiRegexItem(item) {
if (item.type === "optional") return item;
const result = {
type: "optional",
item,
regex: "",
length: item.length,
group: true
};
updateOptionalEmojiRegexItem(result);
return result;
}
/**
* Clone item
*/
function cloneEmojiRegexItem(item, shallow = false) {
const result = { ...item };
if (result.type !== "optional" && result.numbers) result.numbers = [...result.numbers];
switch (result.type) {
case "utf16": break;
case "sequence":
if (shallow) result.items = [...result.items];
else result.items = result.items.map((item$1) => cloneEmojiRegexItem(item$1, false));
break;
case "set":
if (shallow) result.sets = [...result.sets];
else result.sets = result.sets.map((item$1) => cloneEmojiRegexItem(item$1, false));
break;
case "optional":
if (!shallow) result.item = cloneEmojiRegexItem(result.item, false);
break;
default: assertNever(result);
}
return result;
}
export { cloneEmojiRegexItem, createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem, updateOptionalEmojiRegexItem, updateSequenceEmojiRegexItem, updateSetEmojiRegexItem, updateUTF16EmojiRegexItem, wrapRegexInGroup };

View File

@@ -0,0 +1,20 @@
/**
* Create optimised regex
*/
declare function createOptimisedRegexForEmojiSequences(sequences: number[][]): string;
/**
* Create optimised regex for emojis
*
* First parameter is array of emojis, entry can be either list of
* code points or emoji sequence as a string
*
* Examples of acceptable strings (case insensitive):
* '1F636 200D 1F32B FE0F' - space separated UTF32 sequence
* '1f636-200d-1f32b-fe0f' - dash separated UTF32 sequence
* 'd83d-de36-200d-d83c-df2b-fe0f' - dash separated UTF16 sequence
* '\\uD83D\\uDE36\\u200D\\uD83C\\uDF2B\\uFE0F' - UTF16 sequence escaped with '\\u'
*
* All examples above refer to the same emoji and will generate the same regex result
*/
declare function createOptimisedRegex(emojis: (string | number[])[]): string;
export { createOptimisedRegex, createOptimisedRegexForEmojiSequences };

View File

@@ -0,0 +1,14 @@
import { EmojiItemRegex, OptionalEmojiItemRegex, SequenceEmojiItemRegex, SetEmojiItemRegex, UTF16EmojiItemRegex } from "./base.js";
/**
* Create regex item for set of numbers
*/
declare function createEmojiRegexItemForNumbers(numbers: number[]): UTF16EmojiItemRegex | SequenceEmojiItemRegex | SetEmojiItemRegex;
/**
* Create sequence of numbers
*/
declare function createRegexForNumbersSequence(numbers: number[], optionalVariations?: boolean): SequenceEmojiItemRegex | UTF16EmojiItemRegex | OptionalEmojiItemRegex;
/**
* Attempt to optimise numbers in a set
*/
declare function optimiseNumbersSet(set: SetEmojiItemRegex): EmojiItemRegex;
export { createEmojiRegexItemForNumbers, createRegexForNumbersSequence, optimiseNumbersSet };

View File

@@ -0,0 +1,18 @@
import { EmojiItemRegex } from "./base.js";
/**
* Tree item
*/
interface TreeItem {
regex: EmojiItemRegex;
end?: true;
children?: TreeItem[];
}
/**
* Create tree
*/
declare function createEmojisTree(sequences: number[][]): TreeItem[];
/**
* Parse tree
*/
declare function parseEmojiTree(items: TreeItem[]): EmojiItemRegex;
export { createEmojisTree, parseEmojiTree };

View File

@@ -0,0 +1,81 @@
import { joinerEmoji } from "../data.js";
import { convertEmojiSequenceToUTF32 } from "../convert.js";
import { splitEmojiSequences } from "../cleanup.js";
import { createOptionalEmojiRegexItem, createSequenceEmojiRegexItem, createSetEmojiRegexItem, createUTF16EmojiRegexItem } from "./base.js";
import { createRegexForNumbersSequence } from "./numbers.js";
import { mergeSimilarItemsInSet } from "./similar.js";
/**
* Create tree
*/
function createEmojisTree(sequences) {
const root = [];
for (let i = 0; i < sequences.length; i++) {
const split = splitEmojiSequences(convertEmojiSequenceToUTF32(sequences[i]));
let parent = root;
for (let j = 0; j < split.length; j++) {
const regex = createRegexForNumbersSequence(split[j]);
let item;
const match = parent.find((item$1) => item$1.regex.regex === regex.regex);
if (!match) {
item = { regex };
parent.push(item);
} else item = match;
if (j === split.length - 1) {
item.end = true;
break;
}
parent = item.children || (item.children = []);
}
}
return root;
}
/**
* Parse tree
*/
function parseEmojiTree(items) {
function mergeParsedChildren(items$1) {
const parsedItems = [];
const mapWithoutEnd = Object.create(null);
const mapWithEnd = Object.create(null);
for (let i = 0; i < items$1.length; i++) {
const item = items$1[i];
const children = item.children;
if (children) {
const fullItem = item;
const target = item.end ? mapWithEnd : mapWithoutEnd;
const regex = children.regex;
if (!target[regex]) target[regex] = [fullItem];
else target[regex].push(fullItem);
} else parsedItems.push(item.regex);
}
[mapWithEnd, mapWithoutEnd].forEach((source) => {
for (const regex in source) {
const items$2 = source[regex];
const firstItem = items$2[0];
let childSequence = [createUTF16EmojiRegexItem([joinerEmoji]), firstItem.children];
if (firstItem.end) childSequence = [createOptionalEmojiRegexItem(createSequenceEmojiRegexItem(childSequence))];
let mergedRegex;
if (items$2.length === 1) mergedRegex = firstItem.regex;
else mergedRegex = mergeSimilarItemsInSet(createSetEmojiRegexItem(items$2.map((item) => item.regex)));
const sequence = createSequenceEmojiRegexItem([mergedRegex, ...childSequence]);
parsedItems.push(sequence);
}
});
if (parsedItems.length === 1) return parsedItems[0];
return mergeSimilarItemsInSet(createSetEmojiRegexItem(parsedItems));
}
function parseItemChildren(item) {
const result = {
regex: item.regex,
end: !!item.end
};
const children = item.children;
if (!children) return result;
result.children = mergeParsedChildren(children.map(parseItemChildren));
return result;
}
return mergeParsedChildren(items.map(parseItemChildren));
}
export { createEmojisTree, parseEmojiTree };

View File

@@ -0,0 +1,14 @@
import { EmojiRegexMatch } from "./find.js";
/**
* Callback for replacing emoji in text
*
* Returns text to replace emoji with, undefined to skip replacement
*/
type FindAndReplaceEmojisInTextCallback = (match: EmojiRegexMatch, prev: string) => string | undefined;
/**
* Find and replace emojis in text
*
* Returns null if nothing was replaced
*/
declare function findAndReplaceEmojisInText(regexp: string | RegExp | (string | RegExp)[], content: string, callback: FindAndReplaceEmojisInTextCallback): string | null;
export { FindAndReplaceEmojisInTextCallback, findAndReplaceEmojisInText };

View File

@@ -0,0 +1,41 @@
import { EmojiComponentType } from "../data.js";
import { EmojiTestData, EmojiTestDataItem } from "./parse.js";
interface EmojiTestDataComponentsMap {
converted: Map<number, string>;
items: Map<string | number, EmojiTestDataItem>;
names: Map<string | number, string>;
types: Record<string, EmojiComponentType>;
keywords: Record<string, string>;
}
/**
* Map components from test data
*/
declare function mapEmojiTestDataComponents(testSequences: EmojiTestData): EmojiTestDataComponentsMap;
/**
* Sequence with components
*/
type EmojiSequenceWithComponents = (EmojiComponentType | number)[];
/**
* Convert to string
*/
declare function emojiSequenceWithComponentsToString(sequence: EmojiSequenceWithComponents): string;
/**
* Entry in sequence
*/
interface EmojiSequenceComponentEntry {
index: number;
type: EmojiComponentType;
}
/**
* Find variations in sequence
*/
declare function findEmojiComponentsInSequence(sequence: number[]): EmojiSequenceComponentEntry[];
/**
* Component values
*/
type EmojiSequenceComponentValues = Partial<Record<EmojiComponentType, number[]>>;
/**
* Replace components in sequence
*/
declare function replaceEmojiComponentsInCombinedSequence(sequence: EmojiSequenceWithComponents, values: EmojiSequenceComponentValues): number[];
export { EmojiSequenceComponentEntry, EmojiSequenceComponentValues, EmojiSequenceWithComponents, EmojiTestDataComponentsMap, emojiSequenceWithComponentsToString, findEmojiComponentsInSequence, mapEmojiTestDataComponents, replaceEmojiComponentsInCombinedSequence };

View File

@@ -0,0 +1,21 @@
import { BaseEmojiTestDataItem, EmojiTestData, EmojiTestDataItem } from "./parse.js";
import { EmojiSequenceWithComponents, EmojiTestDataComponentsMap } from "./components.js";
import { SplitEmojiName } from "./name.js";
/**
* Similar test data items as one item
*/
interface CombinedEmojiTestDataItem extends BaseEmojiTestDataItem {
name: SplitEmojiName;
sequenceKey: string;
sequence: EmojiSequenceWithComponents;
}
type SimilarEmojiTestData = Record<string, CombinedEmojiTestDataItem>;
/**
* Find components in item, generate CombinedEmojiTestDataItem
*/
declare function findComponentsInEmojiTestItem(item: EmojiTestDataItem, componentsData: EmojiTestDataComponentsMap): CombinedEmojiTestDataItem;
/**
* Combine similar items in one iteratable item
*/
declare function combineSimilarEmojiTestData(data: EmojiTestData, componentsData?: EmojiTestDataComponentsMap): SimilarEmojiTestData;
export { CombinedEmojiTestDataItem, SimilarEmojiTestData, combineSimilarEmojiTestData, findComponentsInEmojiTestItem };