#!/usr/bin/env python3
"""
SVG转DrawIO转换器
将SVG格式的软件架构分层图转换为draw.io可编辑格式
支持矩形、椭圆、菱形、文本等元素的转换
"""

import xml.etree.ElementTree as ET
import re
import sys
import os


# ============================================================
# 常量与命名空间定义
# ============================================================

SVG_NS = '{http://www.w3.org/2000/svg}'  # SVG默认命名空间


# ============================================================
# 工具函数：XML与属性处理
# ============================================================

def strip_ns(tag):
    """移除XML标签的命名空间前缀
    例如: '{http://www.w3.org/2000/svg}rect' -> 'rect'
    """
    if tag.startswith('{'):
        return tag.split('}', 1)[1]
    return tag


def get_attr(elem, name):
    """安全获取元素属性值，不存在则返回None"""
    return elem.get(name)


# ============================================================
# 工具函数：SVG变换解析
# ============================================================

def parse_translate(transform):
    """解析SVG transform属性中的translate(x,y)偏移量
    支持格式: translate(155,85) 或 translate(155)
    返回: (x, y) 元组，默认为 (0, 0)
    """
    if not transform:
        return 0.0, 0.0
    # 匹配 translate(x,y)
    m = re.search(r'translate\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)', transform)
    if m:
        return float(m.group(1)), float(m.group(2))
    # 匹配 translate(x)
    m = re.search(r'translate\(\s*([-\d.]+)\s*\)', transform)
    if m:
        return float(m.group(1)), 0.0
    return 0.0, 0.0


def parse_matrix_rotation(transform):
    """解析SVG matrix变换，检测90度旋转
    matrix(a,b,c,d,e,f) 当 a≈0, b≈1, c≈-1, d≈0 时为顺时针90度旋转
    返回: (is_90_rotation, e, f) 或 (False, 0, 0)
    """
    if not transform:
        return False, 0.0, 0.0
    m = re.search(
        r'matrix\(\s*([-\d.e+]+)\s*,\s*([-\d.e+]+)\s*,\s*'
        r'([-\d.e+]+)\s*,\s*([-\d.e+]+)\s*,\s*'
        r'([-\d.e+]+)\s*,\s*([-\d.e+]+)\s*\)',
        transform
    )
    if not m:
        return False, 0.0, 0.0
    a, b, c, d = float(m.group(1)), float(m.group(2)), float(m.group(3)), float(m.group(4))
    e, f = float(m.group(5)), float(m.group(6))
    # 检测90度旋转条件
    if abs(a) < 0.01 and abs(b - 1) < 0.01 and abs(c + 1) < 0.01 and abs(d) < 0.01:
        return True, e, f
    return False, 0.0, 0.0


# ============================================================
# 工具函数：SVG路径解析
# ============================================================

def _extract_path_points(d):
    """从SVG路径d属性中提取所有坐标点
    返回: [(x1,y1), (x2,y2), ...] 列表
    """
    if not d:
        return []
    nums = [float(x) for x in re.findall(r'[-\d.]+', d)]
    return [(nums[i], nums[i + 1]) for i in range(0, len(nums) - 1, 2)]


def parse_path_rect(d):
    """从路径数据中解析矩形尺寸
    路径格式: M 0 H L W H L W 0 L 0 0 L 0 H
    返回: (width, height) 或 None
    """
    points = _extract_path_points(d)
    if len(points) < 3:
        return None
    xs = [p[0] for p in points]
    ys = [p[1] for p in points]
    w = max(xs) - min(xs)
    h = max(ys) - min(ys)
    if w < 1 or h < 1:
        return None
    return (w, h)


def parse_path_ellipse(d):
    """从路径数据中解析椭圆的包围盒
    路径包含C(贝塞尔曲线)指令
    返回: (x_min, y_min, width, height) 或 None
    """
    if 'C' not in d:
        return None
    points = _extract_path_points(d)
    if len(points) < 4:
        return None
    xs = [p[0] for p in points]
    ys = [p[1] for p in points]
    x_min, x_max = min(xs), max(xs)
    y_min, y_max = min(ys), max(ys)
    w = x_max - x_min
    h = y_max - y_min
    if w < 1 or h < 1:
        return None
    return (x_min, y_min, w, h)


