news 2026/9/28 15:35:13

draw.io XML驱动的C语言界面工程化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
draw.io XML驱动的C语言界面工程化实践

1. 项目概述:为什么用 draw.io 绘制界面不是“画图”,而是工程化表达的起点

很多人第一次看到“3-8 用draw.io来绘制界面”这个标题,下意识会想:“不就是拖几个按钮、连几条线?五分钟搞定。”我刚接触这个任务时也这么想——直到被产品经理甩来一份需求文档,里面写着“请输出可交付的UI流程图、状态迁移图、组件通信关系图,需支持导出为XML供后续自动化解析”。那一刻我才明白:draw.io 绘制界面,根本不是美术作业,而是一场面向开发落地的结构化建模实践。它解决的核心问题,是把模糊的“我要一个登录页”转化成程序员能读、测试能验、产品能对齐、甚至未来能喂给代码生成器的可执行语义图谱。

标题里那个星号“*”,不是装饰,是重点。它指向的是 draw.io 的本质能力:以 XML 为底层载体,实现图形与逻辑的双向绑定。你拖拽的每一个矩形,背后都是一段<mxCell>标签;你画的每一条连接线,实际是<mxCell edge="1">的拓扑关系声明;你设置的“点击跳转到首页”文字,会被序列化进value属性,成为后续解析的原始语义。这正是它和 Excalidraw、Figma 等工具的根本分野——前者是视觉表达,后者是可编程的界面元数据。

所以这个项目真正服务的对象,远不止 UI 设计师。它直指三类关键角色:

  • C语言开发者:尤其在嵌入式、单片机或 raylib 这类轻量级图形库场景中,没有 Qt Designer 那样的可视化 IDE,draw.io 输出的 XML 可被 C 解析器读取,直接映射为struct widget初始化参数或state_machine_t的 transition 表;
  • IDE 工程师:比如你在 IDEA 社区版里打开一个 XML 文件,发现格式混乱、缩进错乱、标签闭合异常——那恰恰说明你正在处理的不是静态文档,而是需要被 IDE 插件实时解析、高亮、校验的结构化配置源码;
  • 教学实践者:翁恺老师讲 C 语言时强调“程序即状态+转移”,而 draw.io 的状态图(State Diagram)模板,能把“冒泡排序的每一轮比较”、“字符串逆序的指针移动”这些抽象过程,变成学生一眼看懂的节点与箭头,比纯代码演示直观十倍。

我试过用 draw.io 画一个简易的“C语言打字游戏”界面原型:主窗口、输入框、计分板、倒计时条、错误提示弹窗。导出 XML 后,用 Python 写了 30 行解析脚本,自动提取所有组件 ID 和坐标,生成game_ui.h头文件里的#define WIN_X 100、#define INPUT_W 320等宏定义。同事拿到头文件,直接#include进 raylib 项目,DrawRectangle(x, y, w, h, RED)的参数就全有了。这才是“绘制界面”的真实价值——它不是终点,而是从设计到编码之间最短的那座桥。

2. 核心思路拆解:为什么选 draw.io 而不是其他工具?三个硬性技术理由

2.1 XML 是唯一能穿透工具链的通用协议,而 draw.io 是 XML 的“原生公民”

市面上能画图的工具很多,但能让你把一张图当代码一样管理、版本控制、diff 对比、CI/CD 自动校验的,只有 draw.io。原因很简单:它的.drawio文件本质就是纯文本 XML,没有任何二进制封装或私有加密。你可以用git diff直接看到“按钮A的x坐标从120改成了135”,可以用grep -n "login_btn" *.drawio快速定位所有含登录按钮的图,甚至能用sed -i 's/width="120"/width="140"/g' login_page.drawio批量调整所有按钮宽度——这些操作,在 Figma 的.fig文件或 Visio 的.vsdx文件里,根本不可能。

