+41
@@ -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 };
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { emojiComponents } from "../data.js";
|
||||
import { getEmojiSequenceKeyword } from "../format.js";
|
||||
/**
|
||||
* Map components from test data
|
||||
*/
|
||||
function mapEmojiTestDataComponents(testSequences) {
|
||||
const results = {
|
||||
converted: /* @__PURE__ */ new Map(),
|
||||
items: /* @__PURE__ */ new Map(),
|
||||
names: /* @__PURE__ */ new Map(),
|
||||
types: {},
|
||||
keywords: {}
|
||||
};
|
||||
for (const key in emojiComponents) {
|
||||
const type = key;
|
||||
const range = emojiComponents[type];
|
||||
for (let number = range[0]; number < range[1]; number++) {
|
||||
const keyword = getEmojiSequenceKeyword([number]);
|
||||
const item = testSequences[keyword];
|
||||
if (!item) throw new Error(`Missing emoji component in test sequence: "${keyword}"`);
|
||||
results.converted.set(number, keyword);
|
||||
results.items.set(number, item);
|
||||
results.items.set(keyword, item);
|
||||
const name = item.name;
|
||||
results.names.set(number, name);
|
||||
results.names.set(keyword, name);
|
||||
results.types[name] = type;
|
||||
results.keywords[name] = keyword;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
/**
|
||||
* Convert to string
|
||||
*/
|
||||
function emojiSequenceWithComponentsToString(sequence) {
|
||||
return sequence.map((item) => typeof item === "number" ? item.toString(16) : item).join("-");
|
||||
}
|
||||
/**
|
||||
* Find variations in sequence
|
||||
*/
|
||||
function findEmojiComponentsInSequence(sequence) {
|
||||
const components = [];
|
||||
for (let index = 0; index < sequence.length; index++) {
|
||||
const code = sequence[index];
|
||||
for (const key in emojiComponents) {
|
||||
const type = key;
|
||||
const range = emojiComponents[type];
|
||||
if (code >= range[0] && code < range[1]) {
|
||||
components.push({
|
||||
index,
|
||||
type
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return components;
|
||||
}
|
||||
/**
|
||||
* Replace components in sequence
|
||||
*/
|
||||
function replaceEmojiComponentsInCombinedSequence(sequence, values) {
|
||||
const indexes = {
|
||||
"hair-style": 0,
|
||||
"skin-tone": 0
|
||||
};
|
||||
return sequence.map((item) => {
|
||||
if (typeof item === "number") return item;
|
||||
const index = indexes[item]++;
|
||||
const list = values[item];
|
||||
if (!list || !list.length) throw new Error(`Cannot replace ${item}: no valid values provided`);
|
||||
return list[index >= list.length ? list.length - 1 : index];
|
||||
});
|
||||
}
|
||||
export { emojiSequenceWithComponentsToString, findEmojiComponentsInSequence, mapEmojiTestDataComponents, replaceEmojiComponentsInCombinedSequence };
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { EmojiComponentsTree } from "./tree.js";
|
||||
/**
|
||||
* Base type to extend
|
||||
*/
|
||||
interface BaseSequenceItem {
|
||||
sequence: number[];
|
||||
sequenceKey?: string;
|
||||
}
|
||||
/**
|
||||
* Find missing emojis
|
||||
*
|
||||
* Result includes missing items, which are extended from items that needs to
|
||||
* be copied. To identify which emojis to copy, source object should include
|
||||
* something like `iconName` key that points to icon sequence represents.
|
||||
*/
|
||||
declare function findMissingEmojis<T extends BaseSequenceItem>(sequences: T[], testDataTree: EmojiComponentsTree): T[];
|
||||
export { findMissingEmojis };
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { emojiComponents } from "../data.js";
|
||||
import { getUnqualifiedEmojiSequence } from "../cleanup.js";
|
||||
import { getEmojiSequenceKeyword } from "../format.js";
|
||||
import { replaceEmojiComponentsInCombinedSequence } from "./components.js";
|
||||
/**
|
||||
* Find missing emojis
|
||||
*
|
||||
* Result includes missing items, which are extended from items that needs to
|
||||
* be copied. To identify which emojis to copy, source object should include
|
||||
* something like `iconName` key that points to icon sequence represents.
|
||||
*/
|
||||
function findMissingEmojis(sequences, testDataTree) {
|
||||
const results = [];
|
||||
const existingItems = Object.create(null);
|
||||
const copiedItems = Object.create(null);
|
||||
sequences.forEach((item) => {
|
||||
const key = getEmojiSequenceKeyword(getUnqualifiedEmojiSequence(item.sequence));
|
||||
if (!existingItems[key] || existingItems[key].sequence.length < item.sequence.length) existingItems[key] = item;
|
||||
});
|
||||
const iterate = (type, parentTree, parentValues, parentItem, deep) => {
|
||||
const childTree = parentTree.children?.[type];
|
||||
if (!childTree) return;
|
||||
const range = emojiComponents[type];
|
||||
for (let number = range[0]; number < range[1]; number++) {
|
||||
const values = {
|
||||
"hair-style": [...parentValues["hair-style"]],
|
||||
"skin-tone": [...parentValues["skin-tone"]]
|
||||
};
|
||||
values[type].push(number);
|
||||
const sequence = replaceEmojiComponentsInCombinedSequence(childTree.item.sequence, values);
|
||||
const key = getEmojiSequenceKeyword(getUnqualifiedEmojiSequence(sequence));
|
||||
const oldItem = existingItems[key];
|
||||
let item;
|
||||
if (oldItem) item = oldItem;
|
||||
else {
|
||||
item = copiedItems[key];
|
||||
if (!item) {
|
||||
item = {
|
||||
...parentItem,
|
||||
sequence
|
||||
};
|
||||
if (item.sequenceKey) item.sequenceKey = key;
|
||||
copiedItems[key] = item;
|
||||
results.push(item);
|
||||
}
|
||||
}
|
||||
if (deep || oldItem) for (const key in values) iterate(key, childTree, values, item, deep);
|
||||
}
|
||||
};
|
||||
const parse = (key, deep) => {
|
||||
const treeItem = testDataTree[key];
|
||||
const rootItem = existingItems[treeItem.item.sequenceKey];
|
||||
if (!rootItem) return;
|
||||
const values = {
|
||||
"skin-tone": [],
|
||||
"hair-style": []
|
||||
};
|
||||
for (const key in values) iterate(key, treeItem, values, rootItem, deep);
|
||||
};
|
||||
for (const key in testDataTree) {
|
||||
parse(key, false);
|
||||
parse(key, true);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
export { findMissingEmojis };
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { EmojiSequenceComponentEntry, EmojiTestDataComponentsMap } from "./components.js";
|
||||
/**
|
||||
* Split emoji name in base name and variations
|
||||
*
|
||||
* Variations are also split in strings and emoji components with indexes pointing to sequence
|
||||
*/
|
||||
interface SplitEmojiName {
|
||||
base: string;
|
||||
key: string;
|
||||
variations?: (string | EmojiSequenceComponentEntry)[];
|
||||
components?: number;
|
||||
}
|
||||
/**
|
||||
* Split emoji name to base name and variations
|
||||
*
|
||||
* Also finds indexes of each variation
|
||||
*/
|
||||
declare function splitEmojiNameVariations(name: string, sequence: number[], componentsData: EmojiTestDataComponentsMap): SplitEmojiName;
|
||||
export { SplitEmojiName, splitEmojiNameVariations };
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { emojiComponents } from "../data.js";
|
||||
const nameSplit = ": ";
|
||||
const variationSplit = ", ";
|
||||
const ignoredVariations = new Set(["person"]);
|
||||
/**
|
||||
* Split emoji name to base name and variations
|
||||
*
|
||||
* Also finds indexes of each variation
|
||||
*/
|
||||
function splitEmojiNameVariations(name, sequence, componentsData) {
|
||||
const parts = name.split(nameSplit);
|
||||
const base = parts.shift();
|
||||
if (!parts.length) return {
|
||||
base,
|
||||
key: base
|
||||
};
|
||||
const baseVariations = parts.join(nameSplit).split(variationSplit).filter((text) => {
|
||||
if (!componentsData.types[text]) return !ignoredVariations.has(text);
|
||||
return false;
|
||||
});
|
||||
const result = {
|
||||
base,
|
||||
key: base + (baseVariations.length ? nameSplit + baseVariations.join(variationSplit) : "")
|
||||
};
|
||||
let components = 0;
|
||||
const variations = [...baseVariations];
|
||||
for (let index = 0; index < sequence.length; index++) {
|
||||
const num = sequence[index];
|
||||
for (const key in emojiComponents) {
|
||||
const type = key;
|
||||
const range = emojiComponents[type];
|
||||
if (num >= range[0] && num < range[1]) {
|
||||
variations.push({
|
||||
index,
|
||||
type
|
||||
});
|
||||
components++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (variations.length) result.variations = variations;
|
||||
if (components) result.components = components;
|
||||
return result;
|
||||
}
|
||||
export { splitEmojiNameVariations };
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
type EmojiStatus = 'component' | 'fully-qualified' | 'minimally-qualified' | 'unqualified';
|
||||
declare const componentStatus: EmojiStatus;
|
||||
/**
|
||||
* Base item
|
||||
*/
|
||||
interface BaseEmojiTestDataItem {
|
||||
group: string;
|
||||
subgroup: string;
|
||||
version: string;
|
||||
}
|
||||
/**
|
||||
* Test data item
|
||||
*/
|
||||
interface EmojiTestDataItem extends BaseEmojiTestDataItem {
|
||||
sequence: number[];
|
||||
emoji: string;
|
||||
status: EmojiStatus;
|
||||
name: string;
|
||||
}
|
||||
type EmojiTestData = Record<string, EmojiTestDataItem>;
|
||||
/**
|
||||
* Get all emoji sequences from test file
|
||||
*
|
||||
* Returns all emojis as UTF-32 sequences, where:
|
||||
* key = unqualified sequence (without \uFE0F)
|
||||
* value = qualified sequence (with \uFE0F)
|
||||
*
|
||||
* Duplicate items that have different versions with and without \uFE0F are
|
||||
* listed only once, with unqualified sequence as key and longest possible
|
||||
* qualified sequence as value
|
||||
*
|
||||
* Example of 3 identical entries:
|
||||
* '1F441 FE0F 200D 1F5E8 FE0F'
|
||||
* '1F441 200D 1F5E8 FE0F'
|
||||
* '1F441 FE0F 200D 1F5E8'
|
||||
* '1F441 200D 1F5E8'
|
||||
*
|
||||
* Out of these entries, only one item will be returned with:
|
||||
* key = '1f441-200d-1f5e8' (converted to lower case, separated with dash)
|
||||
* value.sequence = [0x1F441, 0xFE0F, 0x200D, 0x1F5E8, 0xFE0F]
|
||||
* value.status = 'fully-qualified'
|
||||
* other properties in value are identical for all versions
|
||||
*/
|
||||
declare function parseEmojiTestFile(data: string): EmojiTestData;
|
||||
export { BaseEmojiTestDataItem, EmojiStatus, EmojiTestData, EmojiTestDataItem, componentStatus, parseEmojiTestFile };
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { getEmojiSequenceFromString, getUnqualifiedEmojiSequence } from "../cleanup.js";
|
||||
import { getEmojiSequenceKeyword } from "../format.js";
|
||||
const componentStatus = "component";
|
||||
const allowedStatus = new Set([
|
||||
componentStatus,
|
||||
"fully-qualified",
|
||||
"minimally-qualified",
|
||||
"unqualified"
|
||||
]);
|
||||
/**
|
||||
* Get qualified variations from parsed test file
|
||||
*
|
||||
* Key is unqualified emoji, value is longest fully qualified emoji
|
||||
*/
|
||||
function getQualifiedTestData(data) {
|
||||
const results = Object.create(null);
|
||||
for (const key in data) {
|
||||
const item = data[key];
|
||||
const sequence = getUnqualifiedEmojiSequence(item.sequence);
|
||||
const shortKey = getEmojiSequenceKeyword(sequence);
|
||||
if (!results[shortKey] || results[shortKey].sequence.length < sequence.length) results[shortKey] = item;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
/**
|
||||
* Get all emoji sequences from test file
|
||||
*
|
||||
* Returns all emojis as UTF-32 sequences, where:
|
||||
* key = unqualified sequence (without \uFE0F)
|
||||
* value = qualified sequence (with \uFE0F)
|
||||
*
|
||||
* Duplicate items that have different versions with and without \uFE0F are
|
||||
* listed only once, with unqualified sequence as key and longest possible
|
||||
* qualified sequence as value
|
||||
*
|
||||
* Example of 3 identical entries:
|
||||
* '1F441 FE0F 200D 1F5E8 FE0F'
|
||||
* '1F441 200D 1F5E8 FE0F'
|
||||
* '1F441 FE0F 200D 1F5E8'
|
||||
* '1F441 200D 1F5E8'
|
||||
*
|
||||
* Out of these entries, only one item will be returned with:
|
||||
* key = '1f441-200d-1f5e8' (converted to lower case, separated with dash)
|
||||
* value.sequence = [0x1F441, 0xFE0F, 0x200D, 0x1F5E8, 0xFE0F]
|
||||
* value.status = 'fully-qualified'
|
||||
* other properties in value are identical for all versions
|
||||
*/
|
||||
function parseEmojiTestFile(data) {
|
||||
const results = Object.create(null);
|
||||
let group;
|
||||
let subgroup;
|
||||
data.split("\n").forEach((line) => {
|
||||
line = line.trim();
|
||||
const parts = line.split("#");
|
||||
if (parts.length < 2) return;
|
||||
const firstChunk = parts.shift().trim();
|
||||
const secondChunk = parts.join("#").trim();
|
||||
if (!firstChunk) {
|
||||
const commentParts = secondChunk.split(":");
|
||||
if (commentParts.length === 2) {
|
||||
const key = commentParts[0].trim();
|
||||
const value = commentParts[1].trim();
|
||||
switch (key) {
|
||||
case "group":
|
||||
group = value;
|
||||
subgroup = void 0;
|
||||
break;
|
||||
case "subgroup":
|
||||
subgroup = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!group || !subgroup) return;
|
||||
const firstChunkParts = firstChunk.split(";");
|
||||
if (firstChunkParts.length !== 2) return;
|
||||
const code = firstChunkParts[0].trim();
|
||||
if (!code || !code.match(/^[A-F0-9]+[A-F0-9\s]*[A-F0-9]+$/)) return;
|
||||
const status = firstChunkParts[1].trim();
|
||||
if (!allowedStatus.has(status)) throw new Error(`Bad emoji type: ${status}`);
|
||||
const secondChunkParts = secondChunk.split(/\s+/);
|
||||
if (secondChunkParts.length < 3) throw new Error(`Bad emoji comment for: ${code}`);
|
||||
const emoji = secondChunkParts.shift();
|
||||
const version = secondChunkParts.shift();
|
||||
if (version.slice(0, 1) !== "E") throw new Error(`Bad unicode version "${version}" for: ${code}`);
|
||||
const name = secondChunkParts.join(" ");
|
||||
const sequence = getEmojiSequenceFromString(code);
|
||||
const key = getEmojiSequenceKeyword(sequence);
|
||||
if (results[key]) throw new Error(`Duplicate entry for "${code}"`);
|
||||
results[key] = {
|
||||
group,
|
||||
subgroup,
|
||||
sequence,
|
||||
emoji,
|
||||
status,
|
||||
version,
|
||||
name
|
||||
};
|
||||
});
|
||||
return getQualifiedTestData(results);
|
||||
}
|
||||
export { componentStatus, parseEmojiTestFile };
|
||||
+21
@@ -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 };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { vs16Emoji } from "../data.js";
|
||||
import { emojiSequenceWithComponentsToString, mapEmojiTestDataComponents } from "./components.js";
|
||||
import { splitEmojiNameVariations } from "./name.js";
|
||||
/**
|
||||
* Find components in item, generate CombinedEmojiTestDataItem
|
||||
*/
|
||||
function findComponentsInEmojiTestItem(item, componentsData) {
|
||||
const name = splitEmojiNameVariations(item.name, item.sequence, componentsData);
|
||||
const sequence = [...item.sequence];
|
||||
name.variations?.forEach((item) => {
|
||||
if (typeof item !== "string") sequence[item.index] = item.type;
|
||||
});
|
||||
const sequenceKey = emojiSequenceWithComponentsToString(sequence.filter((code) => code !== vs16Emoji));
|
||||
return {
|
||||
...item,
|
||||
name,
|
||||
sequenceKey,
|
||||
sequence
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Combine similar items in one iteratable item
|
||||
*/
|
||||
function combineSimilarEmojiTestData(data, componentsData) {
|
||||
const results = Object.create(null);
|
||||
componentsData = componentsData || mapEmojiTestDataComponents(data);
|
||||
for (const key in data) {
|
||||
const sourceItem = data[key];
|
||||
if (sourceItem.status !== "component") {
|
||||
const item = findComponentsInEmojiTestItem(sourceItem, componentsData);
|
||||
results[item.sequenceKey] = item;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
export { combineSimilarEmojiTestData, findComponentsInEmojiTestItem };
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { EmojiComponentType } from "../data.js";
|
||||
import { CombinedEmojiTestDataItem, SimilarEmojiTestData } from "./similar.js";
|
||||
/**
|
||||
* List of components
|
||||
*/
|
||||
type ComponentsCount = Required<Record<EmojiComponentType, number>>;
|
||||
/**
|
||||
* Extended tree item
|
||||
*/
|
||||
interface TreeSplitEmojiTestDataItem extends CombinedEmojiTestDataItem {
|
||||
components: ComponentsCount;
|
||||
componentsKey: string;
|
||||
}
|
||||
/**
|
||||
* Tree item
|
||||
*/
|
||||
interface EmojiComponentsTreeItem {
|
||||
item: TreeSplitEmojiTestDataItem;
|
||||
children?: Record<EmojiComponentType, EmojiComponentsTreeItem>;
|
||||
}
|
||||
type EmojiComponentsTree = Record<string, EmojiComponentsTreeItem>;
|
||||
/**
|
||||
* Convert test data to dependencies tree, based on components
|
||||
*/
|
||||
declare function getEmojiTestDataTree(data: SimilarEmojiTestData): EmojiComponentsTree;
|
||||
export { EmojiComponentsTree, EmojiComponentsTreeItem, getEmojiTestDataTree };
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import { emojiComponents } from "../data.js";
|
||||
/**
|
||||
* Merge types for unique key
|
||||
*/
|
||||
function mergeComponentTypes(value) {
|
||||
return "[" + value.join(",") + "]";
|
||||
}
|
||||
/**
|
||||
* Merge count for unique key
|
||||
*/
|
||||
function mergeComponentsCount(value) {
|
||||
const keys = [];
|
||||
for (const key in emojiComponents) {
|
||||
const type = key;
|
||||
for (let i = 0; i < value[type]; i++) keys.push(type);
|
||||
}
|
||||
return keys.length ? mergeComponentTypes(keys) : "";
|
||||
}
|
||||
/**
|
||||
* Get item from group
|
||||
*/
|
||||
function getGroupItem(items, components) {
|
||||
const item = items[mergeComponentsCount(components)];
|
||||
if (item) {
|
||||
item.parsed = true;
|
||||
return item.item;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convert test data to dependencies tree, based on components
|
||||
*/
|
||||
function getEmojiTestDataTree(data) {
|
||||
const groups = Object.create(null);
|
||||
for (const key in data) {
|
||||
const item = data[key];
|
||||
const text = item.name.key;
|
||||
const parent = groups[text] || (groups[text] = {});
|
||||
const components = {
|
||||
"hair-style": 0,
|
||||
"skin-tone": 0
|
||||
};
|
||||
item.sequence.forEach((value) => {
|
||||
if (typeof value !== "number") components[value]++;
|
||||
});
|
||||
const componentsKey = mergeComponentsCount(components);
|
||||
if (parent[componentsKey]) throw new Error(`Duplicate components tree item for "${text}"`);
|
||||
parent[componentsKey] = { item: {
|
||||
...item,
|
||||
components,
|
||||
componentsKey
|
||||
} };
|
||||
}
|
||||
const results = Object.create(null);
|
||||
for (const key in groups) {
|
||||
const items = groups[key];
|
||||
const check = (parent, parentComponents, type) => {
|
||||
const item = parse(parentComponents, [type]);
|
||||
if (item) {
|
||||
const children = parent.children || (parent.children = {});
|
||||
children[type] = item;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const parse = (parentComponents, newComponents) => {
|
||||
const components = {
|
||||
"hair-style": 0,
|
||||
"skin-tone": 0
|
||||
};
|
||||
const componentsList = parentComponents.concat(newComponents);
|
||||
componentsList.forEach((type) => {
|
||||
components[type]++;
|
||||
});
|
||||
let item = getGroupItem(items, components);
|
||||
if (!item && newComponents.length === 1 && newComponents[0] === "skin-tone") {
|
||||
const doubleComponents = { ...components };
|
||||
doubleComponents["skin-tone"]++;
|
||||
item = getGroupItem(items, doubleComponents);
|
||||
}
|
||||
if (item) {
|
||||
const result = { item };
|
||||
for (const key in emojiComponents) check(result, componentsList, key);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
const root = parse([], []);
|
||||
if (!root) throw new Error(`Cannot find parent item for "${key}"`);
|
||||
for (const itemsKey in items) if (!items[itemsKey].parsed) throw new Error(`Error generating tree for "${key}"`);
|
||||
if (root.children) results[key] = root;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
export { getEmojiTestDataTree };
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Get qualified sequence, adding optional `FE0F` wherever it might exist
|
||||
*
|
||||
* This might result in sequence that is not actually valid, but considering
|
||||
* that `FE0F` is always treated as optional, full sequence used in regex will
|
||||
* catch both qualified and unqualified emojis, so proper sequence will get
|
||||
* caught anyway. This function just makes sure that in case if sequence does
|
||||
* have `FE0F`, it will be caught by regex too.
|
||||
*/
|
||||
declare function guessQualifiedEmojiSequence(sequence: number[]): number[];
|
||||
/**
|
||||
* Base type to extend
|
||||
*/
|
||||
interface BaseSequenceItem {
|
||||
sequence: number[];
|
||||
sequenceKey?: string;
|
||||
}
|
||||
/**
|
||||
* Get qualified variations for emojis
|
||||
*
|
||||
* Also converts list to UTF-32 as needed and removes duplicate items
|
||||
*/
|
||||
declare function getQualifiedEmojiVariation<T extends BaseSequenceItem>(item: T): T;
|
||||
/**
|
||||
* Get qualified emoji variations for set of emojis, ignoring duplicate entries
|
||||
*/
|
||||
declare function getQualifiedEmojiVariations<T extends BaseSequenceItem>(items: T[]): T[];
|
||||
export { getQualifiedEmojiVariation, getQualifiedEmojiVariations, guessQualifiedEmojiSequence };
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { emojiComponents, vs16Emoji } from "../data.js";
|
||||
import { convertEmojiSequenceToUTF32 } from "../convert.js";
|
||||
import { getUnqualifiedEmojiSequence, joinEmojiSequences, splitEmojiSequences } from "../cleanup.js";
|
||||
import { getEmojiSequenceKeyword } from "../format.js";
|
||||
/**
|
||||
* Get qualified sequence, adding optional `FE0F` wherever it might exist
|
||||
*
|
||||
* This might result in sequence that is not actually valid, but considering
|
||||
* that `FE0F` is always treated as optional, full sequence used in regex will
|
||||
* catch both qualified and unqualified emojis, so proper sequence will get
|
||||
* caught anyway. This function just makes sure that in case if sequence does
|
||||
* have `FE0F`, it will be caught by regex too.
|
||||
*/
|
||||
function guessQualifiedEmojiSequence(sequence) {
|
||||
return joinEmojiSequences(splitEmojiSequences(sequence).map((part) => {
|
||||
if (part.indexOf(65039) !== -1) return part;
|
||||
if (part.length === 2) {
|
||||
const lastNum = part[1];
|
||||
if (lastNum === 8419) return [
|
||||
part[0],
|
||||
vs16Emoji,
|
||||
lastNum
|
||||
];
|
||||
for (const key in emojiComponents) {
|
||||
const range = emojiComponents[key];
|
||||
if (lastNum >= range[0] && lastNum < range[1]) return [
|
||||
part[0],
|
||||
vs16Emoji,
|
||||
lastNum
|
||||
];
|
||||
}
|
||||
}
|
||||
return part.length === 1 ? [part[0], vs16Emoji] : part;
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Get qualified variations for emojis
|
||||
*
|
||||
* Also converts list to UTF-32 as needed and removes duplicate items
|
||||
*/
|
||||
function getQualifiedEmojiVariation(item) {
|
||||
const unqualifiedSequence = getUnqualifiedEmojiSequence(convertEmojiSequenceToUTF32(item.sequence));
|
||||
const result = {
|
||||
...item,
|
||||
sequence: guessQualifiedEmojiSequence(unqualifiedSequence)
|
||||
};
|
||||
if (result.sequenceKey) result.sequenceKey = getEmojiSequenceKeyword(unqualifiedSequence);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Get qualified emoji variations for set of emojis, ignoring duplicate entries
|
||||
*/
|
||||
function getQualifiedEmojiVariations(items) {
|
||||
const results = Object.create(null);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const result = getQualifiedEmojiVariation(items[i]);
|
||||
const key = getEmojiSequenceKeyword(getUnqualifiedEmojiSequence(result.sequence));
|
||||
if (!results[key] || results[key].sequence.length < result.sequence.length) results[key] = result;
|
||||
}
|
||||
return Object.values(results);
|
||||
}
|
||||
export { getQualifiedEmojiVariation, getQualifiedEmojiVariations, guessQualifiedEmojiSequence };
|
||||
Reference in New Issue
Block a user