def parse_path_diamond(d):
    """从路径数据中解析菱形的包围盒
    路径格式: M x1 y1 L x2 y2 L x3 y3 L x4 y4 L x1 y1
    返回: (x_min, y_min, width, height) 或 None
    """
    points = _extract_path_points(d)
    if len(points) < 4:
        return None
    xs = [p[0] for p in points]
    ys = [p[1] for p in points]
    w = max(xs) - min(xs)
    h = max(ys) - min(ys)
    if w < 1 or h < 1:
        return None
    return (min(xs), min(ys), w, h)


def classify_path(d):
    """根据路径数据判断形状类型并提取尺寸
    返回: ('rect', (w, h)) 或 ('ellipse', (x, y, w, h)) 
          或 ('diamond', (x, y, w, h)) 或 None
    """
    if not d:
        return None

    # 包含C命令 → 可能是椭圆
    if 'C' in d:
        result = parse_path_ellipse(d)
        if result:
            return ('ellipse', result)

    # 提取所有点用于判断
    points = _extract_path_points(d)
    if len(points) < 3:
        return None

    # 判断是矩形还是菱形
    xs = sorted(set(round(p[0], 1) for p in points))
    ys = sorted(set(round(p[1], 1) for p in points))

    # 矩形: 只有2个不同x值和2个不同y值
    if len(xs) <= 2 and len(ys) <= 2:
        result = parse_path_rect(d)
        if result:
            return ('rect', result)
    else:
        # 非矩形且无曲线 → 菱形
        if 'C' not in d:
            result = parse_path_diamond(d)
            if result:
                return ('diamond', result)

    # 兜底: 尝试解析为矩形
    result = parse_path_rect(d)
    if result:
        return ('rect', result)
    return None


# ============================================================
# 工具函数：文本提取
# ============================================================

def extract_text_lines(text_elem):
    """从text元素中提取文本行
    每个一级tspan视为一行，内层tspan文本合并
    返回: ['行1', '行2', ...] 列表
    """
    lines = []
    # 遍历text的直接tspan子元素（每行一个）
    for child in text_elem:
        if strip_ns(child.tag) != 'tspan':
            continue
        # 收集这个tspan内所有文本片段
        parts = _collect_inner_text(child)
        line = ' '.join(parts).strip()
        if line:
            lines.append(line)

    # 如果没有tspan子元素，直接取text的文本
    if not lines and text_elem.text and text_elem.text.strip():
        lines.append(text_elem.text.strip())

    return lines


def _collect_inner_text(elem):
    """递归收集元素内部所有文本片段"""
    parts = []
    if elem.text and elem.text.strip():
        parts.append(elem.text.strip())
    for child in elem:
        parts.extend(_collect_inner_text(child))
        if child.tail and child.tail.strip():
            parts.append(child.tail.strip())
    return parts


def get_text_style(text_elem):
    """从text元素及其tspan中提取字体样式
    返回: (font_size, font_color, is_bold)
    """
    font_size = 14
    font_color = '#000000'
    is_bold = False

    # 从text元素属性获取
    size_attr = get_attr(text_elem, 'font-size')
    if size_attr:
        font_size = int(float(size_attr.replace('px', '')))

    fill_attr = get_attr(text_elem, 'fill')
    if fill_attr and not fill_attr.startswith('url('):
        hex_color, _ = rgba_to_hex(fill_attr)
        if hex_color:
            font_color = hex_color

    weight_attr = get_attr(text_elem, 'font-weight')
    if weight_attr in ('bold', '700'):
        is_bold = True

    # 从tspan子元素的style属性获取（覆盖）
    for tspan in text_elem.iter(f'{SVG_NS}tspan'):
        style = get_attr(tspan, 'style') or ''
        if 'font-weight: bold' in style or 'font-weight:bold' in style:
            is_bold = True
        size_m = re.search(r'font-size:\s*(\d+)px', style)
        if size_m:
            font_size = int(size_m.group(1))
        color_m = re.search(r'fill:\s*(#[0-9a-fA-F]+)', style)
        if color_m:
            font_color = color_m.group(1)

    return font_size, font_color, is_bold


