devicon icon indicating copy to clipboard operation
devicon copied to clipboard

[BUG]: Kotlin and Nodejs icon colors are mixed

Open Yue-plus opened this issue 1 month ago • 3 comments

I have searched through the issues and didn't find my problem.

  • [x] Confirm

Bug description

Image

Possible fixes or solutions

Modify the 'id' attribute of 'linearGradient'

Additional information

No response

Yue-plus avatar Nov 22 '25 01:11 Yue-plus

I'm trying to fix it

Yue-plus avatar Nov 22 '25 01:11 Yue-plus

There are still 119 ICONS that will encounter similar problems when used simultaneously 😂

Image

Yue-plus avatar Nov 22 '25 02:11 Yue-plus

This is a tool written by AI—it's messy but functional. Maybe we could rewrite it in Python and place it in .github/scripts?

Install the dependencies before use: npm install @types/xmldom xmldom

import * as fs from 'fs';
import * as path from 'path';
import { DOMParser, XMLSerializer } from 'xmldom';

// 定义搜索目录 - 假设脚本从项目根目录运行
const ICONS_DIR = path.join(process.cwd(), 'icons');

/**
 * 搜索包含 id="a" 的 SVG 文件
 * @param directory 要搜索的目录
 * @returns 找到的 SVG 文件路径数组
 */
function findSvgFilesWithIdA(directory: string): string[] {
  const svgFiles: string[] = [];

  // 递归搜索目录
  function searchDir(currentDir: string) {
    const files = fs.readdirSync(currentDir);

    for (const file of files) {
      const filePath = path.join(currentDir, file);
      const stat = fs.statSync(filePath);

      if (stat.isDirectory()) {
        searchDir(filePath); // 递归搜索子目录
      } else if (file.endsWith('.svg')) {
        // 读取文件内容检查是否包含 id="a"
        const content = fs.readFileSync(filePath, 'utf-8');
        if (content.includes('id="a"')) {
          svgFiles.push(filePath);
          console.log(`找到包含 id="a" 的文件: ${filePath}`);
        }
      }
    }
  }

  searchDir(directory);
  return svgFiles;
}

/**
 * 解析 SVG 内容并返回文档对象
 * @param content SVG 内容
 * @returns 解析后的文档对象
 */
function parseSvg(content: string): any {
  const parser = new DOMParser();
  // 移除注释以避免解析问题
  const contentWithoutComments = content.replace(/<!--[\s\S]*?-->/g, '');
  return parser.parseFromString(contentWithoutComments, 'text/xml');
}

/**
 * 将文档对象序列化为字符串
 * @param document 文档对象
 * @returns 序列化后的字符串
 */
function serializeSvg(document: any): string {
  const serializer = new XMLSerializer();
  return serializer.serializeToString(document);
}

/**
 * 格式化 SVG 文档(使用库的序列化功能)
 * @param content SVG 内容
 * @returns 格式化后的内容
 */
function formatSvg(content: string): string {
  const document = parseSvg(content);
  return serializeSvg(document);
}

/**
 * 对 SVG 文件中所有 id 属性添加文件名前缀
 * @param content SVG 内容
 * @param fileName 文件名(不含扩展名)
 * @returns 处理后的内容
 */
function updateIdAttributes(content: string, fileName: string): string {
  const document = parseSvg(content);
  const elementsWithId = document.querySelectorAll('[id]');

  for (let i = 0; i < elementsWithId.length; i++) {
    const element = elementsWithId[i];
    const id = element.getAttribute('id');
    if (id && !id.startsWith(`${fileName}-`)) {
      element.setAttribute('id', `${fileName}-${id}`);
    }
  }

  return serializeSvg(document);
}

/**
 * 对 CSS 文本中所有 id 选择器添加文件名前缀
 * @param cssText CSS 文本内容
 * @param fileName 文件名(不含扩展名)
 * @returns 处理后的 CSS 文本
 */
function updateIdSelectors(cssText: string, fileName: string): string {
  return cssText.replace(/#([a-zA-Z0-9_-]+)/g, (match: string, idValue: string) => {
    if (!idValue.startsWith(`${fileName}-`)) {
      return `#${fileName}-${idValue}`;
    }
    return match;
  });
}

/**
 * 对 SVG 文件中所有 id 选择器添加文件名前缀(针对完整 SVG 内容)
 * @param content SVG 内容
 * @param fileName 文件名(不含扩展名)
 * @returns 处理后的 SVG 内容
 */
function updateIdSelectorsInSvg(content: string, fileName: string): string {
  const document = parseSvg(content);
  const styleElements = document.getElementsByTagName('style');

  for (let i = 0; i < styleElements.length; i++) {
    const styleElement = styleElements[i];
    if (styleElement.firstChild && styleElement.firstChild.nodeType === 3) {
      // 修改 CSS 文本内容中的 id 选择器
      let cssText = styleElement.firstChild.nodeValue || '';
      cssText = updateIdSelectors(cssText, fileName);
      styleElement.firstChild.nodeValue = cssText;
    }
  }

  return serializeSvg(document);
}

/**
 * 处理单个 SVG 文件
 * @param filePath SVG 文件路径
 */