更关键的是,draw.io 的 XML 结构高度规范且文档完备。官方 DTD(Document Type Definition)明确定义了<mxGraphModel>作为根节点,<root>下必须包含<mxCell id="0"/>(根容器)和<mxCell id="1" parent="0"/>(默认层),所有图形元素都通过parent属性形成树状引用关系。这种强约束,让 C 语言解析器写起来极其省心。我用libxml2库写过一个极简解析器,核心逻辑只有三步:

  1. xmlDocPtr doc = xmlReadFile("ui.drawio", NULL, XML_PARSE_NOBLANKS);
  2. xmlNodePtr root = xmlDocGetRootElement(doc);
  3. 递归遍历root->children,对每个mxCell节点提取getAttribute("value")、getAttribute("x")、getAttribute("y")、getAttribute("width")、getAttribute("height")。

全程不需要正则匹配,不依赖 JSON Schema 那种动态解析,因为 draw.io 的 XML 就是“所见即所得”的结构化数据。反观某些国产在线绘图工具,导出的 XML 嵌套层数深、属性名随意(比如><mxCell id="2" value="&lt;div&gt;&lt;b&gt;用户名&lt;/b&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1"> <mxGeometry x="120" y="80" width="100" height="30" as="geometry"/> </mxCell>

注意style属性里的whiteSpace=wrap;html=1,这是 draw.io 的样式 DSL,但它对 C 解析器完全透明——你只需提取x,y,width,height,value四个字段,其余style内容可以原样存入结构体留待渲染层处理。这意味着你的 C 解析器可以做到:

  • 零外部依赖:只用标准libxml2或更轻量的mxml库(仅 200KB);
  • 极低内存占用:解析一个含 50 个组件的界面图,内存峰值不到 1MB;
  • 可嵌入任意环境:我在 STM32F407 上跑过简化版解析器,用tinyxml2替代libxml2,配合 raylib 的DrawText()函数,成功把 draw.io 导出的菜单界面渲染到 320x240 的 TFT 屏上。

对比之下,如果用 JSON 格式描述界面(如某些前端框架),C 语言解析 JSON 需要cJSON库,而cJSON在资源受限设备上容易触发堆栈溢出——这正是“单片机 C 语言没有堆栈吗”这类热搜词背后的现实痛点。draw.io 的 XML 天然扁平、层级浅、属性明确,恰恰绕开了 C 语言最脆弱的内存管理环节。

2.3 与 raylib 的协同逻辑:图形库不提供 UI 框架,draw.io 填补空白

raylib 是个极简主义图形库,它的哲学是“只做渲染,不做 UI”。官方示例里所有按钮、滑块、输入框,都是用DrawRectangle()、DrawText()手动画出来的,坐标和尺寸全靠硬编码。这在原型阶段没问题,但一旦界面复杂(比如一个带 12 个控件的设置页),维护成本就爆炸了。这时候 draw.io 就成了 raylib 的“外挂 UI 编译器”。

我的实操路径是:

  1. 在 draw.io 中用“Software”模板画出 raylib 界面草图,严格按 raylib 坐标系(左上角 0,0,y 轴向下)布局;
  2. 导出 XML,用 C 解析器读取,生成ui_config.c:
typedef struct { int x, y, w, h; const char* text; } ui_element_t; ui_element_t login_ui[] = { {120, 80, 100, 30, "用户名"}, {120, 120, 100, 30, "密码"}, {150, 180, 80, 40, "登录"} };
  1. 在 raylib 主循环中,遍历login_ui数组,调用DrawRectangleRec((Rectangle){e.x,e.y,e.w,e.h}, LIGHTGRAY)和DrawText(e.text, e.x+10, e.y+10, 20, BLACK)。

这个流程的关键在于:draw.io 定义了“界面是什么”,raylib 负责“界面怎么画”,二者职责清晰分离。你改界面布局,只需在 draw.io 里拖动,重新导出 XML,C 解析器自动更新ui_config.c;你优化渲染效果,只需改 raylib 的DrawXXX()调用,不影响界面结构。这种解耦,比硬编码坐标可靠十倍——我曾因一个y+5的偏移量写错,导致整个表单错位,调试两小时才发现是手误。而 draw.io 的可视化编辑,让这种低级错误几乎绝迹。

3. 核心细节解析:从 draw.io 操作到 C 解析器落地的完整链路

3.1 draw.io 界面绘制的四个必守原则(避坑指南)

很多新手画完图导出 XML,发现 C 解析器读出来全是空值,最后查半天发现是 draw.io 设置问题。以下是我在 37 个实际项目中总结的硬性规则,每一条都踩过坑:

提示:draw.io 默认导出的 XML 包含大量冗余信息(如connectable="1"、movable="1"),这些对 C 解析器毫无价值,反而增加解析负担。务必在导出前关闭。

原则一:禁用“自动连接线”功能,手动标注交互逻辑
draw.io 的“连接线”工具默认开启“自动吸附”,当你把线拖到组件边缘时,它会自动生成<mxCell edge="1" source="2" target="3">。表面看很智能,实则埋雷:C 解析器无法区分“这是 UI 布局线”还是“这是状态跳转线”。正确做法是:

  • 关闭顶部菜单Arrange → Insert → Connector,改用Arrow形状手动画线;
  • 在箭头旁添加文本标签,如onClick→main_menu,并把该文本放入mxCell的value属性;
  • 这样解析器就能通过strstr(value, "onClick")精准提取事件逻辑,而不是靠source/targetID 猜测。

原则二:组件 ID 必须人工命名,禁用自动生成 ID
draw.io 默认给每个元素分配随机 ID(如2a3b4c5d),但 C 解析器需要稳定标识符。操作路径:

  • 选中组件 → 右键 →Edit Style→ 在弹出框底部ID输入框填入有意义名称,如btn_login、txt_username;
  • 这个 ID 会写入 XML 的id属性,解析时可直接用strcmp(id, "btn_login") == 0判断类型;
  • 实测发现:若 ID 含空格或特殊字符(如login button),XML 解析会失败,必须用下划线login_button。

原则三:所有文本内容必须用 HTML 实体编码,避免 C 字符串截断
draw.io 允许在文本框里输入&、<、>等符号,但它们在 XML 中是保留字符。如果不编码,导出的 XML 会变成:

<mxCell value="用户名 & 密码" ... /> <!-- 错误!& 未转义 -->

C 解析器读到&就认为是实体开始,后续全乱。正确做法:

  • 在 draw.io 文本框中,手动输入&amp;代替&,&lt;代替<,&gt;代替>;
  • 或安装插件HTML Entity Encoder(社区版免费),一键转换;
  • 解析时xmlNodeGetContent()返回的是已解码字符串,无需额外处理。

原则四:坐标系必须锁定为“像素单位”,禁用百分比和相对定位
draw.io 默认支持%单位(如x="50%"),这对响应式网页有用,但对 C 语言渲染是灾难。C 解析器无法在运行时计算屏幕宽高再换算。强制设置:

  • 选中画布 → 右键 →Properties→Grid and Guides→ 取消勾选Show grid;
  • 在Format Panel(右侧)→Size标签页 → 将Width/Height单位设为px;
  • 所有组件的x,y,width,height属性在 XML 中必须是纯数字,如x="120",而非x="50%"。

3.2 C 解析器核心代码详解(附实测可运行片段)

以下是我基于libxml2编写的精简解析器,已通过 GCC 11.2 + raylib 4.5 测试,支持 98% 的 draw.io UI 图:

#include <libxml2/libxml/parser.h> #include <libxml2/libxml/tree.h> #include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct { char id[64]; int x, y, w, h; char text[256]; } ui_component_t; // 全局数组存储解析结果 ui_component_t components[100]; int comp_count = 0; // 递归解析 mxCell 节点 void parse_mxcell(xmlNodePtr node) { // 检查是否为 mxCell 元素 if (node->type == XML_ELEMENT_NODE && xmlStrcmp(node->name, BAD_CAST "mxCell") == 0) { // 提取 id 属性 xmlChar *id = xmlGetProp(node, BAD_CAST "id"); if (id && xmlStrlen(id) > 0 && comp_count < 100) { strncpy(components[comp_count].id, (char*)id, sizeof(components[0].id)-1); components[comp_count].id[sizeof(components[0].id)-1] = '\0'; // 提取 x, y, w, h(从 geometry 子节点) xmlNodePtr geom = node->children; while (geom) { if (geom->type == XML_ELEMENT_NODE && xmlStrcmp(geom->name, BAD_CAST "mxGeometry") == 0) { components[comp_count].x = atoi((char*)xmlGetProp(geom, BAD_CAST "x")); components[comp_count].y = atoi((char*)xmlGetProp(geom, BAD_CAST "y")); components[comp_count].w = atoi((char*)xmlGetProp(geom, BAD_CAST "width")); components[comp_count].h = atoi((char*)xmlGetProp(geom, BAD_CAST "height")); break; } geom = geom->next; } // 提取 value 属性(文本内容) xmlChar *value = xmlGetProp(node, BAD_CAST "value"); if (value && xmlStrlen(value) > 0) { // libxml2 自动解码 HTML 实体,直接拷贝 strncpy(components[comp_count].text, (char*)value, sizeof(components[0].text)-1); components[comp_count].text[sizeof(components[0].text)-1] = '\0'; } else { strcpy(components[comp_count].text, ""); } comp_count++; } xmlFree(id); xmlFree(value); } // 递归处理子节点 xmlNodePtr child = node->children; while (child) { parse_mxcell(child); child = child->next; } } // 主解析函数 int load_ui_from_drawio(const char* filename) { xmlDocPtr doc = xmlReadFile(filename, NULL, XML_PARSE_NOBLANKS); if (!doc) return -1; xmlNodePtr root = xmlDocGetRootElement(doc); if (!root) { xmlFreeDoc(doc); return -1; } comp_count = 0; parse_mxcell(root); xmlFreeDoc(doc); return comp_count; }

关键细节说明:

  • XML_PARSE_NOBLANKS参数至关重要:它告诉 libxml2 忽略 XML 中的空白文本节点(如换行、缩进),否则node->children会遍历到大量XML_TEXT_NODE,导致mxGeometry查找失败;
  • xmlGetProp()返回的是xmlChar*,必须用atoi()转整数,不能直接(int)xmlGetProp(...)强转——这是新手高频崩溃点;
  • components数组大小设为 100 是经验阈值:一个典型嵌入式界面 rarely 超过 50 个控件,留 50 余量防溢出;
  • strncpy()加\0截断是 C 字符串安全铁律,否则DrawText()可能读到垃圾内存。

3.3 从 XML 到 raylib 渲染的实操闭环(含坐标系对齐技巧)

draw.io 默认坐标系(0,0 在左上角)和 raylib 完全一致,但有一个隐藏差异:draw.io 的y值是“组件上边缘纵坐标”,而 raylib 的DrawRectangleRec()的y是“矩形上边缘纵坐标”,二者数学等价。真正需要处理的是字体基线偏移。

问题现场:draw.io 里“用户名”文本框高 30px,y=80,但用DrawText("用户名", 120, 80, 20, BLACK)渲染时,文字整体上浮,像被切掉顶部。原因:raylib 的DrawText()的y参数指定的是文字基线位置(baseline),不是文本框上边缘。

解决方案:在 draw.io 中,为所有文本组件手动添加y偏移补偿。计算公式:

raylib_y = drawio_y + drawio_height - font_size * 0.8

其中0.8是经验系数(字体高度约 80% 位于基线下方)。实测font_size=20时,20*0.8=16,所以raylib_y = 80 + 30 - 16 = 94。

我在 C 解析器里加了一行:

// 对文本类组件,y 坐标自动补偿 if (strlen(components[i].text) > 0) { components[i].y += components[i].h - 16; // 20号字的基线补偿 }

这样DrawText(components[i].text, components[i].x+10, components[i].y+5, 20, BLACK)就能精准对齐 draw.io 原图。

另一个实战技巧:draw.io 的“圆角矩形”在 raylib 中要用DrawRectangleRounded(),但该函数需要roundness参数(0.0~1.0)。draw.io 的rounded=1对应roundness=0.2,rounded=0对应roundness=0.0。解析时提取style属性:

xmlChar *style = xmlGetProp(node, BAD_CAST "style"); if (style && strstr((char*)style, "rounded=1")) { components[i].roundness = 0.2f; } else { components[i].roundness = 0.0f; } xmlFree(style);

4. 实操全流程:从零开始完成一个“C语言打字游戏”界面工程

4.1 第一步:在 draw.io 中构建可解析的 UI 原型(含模板选择)

打开 draw.io(推荐使用桌面版,避免浏览器兼容问题),新建空白图。关键设置:

  • 顶部菜单Arrange → Grid and Guides → Grid:勾选Snap to grid,网格大小设为10(便于像素级对齐);
  • 右侧Format Panel→Page标签页:Width=800,Height=600(匹配常见显示器分辨率);
  • Style标签页:Background=#FFFFFF(白底,方便截图);

模板选择策略:

  • 不要用“Flowchart”或“UML”模板——它们自带大量业务语义标签(如start,end,decision),C 解析器会误判;
  • 推荐用General → Rectangle和Arrows → Arrow手动搭建,或Software → UI Elements中的Button,TextField,Label;
  • UI Elements里的组件已预设rounded=0、whiteSpace=wrap等 C 友好属性,直接拖拽即可。

我的“打字游戏”界面布局:

  • 顶部横幅:Rectangle,x=0,y=0,w=800,h=60,value="打字游戏 v1.0";
  • 中央文本区:Rectangle,x=100,y=100,w=600,h=200,value="&lt;p&gt;请输入以下文字:&lt;br&gt;&lt;b&gt;Hello World!&lt;/b&gt;&lt;/p&gt;"(HTML 编码);
  • 输入框:Rectangle,x=100,y=320,w=600,h=40,id="txt_input";
  • 提交按钮:Rectangle,x=350,y=380,w=100,h=40,id="btn_submit",value="提交";
  • 底部状态栏:Rectangle,x=0,y=540,w=800,h=60,value="剩余时间:30s | 正确率:0%";

导出前终极检查:

  • 全选所有组件 → 右键 →Edit Style→ 确认每个id已填写且无重复;
  • 用Ctrl+A全选 → 右键 →Group→ 将所有组件组合成一个组(避免导出时出现多余mxCell);
  • File → Export As → XML→ 勾选Include a copy of the diagram(确保 XML 完整)→ 保存为typing_game.drawio。

4.2 第二步:用 C 解析器生成可编译的 UI 配置

将typing_game.drawio放入项目assets/目录,编写build_ui.c:

gcc -o build_ui build_ui.c `xml2-config --cflags --libs` -I/usr/include/libxml2 ./build_ui assets/typing_game.drawio > src/ui_config.c

build_ui.c核心逻辑:调用前述load_ui_from_drawio(),遍历components[],生成 C 代码:

// 自动生成的 ui_config.c #include "raylib.h" typedef struct { const char* id; int x, y, w, h; float roundness; const char* text; } UIElement; UIElement ui_elements[] = { {"banner", 0, 0, 800, 60, 0.0f, "打字游戏 v1.0"}, {"text_area", 100, 100, 600, 200, 0.0f, "<p>请输入以下文字:<br><b>Hello World!</b></p>"}, {"txt_input", 100, 320, 600, 40, 0.0f, ""}, {"btn_submit", 350, 380, 100, 40, 0.2f, "提交"}, {"status_bar", 0, 540, 800, 60, 0.0f, "剩余时间:30s | 正确率:0%"} }; const int UI_ELEMENT_COUNT = 5;

关键优势:

  • ui_config.c是纯 C 代码,可直接#include到主程序;
  • UI_ELEMENT_COUNT由解析器自动生成,避免手动维护数组长度出错;
  • roundness字段支持圆角渲染,text字段为空时代表纯容器(如输入框背景)。

4.3 第三步:在 raylib 主循环中集成渲染(含事件绑定逻辑)

main.c中的渲染循环:

#include "raylib.h" #include "ui_config.c" // 直接包含生成的配置 int main() { InitWindow(800, 600, "Typing Game"); SetTargetFPS(60); while (!WindowShouldClose()) { BeginDrawing(); ClearBackground(RAYWHITE); // 渲染所有 UI 元素 for (int i = 0; i < UI_ELEMENT_COUNT; i++) { UIElement* e = &ui_elements[i]; // 渲染背景矩形 if (e->w > 0 && e->h > 0) { DrawRectangleRounded((Rectangle){e->x, e->y, e->w, e->h}, e->roundness, 16, LIGHTGRAY); } // 渲染文本(仅当 text 非空) if (strlen(e->text) > 0) { // 简单 HTML 解析:忽略 <p><b> 标签,只取纯文本 char plain_text[256]; extract_plain_text(e->text, plain_text); // 自定义函数 DrawText(plain_text, e->x + 10, e->y + 10, 20, BLACK); } } EndDrawing(); } CloseWindow(); return 0; }

事件绑定延伸:
draw.io 导出的 XML 不含事件逻辑,但你可以约定value属性中的特殊标记。例如:

  • value="submit_action"→ 解析后e->action = ACTION_SUBMIT;
  • value="timer_30s"→e->timer_ms = 30000;
    这样 C 代码就能根据e->id和e->action字段,构建事件分发器,实现“点击 btn_submit 触发提交逻辑”。

4.4 第四步:调试与验证——如何确认 XML 解析 100% 准确?

最可靠的验证不是看渲染效果,而是双向比对:

  1. 用xmllint --format typing_game.drawio格式化 XML,人工检查x,y,width,height,id,value是否与 draw.io 中设置一致;
  2. 运行build_ui,检查生成的ui_config.c中数值是否与 XML 一致;
  3. 在 raylib 中printf("UI[%d]: %s at (%d,%d)\n", i, e->id, e->x, e->y),确认运行时加载值正确;

我遇到过一次诡异问题:draw.io 中x=100,但ui_config.c里生成x=99。排查发现是 draw.io 的“对齐到网格”功能在Grid=10时,把x=100.5自动修正为x=100,但 XML 中仍写x="100.5",atoi()截断为100,而atof()才能读取小数。最终方案:在 draw.io 中严格用整数坐标,并在 C 解析器中强制atoi(),杜绝浮点误差。

5. 常见问题与独家排查技巧实录

5.1 XML 解析失败的五大高频原因及速查表

现象可能原因排查命令解决方案
xmlReadFile()返回 NULL文件路径错误或权限不足ls -l assets/typing_game.drawio确保路径正确,chmod 644文件
xmlDocGetRootElement()返回 NULLXML 格式损坏(如 BOM 头)file -i assets/typing_game.drawio用 VS Code 以 UTF-8 无 BOM 保存
comp_count为 0未找到mxCell节点grep -c "<mxCell" assets/typing_game.drawio检查 draw.io 是否导出为 XML,而非 PNG
x,y值全为 0mxGeometry子节点未正确提取grep -A 5 "mxGeometry" assets/typing_game.drawio确认mxGeometry是mxCell的 direct child
text字段乱码draw.io 中用了中文但未 UTF-8 编码iconv -f GBK -t UTF-8 assets/typing_game.drawio > tmp.xml在 draw.io 设置File → Preferences → Encoding → UTF-8

注意:VS Code 中打开 draw.io XML 文件,若右下角显示GBK,说明文件编码错误。必须在 draw.io 中重新导出,或用iconv转码。

5.2 IDEA 社区版 XML 格式化问题的根源与根治法

热搜词“idea 社区版怎么让xml 里的文件不格式化”直击痛点。IDEA 默认对 XML 启用Reformat Code,会把 draw.io 的紧凑 XML 展开成多行,破坏libxml2的XML_PARSE_NOBLANKS效果。根治方案:

  • Settings → Editor → Code Style → XML → Other→ 取消勾选Keep line breaks;
  • Settings → Editor → Code Style → XML → Wrapping and Braces→ 将Tag nesting设为Do not wrap;
  • 最关键:Settings → Editor → File Encodings→Default encoding设为UTF-8,Transparent native-to-ascii conversion取消勾选(否则中文变\u4f60\u597d)。

5.3 C 语言解析器性能瓶颈突破技巧

在资源受限设备(如 ESP32)上,libxml2可能内存超限。替代方案:

  • 用mxml库(仅 200KB):mxmlLoadFile()替代xmlReadFile(),mxmlFindElement()替代手动遍历;
  • 极简方案:不用 XML 库,用fgets()逐行扫描,匹配<mxCell.*?id="(.*?)".*?x="(.*?)".*?y="(.*?)".*?/>正则(需pcre库);
  • 终极轻量:要求 draw.io 导出为Plain XML(无注释、无空格),用strtok()分割字符串,strstr()提取属性值——实测在 STM32 上解析 50 个组件仅耗时 12ms。

5.4 从 draw.io 到 C 的“语义鸿沟”填平术

draw.io 的value属性是富文本,但 C 需要纯文本。我的 HTML 解析函数extract_plain_text()仅 20 行:

void extract_plain_text(const char* html, char* plain) { int len = strlen(html); int j = 0; for (int i = 0; i < len && j < 255; i++) { if (html[i] == '<') { // 跳过标签 while (i < len && html[i] != '>') i++; } else if (html[i] == '&') { // 处理实体 if (strncmp(&html[i], "&lt;", 4) == 0) { plain[j++] = '<'; i += 3; } else if (strncmp(&html[i], "&gt;", 4) == 0) { plain[j++] = '>'; i += 3; } else if (strncmp(&html[i], "&amp;", 5) == 0) { plain[j++] = '&'; i += 4; } else plain[j++] = html[i]; } else { plain[j++] = html[i]; } } plain[j] = '\0'; }

这个函数不依赖任何 HTML 库,专为 draw.io 的简单标签(<b>,<p>,<br>)设计,比libxml2的 HTML 解析快 3 倍。

5.5 独家经验:如何让 draw.io 成为 C 语言教学利器

教翁恺 C 语言课时,

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:33:08

Rokid AIUI实现AR推箱子:语音+头控多模态交互实战

1. 这不是玩具&#xff0c;是用Rokid AIUI把童年记忆“喊”进AR眼镜的真实项目我去年在杭州一个教育科技展上&#xff0c;第一次看到有人用Rokid Max眼镜玩推箱子——不是用手柄&#xff0c;不是用触控板&#xff0c;而是靠头动语音双模交互完成全部操作&#xff1a;低头说“向…

作者头像 李华
网站建设 2026/9/28 15:32:08

AgentScope 2.0:企业级Agent工程化落地实践指南

1. 不是“又一个Agent框架”&#xff0c;而是把Agent工程化真正落地的系统最近在几个技术群里&#xff0c;总有人发链接问&#xff1a;“这个AgentScope到底值不值得上手&#xff1f;”——不是问“它能做什么”&#xff0c;而是直接跳到“值不值得”。这背后其实藏着一个被反复…

作者头像 李华
网站建设 2026/9/28 15:30:30

ESP32烧录实战:USB转TTL下载器接线、esptool使用与避坑指南

如果你的ESP32开发板插上USB线后电脑完全没反应&#xff0c;或者用Arduino IDE上传程序时一直卡在Connecting........_____.....____&#xff0c;又或者你手里刚好只有一块USB转TTL下载器和一块裸的ESP32模组——这篇东西就是为你准备的。平时大家用带USB转串口芯片的开发板连接…

作者头像 李华
网站建设 2026/9/28 15:29:28

应届生必看:AI项目开发工作流全流程实战指南

带过几届应届生、也带过不少半路转行做AI的&#xff0c;我经常听到一句话&#xff1a;“网上教程我都跟得下来&#xff0c;课也上了不少&#xff0c;但一进公司、一碰真实项目&#xff0c;整个人就懵了。”这句话基本代表了90%新人的真实状态。不是说模型不会调、代码不会写&am…

作者头像 李华
网站建设 2026/9/28 15:29:09

华为老机型升级鸿蒙后自动重启?警惕CPU虚焊隐患

华为老机型升级鸿蒙系统以后突然开始自动重启&#xff0c;这事这几年我维修中见过太多了。很多人第一反应就是“鸿蒙把我的手机搞坏了”&#xff0c;于是恢复出厂、刷全量包、降回旧版本&#xff0c;折腾一大圈发现重启依旧&#xff0c;机器拿到台上一拆&#xff0c;主板拆出来…

作者头像 李华
网站建设 2026/9/28 15:28:46

Jev:AI决策系统工程化落地方法论

1. 项目概述&#xff1a;Jev 不是新模型&#xff0c;而是一套可落地的 AI 决策系统工程方法论“Jev”这个词最近在技术社区和企业架构讨论中频繁出现&#xff0c;但很多人第一次看到时会下意识把它当成某个新开源大模型、某家创业公司的神秘产品&#xff0c;或者又一个营销包装…

作者头像 李华