Voici votre URL de partage https://sharemycode.io/c/54fbed8 (Cliquer pour copier) (Copié)

Nom du fichier : RedundanT

class ProductDataExtractor {
    constructor(document) {
        this.document = document;
    }

    extractProductData() {
        const productElements = Array.from(this.document.querySelectorAll('.product-technical-data'));
        return productElements.map(element => this.extractProductInfo(element));
    }

    extractProductInfo(productElement) {
        const getTextContent = (xpath) => {
            const element = this.document.evaluate(xpath, productElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            return element ? element.textContent.trim().toUpperCase() : null;
        };

        const variantId = productElement.getAttribute('data-product-id');
        const sku = getTextContent(".//span[contains(text(),'SKU')]/following-sibling::span");
        const colorDescription = getTextContent(".//span[contains(text(),'olor')]/following-sibling::span");
        const sizeDescription = getTextContent(".//span[contains(text(),'roduct') or contains(text(),'rodotto')]/following-sibling::span");
        const groupTension = getTextContent(".//span[contains(text(),'tension') or contains(text(),'fonte')]/following-sibling::span");
        const composition = getTextContent(".//span[contains(text(),'aterial')]/following-sibling::span");

        let cleanedSizeDescription;
        if (sizeDescription) {
            const sizeMatch = sizeDescription.match(/(SMALL|MEDIUM|LARGE|PICCOLA|MEDIA|GRANDE)/);
            if (sizeMatch) {
                cleanedSizeDescription = groupTension ? `${sizeMatch[0]} ${groupTension}` : sizeMatch[0];
                cleanedSizeDescription = cleanedSizeDescription.replace(/INTEGRATED LED|SCHEDA LED|LED INTEGRATO/i, 'LED');
            }
        }

        return {
            variantId,
            sku,
            colorDescription: colorDescription || '',
            sizeDescription: cleanedSizeDescription || sizeDescription,
            composition: composition || ''
        };
    }

    extractJsonData(xpath, fallbackXpath) {
        const getJsonFromScript = (xpath) => {
            const scriptNode = this.document.evaluate(xpath, this.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (scriptNode) {
                const scriptContent = scriptNode.textContent;
                const jsonText = scriptContent.replace(/^\s*<script[^>]*>/, '').replace(/<\/script>\s*$/, '');
                return JSON.parse(jsonText);
            }
            return null;
        };

        return getJsonFromScript(xpath) || getJsonFromScript(fallbackXpath);
    }

    extractRendererData(jsonData, xpath, fallbackXpath) {
        if (!jsonData) return null;

        const rendererData = jsonData["[data-role=swatch-options]"]?.["Magento_Swatches/js/swatch-renderer"];
        if (rendererData) return rendererData;

        const data = jsonData["*"]?.["Magento_Catalog/js/product/view/provider"]?.data;
        return data ? Object.values(data.items)[0] : null;
    }

extractProductDetails() {
    const productNameElement = this.document.querySelector("main[id='maincontent'] div[class*='page'][class*='product'] [itemprop='name']");
    const productName = productNameElement ? productNameElement.textContent : '';

    const selector = this.document.querySelector("div[class*='product'][class*='info'] [class*='product'][class*='description'] > p");
    const selector2 = this.document.evaluate(
        "//div[contains(@class,'product-technical')]//div[contains(@class,'tabs-content')]/div/p[contains(text(),' ')]",
        this.document,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
    ).singleNodeValue;

    const description = selector ? selector.textContent : (selector2 ? selector2.textContent : '');

    return { productName, description };
}

    extractGalleryImages() {
        const productSelector = this.document.evaluate(
            '//script[contains(.,"[data-gallery-role=gallery-placeholder]") and contains(.,"thumb")]',
            this.document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        ).singleNodeValue;

        if (!productSelector) return [];

        const productJson = JSON.parse(productSelector.textContent);
        const galleryData = productJson["[data-gallery-role=gallery-placeholder]"]?.["mage/gallery/gallery"]?.data || [];

        return galleryData.map(data => data.thumb.replace(/\/cache\/.*?\//, '').split('?')[0]);
    }

    formatProductData(productData, renderer, sizeMap) {
        const sortedData = this.sortProductData(productData);
        const { productName, description } = this.extractProductDetails();

        return {
            productId: renderer?.jsonConfig?.productId || renderer?.id,
            productName,
            description,
            variants: Object.keys(sortedData).map(color => ({
                color,
                sizes: sortedData[color].map(item => {
                    const sizeDescription = sizeMap[item.variantId]?.size.toUpperCase() || item.sizeDescription.toUpperCase() || '';
                    const lightSource = sizeMap[item.variantId]?.lightSource.toUpperCase() || ''; // Convertir en majuscules
                    const variantData = renderer?.jsonConfig?.optionPrices?.[item.variantId] || renderer?.price_info || {};
                    const brut = variantData.oldPrice ? `${renderer.jsonConfig.priceFormat.pattern.replace('%s', variantData.oldPrice.amount)}` : `${variantData.regular_price} ${renderer.currency_code}`;
                    const net = variantData.finalPrice ? `${renderer.jsonConfig.priceFormat.pattern.replace('%s', variantData.finalPrice.amount)}` : `${variantData.final_price} ${renderer.currency_code}`;
                    const images = renderer?.jsonConfig?.images?.[item.variantId]?.map(image => image.img) || this.extractGalleryImages();
                    const availability = item.variantId in (renderer?.jsonConfig?.index || {});
                    const availabilityFallback = renderer?.is_available || false;

                    return {
                        [`${sizeDescription} ${lightSource}`]: [{
                            variantId: item.variantId,
                            sku: item.sku,
                            brut,
                            net,
                            images,
                            availability: availability || availabilityFallback,
                            composition: item.composition
                        }]
                    };
                })
            }))
        };
    }

    sortProductData(productData) {
        return productData.reduce((acc, item) => {
            if (!acc[item.colorDescription]) acc[item.colorDescription] = [];
            acc[item.colorDescription].push(item);
            return acc;
        }, {});
    }

    getFinalJson() {
        const productData = this.extractProductData();
        const jsonData = this.extractJsonData("//script[contains(text(), 'json')]", "//script[contains(text(), '/js/product')]");
        const renderer = this.extractRendererData(jsonData);

        const sizeMap = this.extractSizesFromRenderer(renderer);

        const formattedData = this.formatProductData(productData, renderer, sizeMap);
        return JSON.stringify(formattedData, null, 2);
    }

    extractSizesFromRenderer(renderer) {
        const sizeMap = {};
        if (!renderer || !renderer.jsonConfig) return sizeMap;
        const attributes = renderer.jsonConfig.attributes;

        if (!attributes) return sizeMap;
        const colorAttribute = attributes["856"];
        const sizeAttribute = attributes["547"];
        const lightSourceAttribute = attributes["1653"];

        if (!colorAttribute || !sizeAttribute) return sizeMap;

        sizeAttribute.options.forEach(sizeOption => {
            colorAttribute.options.forEach(colorOption => {
                colorOption.products.forEach(sku => {
                    const indexEntry = renderer.jsonConfig.index[sku];
                    if (indexEntry && indexEntry["856"] === colorOption.id && indexEntry["547"] === sizeOption.id) {
                        const lightSource = lightSourceAttribute ?
                            (renderer.jsonConfig.index[sku]["1653"] ? lightSourceAttribute.options.find(source => source.id === renderer.jsonConfig.index[sku]["1653"]).label.toUpperCase() : '')
                            : '';
                        sizeMap[sku] = { size: sizeOption.label.toUpperCase(), lightSource };
                    }
                });
            });
        });

        return sizeMap;
    }
}

const extractor = new ProductDataExtractor(document);
const finalJson = JSON.parse(extractor.getFinalJson());

const jsonContainer = document.createElement('pre');
jsonContainer.textContent = JSON.stringify(finalJson, null, 2);
jsonContainer.id = 'product-data-json';
jsonContainer.style.whiteSpace = 'pre-wrap';
document.body.appendChild(jsonContainer);


`/oort-da-esterno-config-lampada-da-terra-curva/lighting/wall-lamps/tropico-config-wall-and-ceiling-lamp/ricambi/diffusore-sillaba-media-r277501v00op00/illuminazione/lampade-da-parete/frenesi-lampada-a-parete-trasparente-f446745550tcle/ricambi/diffusore-sillaba-piccola-r277500voo0p00`

Informations

Cet extrait a été créé le 2 sept. 2024 à 15:27:26

Cet extrait expire le 2 oct. 2024 à 15:27:26

Langage : javascript

Logo javascript

Link

Voici votre URL de partage : https://sharemycode.io/c/54fbed8 Copié