# ============================================================
# 工具函数：颜色处理
# ============================================================

def rgba_to_hex(color_str):
    """将rgba()或rgb()颜色字符串转换为十六进制格式+透明度
    例如: rgba(175,194,218,1.00) -> ('#AFC2DA', 1.0)
          rgba(127,127,127,0.50) -> ('#7F7F7F', 0.5)
    """
    if not color_str or color_str == 'none':
        return None, 1.0
    # 已经是十六进制格式，无alpha
    if color_str.startswith('#'):
        return color_str.upper(), 1.0
    # 解析rgba(含alpha)
    m = re.match(r'rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([-\d.]+)\s*\)', color_str)
    if m:
        r, g, b = int(m.group(1)), int(m.group(2)), int(m.group(3))
        a = float(m.group(4))
        return f'#{r:02X}{g:02X}{b:02X}', a
    # 解析rgb(无alpha)
    m = re.match(r'rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)', color_str)
    if m:
        r, g, b = int(m.group(1)), int(m.group(2)), int(m.group(3))
        return f'#{r:02X}{g:02X}{b:02X}', 1.0
    return None, 1.0


def resolve_gradient_color(gradient_id, gradients):
    """从渐变定义中获取代表性颜色
    取所有色标的平均RGB值
    """
    if gradient_id not in gradients:
        return '#CCCCCC'  # 未知渐变使用灰色
    stops = gradients[gradient_id]
    if not stops:
        return '#CCCCCC'
    # 计算色标平均RGB
    r_sum, g_sum, b_sum = 0, 0, 0
    count = 0
    for color in stops:
        hex_color, _ = rgba_to_hex(color)
        if hex_color and len(hex_color) == 7:
            r_sum += int(hex_color[1:3], 16)
            g_sum += int(hex_color[3:5], 16)
            b_sum += int(hex_color[5:7], 16)
            count += 1
    if count == 0:
        return '#CCCCCC'
    return f'#{r_sum // count:02X}{g_sum // count:02X}{b_sum // count:02X}'


def parse_fill(fill_str, gradients):
    """解析SVG填充属性，返回十六进制颜色
    支持: rgba(), rgb(), #hex, url(#gradient), none
    """
    if not fill_str or fill_str == 'none':
        return None
    # 渐变引用
    m = re.match(r'url\(#([^)]+)\)', fill_str)
    if m:
        return resolve_gradient_color(m.group(1), gradients)
    # 直接颜色值
    return rgba_to_hex(fill_str)


# ============================================================
# SVG渐变定义收集
# ============================================================

def collect_gradients(svg_root):
    """收集SVG中defs区域内的所有线性渐变定义
    返回: {gradient_id: [color1, color2, ...]} 字典
    """
    gradients = {}
    for defs in svg_root.iter(f'{SVG_NS}defs'):
        for lg in defs.iter(f'{SVG_NS}linearGradient'):
            gid = get_attr(lg, 'id')
            if not gid:
                continue
            colors = []
            for stop in lg.iter(f'{SVG_NS}stop'):
                color = get_attr(stop, 'stop-color')
                if color:
                    colors.append(color)
            if colors:
                gradients[gid] = colors
    return gradients


# ============================================================
# SVG元素遍历与提取
# ============================================================

def walk_svg(elem, offset_x, offset_y, is_rotated, gradients, results, depth=0):
    """递归遍历SVG元素树，提取可视元素信息

    参数:
        elem: 当前XML元素
        offset_x, offset_y: 累积的位置偏移
        is_rotated: 当前是否处于90度旋转状态
        gradients: 渐变定义字典
        results: 输出的元素信息列表
        depth: 递归深度
    """
    tag = strip_ns(elem.tag)

    if tag == 'g':
        _process_group(elem, offset_x, offset_y, is_rotated, gradients, results, depth)
    elif tag == 'rect':
        _process_rect(elem, offset_x, offset_y, results)
    elif tag in ('svg', 'defs', 'pattern', 'image'):
        # 容器元素：递归处理子元素
        for child in elem:
            walk_svg(child, offset_x, offset_y, is_rotated,
                     gradients, results, depth + 1)


