devicon
devicon copied to clipboard
[BUG]: Kotlin and Nodejs icon colors are mixed
I have searched through the issues and didn't find my problem.
- [x] Confirm
Bug description
Possible fixes or solutions
Modify the 'id' attribute of 'linearGradient'
Additional information
No response
I'm trying to fix it
There are still 119 ICONS that will encounter similar problems when used simultaneously 😂
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();