sheets
This commit is contained in:
@@ -25,11 +25,11 @@ const CONFIG = {
|
||||
} as const;
|
||||
|
||||
const EXCEL_FILES = [
|
||||
path.join(process.cwd(), 'data/excel/high-voltage.xlsx'),
|
||||
path.join(process.cwd(), 'data/excel/medium-voltage-KM.xlsx'),
|
||||
path.join(process.cwd(), 'data/excel/medium-voltage-KM 170126.xlsx'),
|
||||
path.join(process.cwd(), 'data/excel/low-voltage-KM.xlsx'),
|
||||
path.join(process.cwd(), 'data/excel/solar-cables.xlsx'),
|
||||
{ path: path.join(process.cwd(), 'data/excel/high-voltage.xlsx'), voltageType: 'high-voltage' },
|
||||
{ path: path.join(process.cwd(), 'data/excel/medium-voltage-KM.xlsx'), voltageType: 'medium-voltage' },
|
||||
{ path: path.join(process.cwd(), 'data/excel/medium-voltage-KM 170126.xlsx'), voltageType: 'medium-voltage' },
|
||||
{ path: path.join(process.cwd(), 'data/excel/low-voltage-KM.xlsx'), voltageType: 'low-voltage' },
|
||||
{ path: path.join(process.cwd(), 'data/excel/solar-cables.xlsx'), voltageType: 'solar' },
|
||||
] as const;
|
||||
|
||||
type MdxProduct = {
|
||||
@@ -144,12 +144,12 @@ function readDesignationsFromExcelFile(filePath: string): Map<string, string> {
|
||||
return out;
|
||||
}
|
||||
|
||||
function loadAllExcelDesignations(): Map<string, string> {
|
||||
const out = new Map<string, string>();
|
||||
for (const filePath of EXCEL_FILES) {
|
||||
const m = readDesignationsFromExcelFile(filePath);
|
||||
function loadAllExcelDesignations(): Map<string, { designation: string; voltageType: string }> {
|
||||
const out = new Map<string, { designation: string; voltageType: string }>();
|
||||
for (const file of EXCEL_FILES) {
|
||||
const m = readDesignationsFromExcelFile(file.path);
|
||||
Array.from(m.entries()).forEach(([k, v]) => {
|
||||
if (!out.has(k)) out.set(k, v);
|
||||
if (!out.has(k)) out.set(k, { designation: v, voltageType: file.voltageType });
|
||||
});
|
||||
}
|
||||
return out;
|
||||
@@ -162,10 +162,10 @@ async function loadProductsFromExcelAndMdx(locale: 'en' | 'de'): Promise<Product
|
||||
const products: ProductData[] = [];
|
||||
let id = 1;
|
||||
|
||||
Array.from(excelDesignations.entries()).forEach(([key, designation]) => {
|
||||
Array.from(excelDesignations.entries()).forEach(([key, data]) => {
|
||||
const mdx = mdxIndex.get(key) || null;
|
||||
|
||||
const title = mdx?.title || designation;
|
||||
const title = mdx?.title || data.designation;
|
||||
const slug =
|
||||
mdx?.slug ||
|
||||
title
|
||||
@@ -191,6 +191,7 @@ async function loadProductsFromExcelAndMdx(locale: 'en' | 'de'): Promise<Product
|
||||
locale,
|
||||
categories: (mdx?.categories || []).map(name => ({ name })),
|
||||
attributes: [],
|
||||
voltageType: data.voltageType,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -209,8 +210,18 @@ async function processChunk(products: ProductData[], chunkIndex: number, totalCh
|
||||
const locale = (product.locale || 'en') as 'en' | 'de';
|
||||
const buffer = await generateDatasheetPdfBuffer({ product, locale });
|
||||
const fileName = generateFileName(product, locale);
|
||||
fs.writeFileSync(path.join(CONFIG.outputDir, fileName), buffer);
|
||||
console.log(`✓ ${locale.toUpperCase()}: ${fileName}`);
|
||||
|
||||
// Determine subfolder based on voltage type
|
||||
const voltageType = (product as any).voltageType || 'other';
|
||||
const subfolder = path.join(CONFIG.outputDir, voltageType);
|
||||
|
||||
// Create subfolder if it doesn't exist
|
||||
if (!fs.existsSync(subfolder)) {
|
||||
fs.mkdirSync(subfolder, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(subfolder, fileName), buffer);
|
||||
console.log(`✓ ${locale.toUpperCase()}: ${voltageType}/${fileName}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 25));
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed to process product ${product.id}:`, error);
|
||||
|
||||
@@ -283,6 +283,125 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
|
||||
}
|
||||
}
|
||||
|
||||
function translateAbbreviation(abbrev: string, description: string, locale: 'en' | 'de'): string {
|
||||
const normalizedDesc = normalizeValue(description);
|
||||
if (!normalizedDesc) return description;
|
||||
|
||||
// German translations for common abbreviations
|
||||
if (locale === 'de') {
|
||||
switch (abbrev) {
|
||||
case 'DI':
|
||||
return 'Durchmesser über Isolation';
|
||||
case 'RI':
|
||||
return 'Widerstand Leiter';
|
||||
case 'Wi':
|
||||
return 'Isolationsdicke';
|
||||
case 'Ibl':
|
||||
return 'Strombelastbarkeit Luft';
|
||||
case 'Ibe':
|
||||
return 'Strombelastbarkeit Erde';
|
||||
case 'Wm':
|
||||
return 'Manteldicke';
|
||||
case 'Rbv':
|
||||
return 'Biegeradius';
|
||||
case 'Fzv':
|
||||
return 'Zugkraft';
|
||||
case 'G':
|
||||
return 'Gewicht';
|
||||
case 'Ik_cond':
|
||||
return 'Kurzschlussstrom Leiter';
|
||||
case 'Ik_screen':
|
||||
return 'Kurzschlussstrom Schirm';
|
||||
case 'Ø':
|
||||
return 'Außen-Ø';
|
||||
case 'Cond':
|
||||
return 'Leiter';
|
||||
case 'shape':
|
||||
return 'Form';
|
||||
case 'cap':
|
||||
return 'Kapazität';
|
||||
case 'X':
|
||||
return 'Reaktanz';
|
||||
case 'test_volt':
|
||||
return 'Prüfspannung';
|
||||
case 'rated_volt':
|
||||
return 'Nennspannung';
|
||||
case 'temp_range':
|
||||
return 'Temperaturbereich';
|
||||
case 'max_op_temp':
|
||||
return 'Leitertemperatur (max.)';
|
||||
case 'max_sc_temp':
|
||||
return 'Kurzschlusstemperatur (max.)';
|
||||
case 'min_store_temp':
|
||||
return 'Minimale Lagertemperatur';
|
||||
case 'min_lay_temp':
|
||||
return 'Minimale Verlegetemperatur';
|
||||
case 'cpr':
|
||||
return 'CPR-Klasse';
|
||||
case 'flame':
|
||||
return 'Flammhemmend';
|
||||
default:
|
||||
return normalizedDesc;
|
||||
}
|
||||
}
|
||||
|
||||
// English translations for common abbreviations
|
||||
switch (abbrev) {
|
||||
case 'DI':
|
||||
return 'Diameter over insulation';
|
||||
case 'RI':
|
||||
return 'DC resistance';
|
||||
case 'Wi':
|
||||
return 'Insulation thickness';
|
||||
case 'Ibl':
|
||||
return 'Current rating in air';
|
||||
case 'Ibe':
|
||||
return 'Current rating in ground';
|
||||
case 'Wm':
|
||||
return 'Sheath thickness';
|
||||
case 'Rbv':
|
||||
return 'Bending radius';
|
||||
case 'Fzv':
|
||||
return 'Pulling force';
|
||||
case 'G':
|
||||
return 'Weight';
|
||||
case 'Ik_cond':
|
||||
return 'Short-circuit current conductor';
|
||||
case 'Ik_screen':
|
||||
return 'Short-circuit current screen';
|
||||
case 'Ø':
|
||||
return 'Outer diameter';
|
||||
case 'Cond':
|
||||
return 'Conductor';
|
||||
case 'shape':
|
||||
return 'Shape';
|
||||
case 'cap':
|
||||
return 'Capacitance';
|
||||
case 'X':
|
||||
return 'Reactance';
|
||||
case 'test_volt':
|
||||
return 'Test voltage';
|
||||
case 'rated_volt':
|
||||
return 'Rated voltage';
|
||||
case 'temp_range':
|
||||
return 'Operating temperature range';
|
||||
case 'max_op_temp':
|
||||
return 'Max operating temperature';
|
||||
case 'max_sc_temp':
|
||||
return 'Max short-circuit temperature';
|
||||
case 'min_store_temp':
|
||||
return 'Min storage temperature';
|
||||
case 'min_lay_temp':
|
||||
return 'Min laying temperature';
|
||||
case 'cpr':
|
||||
return 'CPR class';
|
||||
case 'flame':
|
||||
return 'Flame retardant';
|
||||
default:
|
||||
return normalizedDesc;
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeOptions(options: string[] | undefined): string {
|
||||
const vals = (options || []).map(normalizeValue).filter(Boolean);
|
||||
if (vals.length === 0) return '';
|
||||
@@ -658,9 +777,10 @@ function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
|
||||
for (const col of abbrevCols) {
|
||||
const description = normalizeValue(String(mv.headerRow[col.colKey] || ''));
|
||||
if (description && description !== col.colKey) {
|
||||
const translatedDescription = translateAbbreviation(col.colKey, description, args.locale);
|
||||
legendItems.push({
|
||||
label: col.colKey,
|
||||
value: description,
|
||||
value: translatedDescription,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ProductData {
|
||||
name: string;
|
||||
options: string[];
|
||||
}>;
|
||||
voltageType?: string;
|
||||
}
|
||||
|
||||
export type KeyValueItem = { label: string; value: string; unit?: string };
|
||||
|
||||
@@ -41,13 +41,13 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
|
||||
</View>
|
||||
|
||||
{model.product.descriptionText ? (
|
||||
<Section title={model.labels.description}>
|
||||
<Section title={model.labels.description} minPresenceAhead={24}>
|
||||
<Text style={styles.body}>{model.product.descriptionText}</Text>
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
{model.technicalItems.length ? (
|
||||
<Section title={model.labels.technicalData}>
|
||||
<Section title={model.labels.technicalData} minPresenceAhead={24}>
|
||||
<KeyValueGrid items={model.technicalItems} />
|
||||
</Section>
|
||||
) : null}
|
||||
@@ -56,13 +56,15 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
|
||||
{/*
|
||||
Render all voltage sections in a single flow so React-PDF can paginate naturally.
|
||||
This avoids hard page breaks that waste remaining whitespace at the bottom of a page.
|
||||
Each table section has break={false} to prevent breaking within individual tables,
|
||||
but the overall flow allows tables to move to the next page if needed.
|
||||
*/}
|
||||
<Page size="A4" style={styles.page}>
|
||||
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
|
||||
<Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
|
||||
|
||||
{model.voltageTables.map((t: DatasheetVoltageTable) => (
|
||||
<View key={t.voltageLabel} style={{ marginBottom: 14 }} break={false}>
|
||||
<View key={t.voltageLabel} style={{ marginBottom: 14 }} break={false} minPresenceAhead={24}>
|
||||
<Text style={styles.sectionTitle}>{`${model.labels.crossSection} — ${t.voltageLabel}`}</Text>
|
||||
|
||||
<DenseTable table={{ columns: t.columns, rows: t.rows }} firstColLabel={firstColLabel} />
|
||||
@@ -70,7 +72,7 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
|
||||
))}
|
||||
|
||||
{model.legendItems.length ? (
|
||||
<Section title={model.locale === 'de' ? 'ABKÜRZUNGEN' : 'ABBREVIATIONS'}>
|
||||
<Section title={model.locale === 'de' ? 'ABKÜRZUNGEN' : 'ABBREVIATIONS'} minPresenceAhead={24}>
|
||||
<KeyValueGrid items={model.legendItems} />
|
||||
</Section>
|
||||
) : null}
|
||||
|
||||
@@ -141,7 +141,7 @@ export function DenseTable(props: {
|
||||
const headerFontSize = cols.length >= 14 ? 5.7 : cols.length >= 12 ? 5.9 : cols.length >= 10 ? 6.2 : 6.6;
|
||||
|
||||
return (
|
||||
<View style={styles.tableWrap} break={false}>
|
||||
<View style={styles.tableWrap} break={false} minPresenceAhead={24}>
|
||||
<View style={styles.tableHeader} wrap={false}>
|
||||
<View style={{ width: cfgW }}>
|
||||
<Text
|
||||
|
||||
@@ -26,17 +26,18 @@ export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactEleme
|
||||
key={`${left.label}-${rowIndex}`}
|
||||
style={[styles.kvRow, rowIndex % 2 === 0 ? styles.kvRowAlt : null, isLast ? styles.kvRowLast : null]}
|
||||
wrap={false}
|
||||
minPresenceAhead={12}
|
||||
>
|
||||
<View style={[styles.kvCell, { width: '23%' }]}>
|
||||
<View style={[styles.kvCell, { width: '18%' }]}>
|
||||
<Text style={styles.kvLabelText}>{left.label}</Text>
|
||||
</View>
|
||||
<View style={[styles.kvCell, styles.kvMidDivider, { width: '27%' }]}>
|
||||
<View style={[styles.kvCell, styles.kvMidDivider, { width: '32%' }]}>
|
||||
<Text style={styles.kvValueText}>{leftValue}</Text>
|
||||
</View>
|
||||
<View style={[styles.kvCell, { width: '23%' }]}>
|
||||
<View style={[styles.kvCell, { width: '18%' }]}>
|
||||
<Text style={styles.kvLabelText}>{right?.label || ''}</Text>
|
||||
</View>
|
||||
<View style={[styles.kvCell, { width: '27%' }]}>
|
||||
<View style={[styles.kvCell, { width: '32%' }]}>
|
||||
<Text style={styles.kvValueText}>{rightValue}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
81
scripts/scrape-faber-kabel.ts
Normal file
81
scripts/scrape-faber-kabel.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
interface FaberKabelData {
|
||||
url: string;
|
||||
verwendung: string;
|
||||
technischeDaten: { [key: string]: string };
|
||||
}
|
||||
|
||||
async function scrapeFaberKabel(url: string): Promise<FaberKabelData> {
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
const $ = cheerio.load(response.data);
|
||||
|
||||
// Extract Verwendung
|
||||
const verwendung = $('#applicationdata .text-module--light').text().trim();
|
||||
|
||||
// Extract Technische Daten
|
||||
const technischeDaten: { [key: string]: string } = {};
|
||||
$('#technicaldata table.attributes tr').each((i, el) => {
|
||||
const tds = $(el).find('td');
|
||||
if (tds.length === 2) {
|
||||
const key = $(tds[0]).text().trim();
|
||||
const value = $(tds[1]).text().trim();
|
||||
if (key && value) {
|
||||
technischeDaten[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
url,
|
||||
verwendung,
|
||||
technischeDaten,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error scraping:', url, error);
|
||||
return {
|
||||
url,
|
||||
verwendung: '',
|
||||
technischeDaten: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function scrapeAll(urls: string[]): Promise<FaberKabelData[]> {
|
||||
const results: FaberKabelData[] = [];
|
||||
for (const url of urls) {
|
||||
console.log('Scraping:', url);
|
||||
const data = await scrapeFaberKabel(url);
|
||||
results.push(data);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// List of Faber Kabel URLs for KLZ products
|
||||
const faberKabelUrls = [
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-N2XS-FL-2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-N2XS2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-N2XSF2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-N2XSY/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-NA2XS2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-NA2XSF2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-NA2XS-FL-2Y/',
|
||||
'https://shop.faberkabel.de/Starkstromkabel-1-30-kV/Mittelspannungskabel/Mittelspannungskabel-NA2XSY/',
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const data = await scrapeAll(faberKabelUrls);
|
||||
const outputPath = path.join(__dirname, '..', 'faber-kabel-data.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
||||
console.log('Data saved to:', outputPath);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
export { scrapeFaberKabel, scrapeAll };
|
||||
Reference in New Issue
Block a user