def _process_group(elem, offset_x, offset_y, is_rotated, gradients, results, depth):
    """处理g元素：解析变换，提取形状和文本，递归处理子元素"""
    transform = get_attr(elem, 'transform') or ''

    # 计算新的偏移和旋转状态
    new_x, new_y = offset_x, offset_y
    new_rotated = is_rotated

    # 解析translate
    tx, ty = parse_translate(transform)
    new_x += tx
    new_y += ty

    # 解析matrix旋转
    is_90, mtx, mty = parse_matrix_rotation(transform)
    if is_90:
        new_rotated = True
        new_x = offset_x + mtx
        new_y = offset_y + mty

    # 在当前group的直接子元素中查找path
    path_elem = None
    for child in elem:
        if strip_ns(child.tag) == 'path' and path_elem is None:
            path_elem = child

    # 在当前group的所有后代中查找第一个text
    text_info = None
    for text_elem in elem.iter(f'{SVG_NS}text'):
        if text_info is None:
            text_info = _extract_text_info(text_elem)
        break  # 只取第一个text

    # 如果找到了path，提取形状并关联文本
    if path_elem is not None:
        shape = _extract_shape_from_path(
            path_elem, new_x, new_y, new_rotated, gradients
        )
        if shape:
            if text_info:
                shape['label'] = text_info['label']
                shape['font_size'] = text_info['font_size']
                shape['font_color'] = text_info['font_color']
                shape['bold'] = text_info['bold']
            results.append(shape)

    # 递归处理子g元素
    for child in elem:
        if strip_ns(child.tag) == 'g':
            # 检查子g是否包含自己的path（独立形状）
            has_own_path = any(
                strip_ns(c.tag) == 'path' for c in child
            )
            if has_own_path:
                walk_svg(child, new_x, new_y, new_rotated,
                         gradients, results, depth + 1)
            else:
                # 子g只包含text，已在上面处理
                pass


def _extract_text_info(text_elem):
    """从text元素中提取标签文本和样式信息
    返回: {'label': str, 'font_size': int, 'font_color': str, 'bold': bool}
    """
    lines = extract_text_lines(text_elem)
    # drawio HTML模式使用<br>换行
    label = '<br>'.join(lines)
    font_size, font_color, is_bold = get_text_style(text_elem)
    return {
        'label': label,
        'font_size': font_size,
        'font_color': font_color,
        'bold': is_bold
    }


def _extract_shape_from_path(path_elem, offset_x, offset_y, is_rotated, gradients):
    """从path元素中提取形状信息（位置、尺寸、颜色等）
    返回: 形状信息字典，或 None（如果无法识别）
    """
    d = get_attr(path_elem, 'd')
    if not d:
        return None

    shape_info = classify_path(d)
    if not shape_info:
        return None

    shape_type, dims = shape_info

    # 解析填充颜色
    fill_str = get_attr(path_elem, 'fill') or ''
    fill_color = parse_fill(fill_str, gradients)

    # 解析填充透明度
    fill_opacity = get_attr(path_elem, 'fill-opacity')
    opacity = float(fill_opacity) if fill_opacity else 1.0

    # 解析描边
    stroke = get_attr(path_elem, 'stroke') or ''
    stroke_color = rgba_to_hex(stroke) if stroke and stroke != 'none' else None
    stroke_width = float(get_attr(path_elem, 'stroke-width') or 0)

    # 根据形状类型计算位置和尺寸
    if shape_type == 'rect':
        return _make_rect_shape(dims, offset_x, offset_y, is_rotated,
                                fill_color, stroke_color, stroke_width, opacity)
    elif shape_type == 'ellipse':
        return _make_ellipse_shape(dims, offset_x, offset_y, is_rotated,
                                   fill_color, stroke_color, stroke_width, opacity)
    elif shape_type == 'diamond':
        return _make_diamond_shape(dims, offset_x, offset_y, is_rotated,
                                   fill_color, stroke_color, stroke_width, opacity)
    return None


