Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 24x 7x 1500x 41x 317x 285x 57x 57x 57x 57x 73x 40x 40x 34x 34x 134x 34x 238x 238x 768x 768x 106x 1616x 69x 82x 82x 82x 148x 82x 70x 70x 33x 33x 33x 33x 33x | /**
* @module scripts/statskontoret/internal/text
* @description Internal shared text/number/XML helpers used across the
* Statskontoret submodules (extractors, parsers, domain filters).
*
* Not part of the public re-export surface — callers must continue importing
* the high-level symbols from `scripts/statskontoret-client.js`.
*
* @author Hack23 AB
* @license Apache-2.0
*/
import { decodeHtmlEntities } from '../../html-utils.js';
export function trimTrailingSlash(value: string): string {
return value.replace(/\/+$/, '');
}
export function normalizeWhitespace(value: string): string {
return value.replace(/\s+/g, ' ').trim();
}
export function normalizeKey(value: string): string {
return value
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]+/g, '');
}
export function roundOneDecimal(value: number): number {
return Math.round(value * 10) / 10;
}
export function decodeHtml(value: string): string {
return decodeHtmlEntities(value).replace(/\u00a0/g, ' ');
}
export function decodeXml(value: string): string {
return decodeHtml(value);
}
export function parseStatskontoretSwedishNumber(value: string): number | undefined {
const compact = value.replace(/\s/g, '');
const normalized = compact.includes(',')
? compact.replace(/\./g, '').replace(',', '.')
: compact;
const parsed = Number.parseFloat(normalized);
return Number.isFinite(parsed) ? parsed : undefined;
}
export function parseStatskontoretOptionalInt(value: string | null): number | undefined {
if (!value) return undefined;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : undefined;
}
export function buildRecordLookup(record: Record<string, string>): Map<string, string> {
const lookup = new Map<string, string>();
for (const [key, value] of Object.entries(record)) {
lookup.set(normalizeKey(key), value);
}
return lookup;
}
export function findField(
lookup: ReadonlyMap<string, string>,
candidates: readonly string[],
): string | undefined {
const normalizedCandidates = candidates.map(normalizeKey);
for (const candidate of normalizedCandidates) {
const exact = lookup.get(candidate);
if (exact !== undefined) return exact;
}
for (const [key, value] of lookup.entries()) {
if (normalizedCandidates.some((candidate) => key.includes(candidate))) return value;
}
return undefined;
}
export function parseXmlAttributes(input: string): Map<string, string> {
const attrs = new Map<string, string>();
const attrRe = /([\w:-]+)=["']([^"']*)["']/g;
for (const match of input.matchAll(attrRe)) {
attrs.set(match[1], decodeXml(match[2] ?? ''));
}
return attrs;
}
export function firstXmlTagValue(xml: string, tag: string): string | undefined {
const match = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i').exec(xml);
return match ? decodeXml(match[1] ?? '') : undefined;
}
export function extractTextNodes(xml: string): string {
const parts: string[] = [];
const textRe = /<t\b[^>]*>([\s\S]*?)<\/t>/gi;
for (const match of xml.matchAll(textRe)) {
parts.push(decodeXml(match[1] ?? ''));
}
return parts.join('');
}
|