pulse-cv / server /analysis /normalization.ts
qyam23's picture
Deploy PulseCV Docker Space
95abc1b verified
Raw
History Blame Contribute Delete
2.89 kB
export function normalizeWhitespace(value: string): string {
return value.replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
}
export function normalizeText(value: string): string {
return normalizeWhitespace(value)
.toLowerCase()
.replace(/([\u0590-\u05FF]{2,})[./](?:\u05D4|\u05EA)(?=\s|$)/gu, "$1")
.replace(/[\u201C\u201D"']/g, "")
.replace(/[\u2013\u2014]/g, "-")
.replace(/[.,/\\()|[\]{}:;]+/g, " ");
}
export function slugify(value: string): string {
return normalizeText(value)
.replace(/[^a-z0-9\u0590-\u05FF]+/g, "-")
.replace(/^-+|-+$/g, "");
}
export function stripHebrewPrefixes(token: string): string {
const prefixes = ["ื•", "ื‘", "ืœ", "ื›", "ืž", "ืฉ", "ื”"];
let output = token;
while (output.length > 4 && prefixes.includes(output[0])) {
output = output.slice(1);
}
return output;
}
export function normalizeToken(token: string): string {
const cleaned = normalizeText(token).replace(/^[^a-z0-9\u0590-\u05FF]+|[^a-z0-9\u0590-\u05FF]+$/g, "");
if (/^[\u0590-\u05FF]+$/u.test(cleaned)) {
return stripHebrewPrefixes(cleaned);
}
return cleaned;
}
export function tokenize(value: string): string[] {
const raw = normalizeText(value).match(/[a-z0-9+#.]{2,}|[\u0590-\u05FF]{2,}/g) || [];
return raw.map(normalizeToken).filter(Boolean);
}
export function unique<T>(values: T[]): T[] {
return Array.from(new Set(values));
}
export function lineSpans(text: string): { text: string; start: number; end: number }[] {
const lines = text.split(/\r?\n/);
const spans: { text: string; start: number; end: number }[] = [];
let cursor = 0;
for (const line of lines) {
const start = cursor;
const end = cursor + line.length;
if (line.trim()) {
spans.push({ text: line.trim(), start, end });
}
cursor = end + 1;
}
return spans;
}
export function sentenceSpans(text: string): { text: string; start: number; end: number }[] {
const pattern = /[^.!?\n]+[.!?]?/g;
const spans: { text: string; start: number; end: number }[] = [];
let match: RegExpExecArray | null;
while ((match = pattern.exec(text)) !== null) {
const value = match[0].trim();
if (!value) continue;
spans.push({ text: value, start: match.index, end: match.index + match[0].length });
}
return spans;
}
export function includesAlias(text: string, alias: string): boolean {
const haystack = normalizeText(text);
const needle = normalizeText(alias);
if (!needle) return false;
return haystack.includes(needle);
}
export function overlapRatio(left: string, right: string): number {
const leftTokens = unique(tokenize(left));
const rightTokens = unique(tokenize(right));
if (!leftTokens.length || !rightTokens.length) return 0;
const intersection = leftTokens.filter((token) => rightTokens.includes(token)).length;
return intersection / Math.max(rightTokens.length, 1);
}