def _make_rect_shape(dims, offset_x, offset_y, is_rotated,
                     fill_color, stroke_color, stroke_width, opacity):
    """创建矩形形状信息字典
    90度旋转时: 宽高互换，x位置需减去原高度
    """
    w, h = dims
    if is_rotated:
        x = offset_x - h
        y = offset_y
        w, h = h, w
    else:
        x, y = offset_x, offset_y
    return {
        'type': 'rect', 'x': round(x, 1), 'y': round(y, 1),
        'w': round(w, 1), 'h': round(h, 1),
        'fill': fill_color, 'stroke': stroke_color,
        'stroke_width': stroke_width, 'opacity': opacity,
        'label': '', 'font_size': 14,
        'font_color': '#000000', 'bold': False,
        'rotation': 90 if is_rotated else 0
    }


def _make_ellipse_shape(dims, offset_x, offset_y, is_rotated,
                        fill_color, stroke_color, stroke_width, opacity):
    """创建椭圆形状信息字典
    dims: (x_min, y_min, width, height) 椭圆在局部坐标系的包围盒
    """
    ex, ey, ew, eh = dims
    if is_rotated:
        x = offset_x - eh - ey
        y = offset_y + ex
        ew, eh = eh, ew
    else:
        x = offset_x + ex
        y = offset_y + ey
    return {
        'type': 'ellipse', 'x': round(x, 1), 'y': round(y, 1),
        'w': round(ew, 1), 'h': round(eh, 1),
        'fill': fill_color, 'stroke': stroke_color,
        'stroke_width': stroke_width, 'opacity': opacity,
        'label': '', 'font_size': 14,
        'font_color': '#000000', 'bold': False,
        'rotation': 0
    }


def _make_diamond_shape(dims, offset_x, offset_y, is_rotated,
                        fill_color, stroke_color, stroke_width, opacity):
    """创建菱形形状信息字典
    dims: (x_min, y_min, width, height) 菱形在局部坐标系的包围盒
    """
    dx, dy, dw, dh = dims
    if is_rotated:
        x = offset_x - dh - dy
        y = offset_y + dx
        dw, dh = dh, dw
    else:
        x = offset_x + dx
        y = offset_y + dy
    return {
        'type': 'diamond', 'x': round(x, 1), 'y': round(y, 1),
        'w': round(dw, 1), 'h': round(dh, 1),
        'fill': fill_color, 'stroke': stroke_color,
        'stroke_width': stroke_width, 'opacity': opacity,
        'label': '', 'font_size': 14,
        'font_color': '#000000', 'bold': False,
        'rotation': 0
    }


def _process_rect(elem, offset_x, offset_y, results):
    """处理SVG rect元素，提取为矩形形状"""
    x = float(get_attr(elem, 'x') or 0) + offset_x
    y = float(get_attr(elem, 'y') or 0) + offset_y
    w = float(get_attr(elem, 'width') or 0)
    h = float(get_attr(elem, 'height') or 0)
    fill_str = get_attr(elem, 'fill') or ''

    if w < 1 or h < 1:
        return

    fill_color = rgba_to_hex(fill_str) if fill_str and fill_str != 'none' else None
    results.append({
        'type': 'rect', 'x': round(x, 1), 'y': round(y, 1),
        'w': round(w, 1), 'h': round(h, 1),
        'fill': fill_color, 'stroke': None,
        'stroke_width': 0, 'opacity': 1.0,
        'label': '', 'font_size': 14,
        'font_color': '#000000', 'bold': False,
        'rotation': 0
    })


# ============================================================
# DrawIO XML生成
# ============================================================