async function processSvgFile(filePath: string): Promise<void> {
  try {
    // 读取文件内容检查是否包含 id="a"
    const content = await fs.promises.readFile(filePath, 'utf8');

    if (content.includes('id="a"')) {
      const fileName = path.parse(filePath).name;
      console.log(`处理文件: ${filePath}`);

      // 使用 xmldom 解析 SVG
      const document = parseSvg(content);

      // 检查是否存在 id="a" 的元素
      let elementWithIdA = null;
      const allElements = document.getElementsByTagName('*');
      for (let i = 0; i < allElements.length; i++) {
        if (allElements[i].getAttribute('id') === 'a') {
          elementWithIdA = allElements[i];
          break;
        }
      }
      if (elementWithIdA) {
        console.log(`找到 id="a" 的元素,开始处理`);

        // 更新所有 id 属性(使用 getElementsByTagName 查找所有元素)
        const allElements = document.getElementsByTagName('*');
        const idMap: Record<string, string> = {};

        // 第一步:收集所有需要更新的id并创建映射
        for (let i = 0; i < allElements.length; i++) {
          const element = allElements[i];
          const id = element.getAttribute('id');
          if (id && !id.startsWith(`${fileName}-`)) {
            const newId = `${fileName}-${id}`;
            idMap[id] = newId;
          }
        }

        // 第二步:更新所有元素的id属性
        for (const oldId in idMap) {
          const element = document.getElementById(oldId);
          if (element) {
            element.setAttribute('id', idMap[oldId]);
          }
        }

        // 第三步:更新所有对这些id的引用(如fill, stroke, filter等属性)
        for (let i = 0; i < allElements.length; i++) {
          const element = allElements[i];
          const attributes = element.attributes;

          for (let j = 0; j < attributes.length; j++) {
            const attr = attributes[j];
            const attrName = attr.name;
            const value = attr.value;

            // 检查属性值是否包含url(#id)格式的引用
            if (value.includes('url(#')) {
              let updatedValue = value;
              for (const oldId in idMap) {
                // 使用字符串替换而不是正则表达式,避免转义问题
                const searchString = 'url(#' + oldId + ')';
                const replaceString = 'url(#' + idMap[oldId] + ')';
                while (updatedValue.includes(searchString)) {
                  updatedValue = updatedValue.replace(searchString, replaceString);
                }
              }
              if (updatedValue !== value) {
                element.setAttribute(attr.name, updatedValue);
              }
            }

            // 处理xlink:href属性中的直接id引用(如"#id"格式)
            if (attrName === 'xlink:href' || attrName === 'href') {
              if (value.startsWith('#')) {
                const idRef = value.substring(1); // 提取#后面的id
                if (idMap.hasOwnProperty(idRef)) {
                  element.setAttribute(attrName, '#' + idMap[idRef]);
                }
              }
            }
          }
        }

        // 更新 CSS 中的 id 选择器
        const styleElements = document.getElementsByTagName('style');
        for (let i = 0; i < styleElements.length; i++) {
          const styleElement = styleElements[i];
          if (styleElement.firstChild && styleElement.firstChild.nodeType === 3) {
            let cssText = styleElement.firstChild.nodeValue || '';
            // 直接使用 updateIdSelectors 函数处理 CSS 文本
            cssText = updateIdSelectors(cssText, fileName);
            styleElement.firstChild.nodeValue = cssText;
          }
        }

        // 序列化并写回文件
        const updatedContent = serializeSvg(document);
        await fs.promises.writeFile(filePath, updatedContent, 'utf8');
        console.log(`文件已更新: ${filePath}`);
      }
    }
  } catch (error) {
    console.error(`处理文件 ${filePath} 时出错:`, error);
  }
}

/**
 * 递归查找目录中所有的 SVG 文件
 * @param directory 要搜索的目录
 * @returns SVG 文件路径数组
 */
async function findSvgFilesRecursively(directory: string): Promise<string[]> {
  const svgFiles: string[] = [];

  const entries = await fs.promises.readdir(directory, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(directory, entry.name);

    if (entry.isDirectory()) {
      // 递归搜索子目录
      const subDirSvgFiles = await findSvgFilesRecursively(fullPath);
      svgFiles.push(...subDirSvgFiles);
    } else if (entry.isFile() && entry.name.endsWith('.svg')) {
      svgFiles.push(fullPath);
    }
  }

  return svgFiles;
}

/**
 * 主函数
 */
async function main() {
  console.log('开始搜索包含 id="a" 的 SVG 文件...');

  try {
    // 递归查找所有 SVG 文件
    const allSvgFiles = await findSvgFilesRecursively(ICONS_DIR);
    console.log(`共找到 ${allSvgFiles.length} 个 SVG 文件`);

    if (allSvgFiles.length > 0) {
      console.log('开始处理文件...');
      for (const filePath of allSvgFiles) {
        await processSvgFile(filePath);
      }
      console.log('所有文件处理完成!');
    }
  } catch (error) {
    console.error('执行过程中出错:', error);
  }
}

// 执行主函数
main();

Yue-plus avatar Nov 22 '25 06:11 Yue-plus