460 lines
18 KiB
TypeScript
460 lines
18 KiB
TypeScript
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|||
|
import * as plugins from '../plugins.js';
|
|||
|
import { EInvoice } from '../../../ts/index.js';
|
|||
|
import { CorpusLoader } from '../corpus.loader.js';
|
|||
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|||
|
|
|||
|
tap.test('ENC-07: Attribute Encoding - should handle XML attribute encoding correctly', async (t) => {
|
|||
|
// ENC-07: Verify proper encoding of XML attributes including special chars and quotes
|
|||
|
// This test ensures attributes are properly encoded across different scenarios
|
|||
|
|
|||
|
const performanceTracker = new PerformanceTracker('ENC-07: Attribute Encoding');
|
|||
|
const corpusLoader = new CorpusLoader();
|
|||
|
|
|||
|
t.test('Basic attribute encoding', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID schemeID="INVOICE" schemeAgencyID="6">ATTR-BASIC-001</ID>
|
|||
|
<IssueDate>2025-01-25</IssueDate>
|
|||
|
<DocumentCurrencyCode listID="ISO4217" listAgencyID="6" listVersionID="2001">EUR</DocumentCurrencyCode>
|
|||
|
<TaxTotal>
|
|||
|
<TaxAmount currencyID="EUR">19.00</TaxAmount>
|
|||
|
<TaxSubtotal>
|
|||
|
<TaxCategory>
|
|||
|
<ID schemeID="UNCL5305" schemeAgencyID="6">S</ID>
|
|||
|
<Percent>19</Percent>
|
|||
|
<TaxScheme>
|
|||
|
<ID schemeID="UN/ECE 5153" schemeAgencyID="6">VAT</ID>
|
|||
|
</TaxScheme>
|
|||
|
</TaxCategory>
|
|||
|
</TaxSubtotal>
|
|||
|
</TaxTotal>
|
|||
|
<InvoiceLine>
|
|||
|
<ID>1</ID>
|
|||
|
<InvoicedQuantity unitCode="C62" unitCodeListID="UNECERec20">10</InvoicedQuantity>
|
|||
|
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
|
|||
|
</InvoiceLine>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify basic attributes are preserved
|
|||
|
expect(xmlString).toMatch(/schemeID\s*=\s*["']INVOICE["']/);
|
|||
|
expect(xmlString).toMatch(/schemeAgencyID\s*=\s*["']6["']/);
|
|||
|
expect(xmlString).toMatch(/listID\s*=\s*["']ISO4217["']/);
|
|||
|
expect(xmlString).toMatch(/listVersionID\s*=\s*["']2001["']/);
|
|||
|
expect(xmlString).toMatch(/currencyID\s*=\s*["']EUR["']/);
|
|||
|
expect(xmlString).toMatch(/unitCode\s*=\s*["']C62["']/);
|
|||
|
expect(xmlString).toMatch(/unitCodeListID\s*=\s*["']UNECERec20["']/);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('basic-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Attributes with special characters', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-SPECIAL-001</ID>
|
|||
|
<Note languageID="de-DE" encoding="UTF-8">Rechnung für Bücher & Zeitschriften</Note>
|
|||
|
<PaymentMeans>
|
|||
|
<PaymentMeansCode name="Überweisung (Bank & SEPA)">30</PaymentMeansCode>
|
|||
|
<PaymentID reference="Order <2025-001>">PAY-123</PaymentID>
|
|||
|
<PayeeFinancialAccount>
|
|||
|
<Name type="IBAN & BIC">DE89 3704 0044 0532 0130 00</Name>
|
|||
|
<FinancialInstitutionBranch>
|
|||
|
<Name branch="München "Zentrum"">Sparkasse</Name>
|
|||
|
</FinancialInstitutionBranch>
|
|||
|
</PayeeFinancialAccount>
|
|||
|
</PaymentMeans>
|
|||
|
<AllowanceCharge>
|
|||
|
<ChargeIndicator>false</ChargeIndicator>
|
|||
|
<AllowanceChargeReason code="95" description="Discount for > 100€ orders">Volume discount</AllowanceChargeReason>
|
|||
|
<Amount currencyID="EUR" percentage="5%" calculation="100 * 0.05">5.00</Amount>
|
|||
|
</AllowanceCharge>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify special characters in attributes are properly escaped
|
|||
|
expect(xmlString).toMatch(/name\s*=\s*["']Überweisung \(Bank & SEPA\)["']/);
|
|||
|
expect(xmlString).toMatch(/reference\s*=\s*["']Order <2025-001>["']/);
|
|||
|
expect(xmlString).toMatch(/type\s*=\s*["']IBAN & BIC["']/);
|
|||
|
expect(xmlString).toMatch(/branch\s*=\s*["']München ("|")Zentrum("|")["']/);
|
|||
|
expect(xmlString).toMatch(/description\s*=\s*["']Discount for > 100€ orders["']/);
|
|||
|
expect(xmlString).toMatch(/percentage\s*=\s*["']5%["']/);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('special-char-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Quote handling in attributes', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-QUOTES-001</ID>
|
|||
|
<Note title='Single quotes with "double quotes" inside'>Test note</Note>
|
|||
|
<AdditionalDocumentReference>
|
|||
|
<ID description="Product "Premium" edition">DOC-001</ID>
|
|||
|
<DocumentDescription title="User's guide">Manual for "advanced" users</DocumentDescription>
|
|||
|
<Attachment>
|
|||
|
<ExternalReference>
|
|||
|
<URI scheme="http" description='Link to "official" site'>http://example.com/doc?id=123&type="pdf"</URI>
|
|||
|
</ExternalReference>
|
|||
|
</Attachment>
|
|||
|
</AdditionalDocumentReference>
|
|||
|
<InvoiceLine>
|
|||
|
<Item>
|
|||
|
<Name type='"Special" product'>Item with quotes</Name>
|
|||
|
<Description note="Contains both 'single' and "double" quotes">Complex quoting test</Description>
|
|||
|
<AdditionalItemProperty>
|
|||
|
<Name>Quote test</Name>
|
|||
|
<Value type="text" format='He said: "It\'s working!"'>Quoted value</Value>
|
|||
|
</AdditionalItemProperty>
|
|||
|
</Item>
|
|||
|
</InvoiceLine>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify quote handling - implementation may use different strategies
|
|||
|
// Either escape quotes or switch quote style
|
|||
|
expect(xmlString).toBeTruthy();
|
|||
|
|
|||
|
// Should contain the attribute values somehow
|
|||
|
expect(xmlString).toMatch(/Single quotes with .*double quotes.* inside/);
|
|||
|
expect(xmlString).toMatch(/Product .*Premium.* edition/);
|
|||
|
expect(xmlString).toMatch(/User.*s guide/);
|
|||
|
expect(xmlString).toMatch(/Special.*product/);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('quote-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('International characters in attributes', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-INTL-001</ID>
|
|||
|
<Note languageID="multi" region="Europa/歐洲/यूरोप">International attributes</Note>
|
|||
|
<AccountingSupplierParty>
|
|||
|
<Party>
|
|||
|
<PartyName>
|
|||
|
<Name tradingName="Société Générale" localName="ソシエテ・ジェネラル">SG Group</Name>
|
|||
|
</PartyName>
|
|||
|
<PostalAddress>
|
|||
|
<StreetName type="Avenue/大道/एवेन्यू">Champs-Élysées</StreetName>
|
|||
|
<CityName region="Île-de-France">Paris</CityName>
|
|||
|
<Country>
|
|||
|
<IdentificationCode listName="ISO 3166-1 α2">FR</IdentificationCode>
|
|||
|
<Name language="fr-FR">République française</Name>
|
|||
|
</Country>
|
|||
|
</PostalAddress>
|
|||
|
</Party>
|
|||
|
</AccountingSupplierParty>
|
|||
|
<PaymentTerms>
|
|||
|
<Note terms="30 días/天/दिन" currency="€/¥/₹">Multi-currency payment</Note>
|
|||
|
</PaymentTerms>
|
|||
|
<InvoiceLine>
|
|||
|
<Item>
|
|||
|
<Name category="Bücher/书籍/पुस्तकें">International Books</Name>
|
|||
|
<Description author="François Müller (佛朗索瓦·穆勒)">Multilingual content</Description>
|
|||
|
</Item>
|
|||
|
</InvoiceLine>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify international characters in attributes
|
|||
|
expect(xmlString).toContain('Europa/歐洲/यूरोप');
|
|||
|
expect(xmlString).toContain('Société Générale');
|
|||
|
expect(xmlString).toContain('ソシエテ・ジェネラル');
|
|||
|
expect(xmlString).toContain('Avenue/大道/एवेन्यू');
|
|||
|
expect(xmlString).toContain('Île-de-France');
|
|||
|
expect(xmlString).toContain('α2'); // Greek alpha
|
|||
|
expect(xmlString).toContain('République française');
|
|||
|
expect(xmlString).toContain('30 días/天/दिन');
|
|||
|
expect(xmlString).toContain('€/¥/₹');
|
|||
|
expect(xmlString).toContain('Bücher/书籍/पुस्तकें');
|
|||
|
expect(xmlString).toContain('佛朗索瓦·穆勒');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('intl-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Empty and whitespace attributes', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-WHITESPACE-001</ID>
|
|||
|
<Note title="" language="">Empty attributes</Note>
|
|||
|
<DocumentReference>
|
|||
|
<ID schemeID=" " schemeAgencyID=" ">REF-001</ID>
|
|||
|
<DocumentDescription prefix=" " suffix=" "> Trimmed content </DocumentDescription>
|
|||
|
</DocumentReference>
|
|||
|
<PaymentMeans>
|
|||
|
<PaymentID reference="
|
|||
|
multiline
|
|||
|
reference
|
|||
|
">PAY-001</PaymentID>
|
|||
|
<InstructionNote format=" preserved spaces ">Note with spaces</InstructionNote>
|
|||
|
</PaymentMeans>
|
|||
|
<InvoiceLine>
|
|||
|
<LineExtensionAmount currencyID="EUR" decimals="" symbol="€">100.00</LineExtensionAmount>
|
|||
|
<Item>
|
|||
|
<Description short=" " long=" ">Item description</Description>
|
|||
|
</Item>
|
|||
|
</InvoiceLine>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify empty attributes are preserved
|
|||
|
expect(xmlString).toMatch(/title\s*=\s*["'](\s*)["']/);
|
|||
|
expect(xmlString).toMatch(/language\s*=\s*["'](\s*)["']/);
|
|||
|
|
|||
|
// Whitespace handling may vary
|
|||
|
expect(xmlString).toContain('schemeID=');
|
|||
|
expect(xmlString).toContain('reference=');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('whitespace-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Numeric and boolean attribute values', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-NUMERIC-001</ID>
|
|||
|
<AllowanceCharge>
|
|||
|
<ChargeIndicator>true</ChargeIndicator>
|
|||
|
<SequenceNumeric>1</SequenceNumeric>
|
|||
|
<Amount currencyID="EUR" decimals="2" precision="0.01">19.99</Amount>
|
|||
|
<BaseAmount currencyID="EUR" percentage="19.5" factor="0.195">100.00</BaseAmount>
|
|||
|
</AllowanceCharge>
|
|||
|
<TaxTotal>
|
|||
|
<TaxAmount currencyID="EUR" rate="19" rateType="percent">19.00</TaxAmount>
|
|||
|
<TaxSubtotal>
|
|||
|
<TaxableAmount currencyID="EUR" rounded="false">100.00</TaxableAmount>
|
|||
|
<TaxCategory>
|
|||
|
<ID>S</ID>
|
|||
|
<Percent format="decimal">19.0</Percent>
|
|||
|
<TaxExemptionReason code="0" active="true">Not exempt</TaxExemptionReason>
|
|||
|
</TaxCategory>
|
|||
|
</TaxSubtotal>
|
|||
|
</TaxTotal>
|
|||
|
<InvoiceLine>
|
|||
|
<ID sequence="001" index="0">1</ID>
|
|||
|
<InvoicedQuantity unitCode="C62" value="10.0" isInteger="true">10</InvoicedQuantity>
|
|||
|
<Price>
|
|||
|
<PriceAmount currencyID="EUR" negative="false">10.00</PriceAmount>
|
|||
|
<BaseQuantity unitCode="C62" default="1">1</BaseQuantity>
|
|||
|
</Price>
|
|||
|
</InvoiceLine>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify numeric and boolean attributes
|
|||
|
expect(xmlString).toMatch(/decimals\s*=\s*["']2["']/);
|
|||
|
expect(xmlString).toMatch(/precision\s*=\s*["']0\.01["']/);
|
|||
|
expect(xmlString).toMatch(/percentage\s*=\s*["']19\.5["']/);
|
|||
|
expect(xmlString).toMatch(/factor\s*=\s*["']0\.195["']/);
|
|||
|
expect(xmlString).toMatch(/rate\s*=\s*["']19["']/);
|
|||
|
expect(xmlString).toMatch(/rounded\s*=\s*["']false["']/);
|
|||
|
expect(xmlString).toMatch(/active\s*=\s*["']true["']/);
|
|||
|
expect(xmlString).toMatch(/sequence\s*=\s*["']001["']/);
|
|||
|
expect(xmlString).toMatch(/index\s*=\s*["']0["']/);
|
|||
|
expect(xmlString).toMatch(/isInteger\s*=\s*["']true["']/);
|
|||
|
expect(xmlString).toMatch(/negative\s*=\s*["']false["']/);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('numeric-boolean-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Namespace-prefixed attributes', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
|
|||
|
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
|
<Invoice
|
|||
|
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|||
|
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
|||
|
xsi:schemaLocation="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2 Invoice.xsd">
|
|||
|
<UBLVersionID>2.1</UBLVersionID>
|
|||
|
<ID>ATTR-NS-PREFIX-001</ID>
|
|||
|
<ProfileID xsi:type="string">urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ProfileID>
|
|||
|
<AdditionalDocumentReference>
|
|||
|
<ID>DOC-001</ID>
|
|||
|
<Attachment>
|
|||
|
<ExternalReference>
|
|||
|
<URI xlink:type="simple" xlink:href="http://example.com/doc.pdf" xlink:title="Invoice Documentation">http://example.com/doc.pdf</URI>
|
|||
|
</ExternalReference>
|
|||
|
<EmbeddedDocumentBinaryObject
|
|||
|
mimeCode="application/pdf"
|
|||
|
encodingCode="base64"
|
|||
|
filename="invoice.pdf"
|
|||
|
ds:algorithm="SHA256">
|
|||
|
JVBERi0xLjQKJeLjz9MKNCAwIG9iago=
|
|||
|
</EmbeddedDocumentBinaryObject>
|
|||
|
</Attachment>
|
|||
|
</AdditionalDocumentReference>
|
|||
|
<Signature>
|
|||
|
<ID>SIG-001</ID>
|
|||
|
<SignatureMethod ds:Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256">RSA-SHA256</SignatureMethod>
|
|||
|
</Signature>
|
|||
|
</Invoice>`;
|
|||
|
|
|||
|
const einvoice = new EInvoice();
|
|||
|
await einvoice.loadFromString(xmlContent);
|
|||
|
|
|||
|
const xmlString = einvoice.getXmlString();
|
|||
|
|
|||
|
// Verify namespace-prefixed attributes
|
|||
|
expect(xmlString).toContain('xsi:schemaLocation=');
|
|||
|
expect(xmlString).toContain('xsi:type=');
|
|||
|
expect(xmlString).toContain('xlink:type=');
|
|||
|
expect(xmlString).toContain('xlink:href=');
|
|||
|
expect(xmlString).toContain('xlink:title=');
|
|||
|
expect(xmlString).toContain('ds:algorithm=');
|
|||
|
expect(xmlString).toContain('ds:Algorithm=');
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('ns-prefixed-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
t.test('Corpus attribute analysis', async () => {
|
|||
|
const startTime = performance.now();
|
|||
|
let processedCount = 0;
|
|||
|
const attributeStats = {
|
|||
|
totalAttributes: 0,
|
|||
|
escapedAttributes: 0,
|
|||
|
unicodeAttributes: 0,
|
|||
|
numericAttributes: 0,
|
|||
|
emptyAttributes: 0,
|
|||
|
commonAttributes: new Map<string, number>()
|
|||
|
};
|
|||
|
|
|||
|
const files = await corpusLoader.getAllFiles();
|
|||
|
const xmlFiles = files.filter(f => f.endsWith('.xml'));
|
|||
|
|
|||
|
// Analyze attribute usage in corpus
|
|||
|
const sampleSize = Math.min(80, xmlFiles.length);
|
|||
|
const sample = xmlFiles.slice(0, sampleSize);
|
|||
|
|
|||
|
for (const file of sample) {
|
|||
|
try {
|
|||
|
const content = await corpusLoader.readFile(file);
|
|||
|
let xmlString: string;
|
|||
|
|
|||
|
if (Buffer.isBuffer(content)) {
|
|||
|
xmlString = content.toString('utf8');
|
|||
|
} else {
|
|||
|
xmlString = content;
|
|||
|
}
|
|||
|
|
|||
|
// Count attributes
|
|||
|
const attrMatches = xmlString.match(/\s(\w+(?::\w+)?)\s*=\s*["'][^"']*["']/g);
|
|||
|
if (attrMatches) {
|
|||
|
attributeStats.totalAttributes += attrMatches.length;
|
|||
|
|
|||
|
attrMatches.forEach(attr => {
|
|||
|
// Check for escaped content
|
|||
|
if (attr.includes('&') || attr.includes('<') || attr.includes('>') ||
|
|||
|
attr.includes('"') || attr.includes(''')) {
|
|||
|
attributeStats.escapedAttributes++;
|
|||
|
}
|
|||
|
|
|||
|
// Check for Unicode
|
|||
|
if (/[^\x00-\x7F]/.test(attr)) {
|
|||
|
attributeStats.unicodeAttributes++;
|
|||
|
}
|
|||
|
|
|||
|
// Check for numeric values
|
|||
|
if (/=\s*["']\d+(?:\.\d+)?["']/.test(attr)) {
|
|||
|
attributeStats.numericAttributes++;
|
|||
|
}
|
|||
|
|
|||
|
// Check for empty values
|
|||
|
if (/=\s*["']\s*["']/.test(attr)) {
|
|||
|
attributeStats.emptyAttributes++;
|
|||
|
}
|
|||
|
|
|||
|
// Extract attribute name
|
|||
|
const nameMatch = attr.match(/(\w+(?::\w+)?)\s*=/);
|
|||
|
if (nameMatch) {
|
|||
|
const attrName = nameMatch[1];
|
|||
|
attributeStats.commonAttributes.set(
|
|||
|
attrName,
|
|||
|
(attributeStats.commonAttributes.get(attrName) || 0) + 1
|
|||
|
);
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
processedCount++;
|
|||
|
} catch (error) {
|
|||
|
console.log(`Attribute parsing issue in ${file}:`, error.message);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
console.log(`Attribute corpus analysis (${processedCount} files):`);
|
|||
|
console.log(`- Total attributes: ${attributeStats.totalAttributes}`);
|
|||
|
console.log(`- Escaped attributes: ${attributeStats.escapedAttributes}`);
|
|||
|
console.log(`- Unicode attributes: ${attributeStats.unicodeAttributes}`);
|
|||
|
console.log(`- Numeric attributes: ${attributeStats.numericAttributes}`);
|
|||
|
console.log(`- Empty attributes: ${attributeStats.emptyAttributes}`);
|
|||
|
|
|||
|
const topAttributes = Array.from(attributeStats.commonAttributes.entries())
|
|||
|
.sort((a, b) => b[1] - a[1])
|
|||
|
.slice(0, 10);
|
|||
|
console.log('Top 10 attribute names:', topAttributes);
|
|||
|
|
|||
|
expect(processedCount).toBeGreaterThan(0);
|
|||
|
expect(attributeStats.totalAttributes).toBeGreaterThan(0);
|
|||
|
|
|||
|
const elapsed = performance.now() - startTime;
|
|||
|
performanceTracker.addMeasurement('corpus-attributes', elapsed);
|
|||
|
});
|
|||
|
|
|||
|
// Print performance summary
|
|||
|
performanceTracker.printSummary();
|
|||
|
|
|||
|
// Performance assertions
|
|||
|
const avgTime = performanceTracker.getAverageTime();
|
|||
|
expect(avgTime).toBeLessThan(120); // Attribute operations should be reasonably fast
|
|||
|
});
|
|||
|
|
|||
|
tap.start();
|