def make_drawio_doc(page_width, page_height):
    """创建drawio文档的XML框架结构
    返回: (mxfile元素, root元素) 元组
    """
    mxfile = ET.Element('mxfile')
    mxfile.set('host', 'app.diagrams.net')

    diagram = ET.SubElement(mxfile, 'diagram')
    diagram.set('id', 'svg-converted')
    diagram.set('name', 'Page-1')

    model = ET.SubElement(diagram, 'mxGraphModel')
    model.set('dx', '0')
    model.set('dy', '0')
    model.set('grid', '1')
    model.set('gridSize', '10')
    model.set('guides', '1')
    model.set('tooltips', '1')
    model.set('connect', '1')
    model.set('arrows', '1')
    model.set('fold', '1')
    model.set('page', '1')
    model.set('pageScale', '1')
    model.set('pageWidth', str(int(page_width)))
    model.set('pageHeight', str(int(page_height)))
    model.set('math', '0')
    model.set('shadow', '0')

    root = ET.SubElement(model, 'root')

    # drawio必需的两个根单元格: id=0 (图根) 和 id=1 (图层)
    cell0 = ET.SubElement(root, 'mxCell')
    cell0.set('id', '0')

    cell1 = ET.SubElement(root, 'mxCell')
    cell1.set('id', '1')
    cell1.set('parent', '0')

    return mxfile, root


def add_shape_cell(root, cell_id, shape):
    """向drawio文档添加一个形状单元格

    参数:
        root: drawio XML的root元素
        cell_id: 单元格唯一ID
        shape: 形状信息字典
    """
    cell = ET.SubElement(root, 'mxCell')
    cell.set('id', str(cell_id))
    # 设置显示文本（使用HTML实体转义特殊字符）
    cell.set('value', shape.get('label', ''))
    cell.set('style', _build_style(shape))
    cell.set('vertex', '1')
    cell.set('parent', '1')

    # 设置几何位置和尺寸
    geom = ET.SubElement(cell, 'mxGeometry')
    geom.set('x', str(shape['x']))
    geom.set('y', str(shape['y']))
    geom.set('width', str(shape['w']))
    geom.set('height', str(shape['h']))
    geom.set('as', 'geometry')


def _build_style(shape):
    """根据形状信息构建drawio样式字符串
    drawio样式格式: key1=val1;key2=val2;...
    """
    parts = []

    # 形状基础样式
    shape_type = shape.get('type', 'rect')
    if shape_type == 'rect':
        parts.append('rounded=0')
    elif shape_type == 'ellipse':
        parts.append('ellipse')
    elif shape_type == 'diamond':
        parts.append('rhombus')

    parts.append('whiteSpace=wrap')
    parts.append('html=1')

    # 填充颜色
    fill_color = shape.get('fill')
    if fill_color:
        parts.append(f'fillColor={fill_color}')
    else:
        parts.append('fillColor=none')

    # 描边
    stroke_color = shape.get('stroke')
    stroke_width = shape.get('stroke_width', 0)
    if stroke_color and stroke_width > 0:
        parts.append(f'strokeColor={stroke_color}')
        parts.append(f'strokeWidth={int(stroke_width)}')
    elif shape_type == 'diamond' or shape_type == 'ellipse':
        # 菱形和椭圆默认显示描边
        if stroke_color:
            parts.append(f'strokeColor={stroke_color}')
        else:
            parts.append('strokeColor=#000000')
    else:
        parts.append('strokeColor=none')

    # 字体设置
    font_size = shape.get('font_size', 14)
    parts.append(f'fontSize={font_size}')

    if shape.get('bold'):
        parts.append('fontStyle=1')

    font_color = shape.get('font_color', '#000000')
    if font_color and font_color != '#000000':
        parts.append(f'fontColor={font_color}')

    # 文本对齐: 居中
    parts.append('align=center')
    parts.append('verticalAlign=middle')

    # 透明度
    opacity = shape.get('opacity', 1.0)
    if opacity < 1.0:
        parts.append(f'opacity={int(opacity * 100)}')

    # 旋转角度
    rotation = shape.get('rotation', 0)
    if rotation != 0:
        parts.append(f'rotation={rotation}')

    return ';'.join(parts) + ';'


