Site icon John Maeda’s Blog

Extracting Icon Taxonomies from Azure Portal

I recently had to do this and want to remember how to do it in the future. Well, I know it’s not durable but …

First, go to Azure Portal and into dev tools and run

Assuming you’re viewing all services via the URL:

https://portal.azure.com/#allservices/category/All

Then, in 2024 July 6 you were able to do use this JS code to extract a lot of stuff that’ll make you happy:

// Function to collect and combine all unique defs
function collectUniqueDefs() {
    const uniqueDefs = new Map();
    const defsElements = document.querySelectorAll('defs');

    defsElements.forEach(defs => {
        defs.childNodes.forEach(node => {
            if (node.nodeType === Node.ELEMENT_NODE) {
                uniqueDefs.set(node.id, node);
            }
        });
    });

    // Create a combined defs element
    const combinedDefs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
    uniqueDefs.forEach(node => {
        combinedDefs.appendChild(node.cloneNode(true));
    });

    return combinedDefs.outerHTML;
}

// Function to get the full SVG markup based on the <use> href attribute
function getFullSvg(useElement, combinedDefs) {
    if (!useElement) return null;

    const href = useElement.getAttribute('href') || useElement.getAttribute('xlink:href');
    const symbolId = href.startsWith('#') ? href.substring(1) : href;
    const symbolElement = document.getElementById(symbolId);

    if (symbolElement) {
        // Create a new SVG element with the content of the symbol
        const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
        if (useElement.hasAttribute('height')) {
            svgElement.setAttribute('height', useElement.getAttribute('height'));
        }
        if (useElement.hasAttribute('width')) {
            svgElement.setAttribute('width', useElement.getAttribute('width'));
        }
        if (useElement.hasAttribute('aria-hidden')) {
            svgElement.setAttribute('aria-hidden', useElement.getAttribute('aria-hidden'));
        }
        if (useElement.hasAttribute('role')) {
            svgElement.setAttribute('role', useElement.getAttribute('role'));
        }
        if (useElement.hasAttribute('focusable')) {
            svgElement.setAttribute('focusable', useElement.getAttribute('focusable'));
        }

        // Include the combined defs section once in the SVG
        svgElement.innerHTML = combinedDefs + symbolElement.innerHTML;

        // Return the outer HTML of the new SVG element
        return svgElement.outerHTML;
    }

    return useElement.outerHTML; // Fallback to original <use> element if symbol not found
}

// Function to download data as a JSON file
function downloadJSON(data, filename) {
    const blob = new Blob([data], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

// Function to extract service names and their corresponding icons
function extractServiceIcons(batchSize = 50) {
    const services = [];
    const serviceItems = Array.from(document.querySelectorAll('.fxs-sidebar-flyout-allservices-list > li')); // List of all service items

    let currentCategory = '';

    // Collect the combined defs
    const combinedDefs = collectUniqueDefs();

    for (let i = 0; i < serviceItems.length; i += batchSize) {
        const batch = serviceItems.slice(i, i + batchSize);

        batch.forEach(item => {
            if (item.classList.contains('fxs-sidebar-item-category')) {
                // This is a category item
                currentCategory = item.querySelector('h3').innerText.trim();
            } else if (item.classList.contains('fxs-sidebar-flyout-item-content')) {
                const serviceName = item.querySelector('.fxs-sidebar-label-name').innerText;
                const useElement = item.querySelector('.fxs-sidebar-flyout-icon use');
                const fullSvg = getFullSvg(useElement, combinedDefs); // Get the full SVG markup
                services.push({
                    category: currentCategory,
                    name: serviceName,
                    icon: fullSvg
                });
            }
            // Ignore items with 'fxs-sidebar-flyout-noresults' class or those with id starting with '_weave_e_'
        });

        // Download the batch as a JSON file
        const batchData = JSON.stringify(services);
        const batchNumber = i / batchSize + 1;
        downloadJSON(batchData, `services_batch_${batchNumber}.json`);
        services.length = 0; // Clear the array for the next batch

        // Process in batches to avoid overload
        if (i + batchSize < serviceItems.length) {
            console.log(`Processed and downloaded batch ${batchNumber}`);
        }
    }
}

// Extract the service icons and names and download in parts
extractServiceIcons();

You may need to enable pasting code into the console when it tells you to be careful of doing this

allow pasting

Wait a bit — minutes, not seconds. And the files will come over to you one at a time. In my case they were numbered 1 thru 10, and the program below has that hardwired:

import os
import json
import re

# Function to clean up the SVG content
def clean_svg_content(svg_content):
    return svg_content.replace('\\"', '"') if svg_content else ''

# Function to sanitize file names
def sanitize_filename(name):
    # Remove special characters, truncate at the first hyphen, and remove extra spaces
    sanitized = re.sub(r'[^\w\s-]', '', name)
    sanitized = sanitized.split('-')[0].strip()
    sanitized = re.sub(r'\s+', ' ', sanitized)  # Remove extra spaces
    return sanitized

# Create the directory structure
base_dir = 'AzureServicesIcons'
if not os.path.exists(base_dir):
    os.makedirs(base_dir)

# List to hold master data
master_data = []

# Loop through the batch files
for i in range(1, 11):
    batch_file = f'services_batch_{i}.json'
    
    print(f"Processing {batch_file}...")

    with open(batch_file, 'r') as file:
        data = json.load(file)
        
        for entry in data:
            category = entry['category']
            name = entry['name']
            svg_content = entry.get('icon', None)
            
            if svg_content is None:
                print(f"Warning: No icon found for {name} in {category}")
                continue

            sanitized_name = sanitize_filename(name)
            cleaned_svg_content = clean_svg_content(svg_content)
            
            # Create category directory if it doesn't exist
            category_dir = os.path.join(base_dir, category)
            if not os.path.exists(category_dir):
                os.makedirs(category_dir)
            
            # Define the SVG file path
            svg_file_path = os.path.join(category_dir, f"{sanitized_name}.svg")
            
            # Write the SVG content to the file
            with open(svg_file_path, 'w') as svg_file:
                svg_file.write(cleaned_svg_content)
            
            print(f"Saved SVG for {name} in {category}")
            
            # Add entry to the master data
            master_data.append({
                'category': category,
                'name': name,
                'svg_file': svg_file_path
            })

# Write the master JSON file
master_file = os.path.join(base_dir, 'master_data.json')
with open(master_file, 'w') as master_json_file:
    json.dump(master_data, master_json_file, indent=4)

print(f"Master data written to {master_file}")

This should move all the icons into a directory with many subdirectories and a master JSON file to work with. Awesome, huh?

Exit mobile version