def save_drawio(mxfile, output_path):
    """将drawio XML树保存到文件
    自动添加缩进格式化，便于阅读
    """
    ET.indent(mxfile, space='  ')
    tree = ET.ElementTree(mxfile)
    tree.write(output_path, encoding='unicode', xml_declaration=True)


# ============================================================
# 元素过滤与排序
# ============================================================

def filter_and_sort_elements(elements):
    """过滤掉背景/水印元素，按从大到小排序
    大的容器矩形排在前面，小的组件排在后面，确保层次正确
    """
    filtered = []
    for elem in elements:
        # 跳过整个画布大小的背景矩形
        if elem['w'] >= 1700 and elem['h'] >= 1180:
            continue
        # 跳过过小的装饰元素
        if elem['w'] < 5 or elem['h'] < 5:
            continue
        filtered.append(elem)

    # 按面积从大到小排序（大容器先绘制，小组件后绘制）
    filtered.sort(key=lambda e: e['w'] * e['h'], reverse=True)
    return filtered


# ============================================================
# 主转换逻辑
# ============================================================

def convert_svg_to_drawio(svg_path, drawio_path=None):
    """主入口函数：将SVG文件转换为DrawIO文件

    参数:
        svg_path: 输入SVG文件路径
        drawio_path: 输出drawio文件路径（可选，默认同名.drawio）
    """
    # 确定输出路径
    if not drawio_path:
        base = os.path.splitext(svg_path)[0]
        drawio_path = base + '.drawio'

    # 步骤1: 解析SVG文件
    print(f'[1/4] 正在解析SVG文件: {svg_path}')
    tree = ET.parse(svg_path)
    svg_root = tree.getroot()

    # 获取SVG画布尺寸
    svg_width = float(get_attr(svg_root, 'width') or 1000)
    svg_height = float(get_attr(svg_root, 'height') or 800)
    print(f'  画布尺寸: {svg_width} x {svg_height}')

    # 步骤2: 收集渐变定义
    print('[2/4] 正在收集渐变定义...')
    gradients = collect_gradients(svg_root)
    print(f'  找到 {len(gradients)} 个渐变定义')

    # 步骤3: 递归提取SVG可视元素
    print('[3/4] 正在提取SVG元素...')
    elements = []
    walk_svg(svg_root, 0, 0, False, gradients, elements)

    # 过滤和排序
    elements = filter_and_sort_elements(elements)
    print(f'  共提取 {len(elements)} 个可视元素')

    # 按类型统计
    type_counts = {}
    for elem in elements:
        t = elem['type']
        type_counts[t] = type_counts.get(t, 0) + 1
    for t, c in sorted(type_counts.items()):
        print(f'    {t}: {c} 个')

    # 步骤4: 生成DrawIO文件
    print('[4/4] 正在生成DrawIO文件...')
    mxfile, root = make_drawio_doc(svg_width, svg_height)

    # 添加所有元素到drawio（ID从2开始，0和1已被根单元格占用）
    for i, elem in enumerate(elements, start=2):
        add_shape_cell(root, i, elem)

    # 保存到文件
    save_drawio(mxfile, drawio_path)
    print(f'\n转换完成！输出文件: {drawio_path}')
    print(f'共生成 {len(elements)} 个drawio元素')


# ============================================================
# 脚本入口
# ============================================================

if __name__ == '__main__':
    if len(sys.argv) < 2:
        # 默认使用脚本所在目录的SVG文件
        script_dir = os.path.dirname(os.path.abspath(__file__))
        default_svg = os.path.join(
            script_dir, 'SWDD_SoftwareArchitectureLayeredView_ILCP.svg'
        )
        if os.path.exists(default_svg):
            convert_svg_to_drawio(default_svg)
        else:
            print('用法: python svg2drawio.py <input.svg> [output.drawio]')
            sys.exit(1)
    else:
        svg_file = sys.argv[1]
        out_file = sys.argv[2] if len(sys.argv) > 2 else None
        convert_svg_to_drawio(svg_file, out_file)
