c++TinML转html
c++TinML转html
- 前言
- 解析
- 解释
- 转译html
- 类定义
- 开头html
- 结果
- 这是最终效果(部分): 
前言
在python.tkinter设计标记语言(转译2-html)中提到了将TinML转为静态html的python实现方法;在HtmlRender - c++实现的html生成类中又提供了我自己写的基于c++编辑html的简单方式,这篇笔记就是使用c++将tinml转为静态html。
本示例未使用任何第三方库(除了我之前提供的HtmlRender类),但是在html中代码高亮方面使用了在线的highlight.js
。
整套流程下来和基于python的TinText平台类似,都是先解析TinML标记文本段,再给每个标记赋以特定含义(解释过程),最后通过这些解释后的内容生成html结构,生成html文本。
项目示例:CppTinParser: TinML to html by c++
解析
这一部分是对TinML标记的语法解析。
class TinLexer{public:TinLexer(string contents){this->content = '\n' + contents + '\n';}void run_test();vector<string> run();vector<map<string,vector<string>>> lex();private:string content;};
主要解析功能就是对每一行TinML标记文本段进行基于上下文标记的正则匹配和结构解析,这部分相当于TinText项目中的TinParser
:
- 单行TinML标记,分为标记名称和标记参数组
- 多行模式TinML,转为单行TinML标记解析后格式
部分代码如下:
vector<map<string,vector<string>>> TinLexer::lex(){// 预处理// tackle <tinfile>: add context of the specific TinML file into this->content// 此部分代码省略,可见程序源码// ...vector<map<string,vector<string>>> tinresults;//总结果vector<string> contents;string nowtag;//当前标签contents = split(this->content, '\n');bool Lines = false;//多行模式regex pattern("^<(.*?)>(.*)");smatch result;vector<string> args;//参数列表for (const auto &i : contents){if (size(i) == 1){// 实际上是empty,不过貌似写不对// 既有可能是因为getline将\n转为\0,导致该字符串截止continue;}if (size(i)>=2 && i.substr(0,2)=="|-"){// cout << "Comment: " << i << endl;continue;}map<string,vector<string>> lineargs;//单行参数if (Lines){if (i[0]=='|'){if (size(i)>2 && i[i.length()-2]!='|'){string content = i.substr(1,size(i)-2);args.push_back(subreplace(content, "%VEB%", "|"));}else if (size(i)==2){args.push_back("");}else{string content = i.substr(1,size(i)-3);args.push_back(subreplace(content, "%VEB%", "|"));// for (auto const &j : args){// cout << "Arg: " << j << endl;// }lineargs[nowtag] = args;tinresults.push_back(lineargs);args.clear();Lines = false;}}}else if (i[i.length()-2] == ';'){// getline最后一个为\0,因此-2// string本身不以\0结尾,但是getline会改成\0Lines = true;bool ismatch = regex_search(i, result, pattern);if (ismatch){// cout << "Tag: " << result[1] << endl;nowtag = result[1];string content = result[2];int lastindex = content.find_last_of(';');content = content.substr(0, lastindex);args.push_back(subreplace(content, "%VEB%", "|"));}else{cout << "\033[33;1m不可被解析,当作<p>处理:" << i << "\033[0m" << endl;args.push_back(subreplace(i, "%VEB%", "|"));lineargs["p"] = args;tinresults.push_back(lineargs);args.clear();}}else{bool ismatch = regex_search(i, result, pattern);if (ismatch){nowtag = result[1];string content = result[2];auto oargs = split(content, '|');for (auto const &j : oargs)args.push_back(subreplace(j, "%VEB%", "|"));lineargs[nowtag] = args;tinresults.push_back(lineargs);args.clear();}else{//无法匹配当作p处理cout << "\033[33;1m不可被解析,当作<p>处理:" << i << "\033[0m" << endl;args.push_back(subreplace(i, "%VEB%", "|"));lineargs["p"] = args;tinresults.push_back(lineargs);args.clear();}}}return tinresults;
}
这里还遇到一个坑,
string.getline
会将分割字符转为\0
,这样导致string类型变量所包含的文字比表达的文字多一个“字”。string的设计是不包含的\0
,所以要注意从string类型中取字符的下标。
解析过程是最基础的,也几乎不会再维护,毕竟TinML的语法就这样了。
解释
这和TinText软件中转译html的过程一样,只不过TinEngine在渲染之前就生成了解释内容并传递给了转译器,而CppTinParser需要单独进行参数解释。
首先,生成每个标记对应的参数含义:
static map<string, vector<string>> tinkws;void loadkws()
{// 标记对应的参数键tinkws["ac"] = {"name"};tinkws["anchor"] = {"name"};tinkws["code"] = {"type", "codes"};tinkws["fl"] = {};tinkws["follow"] = {};tinkws["html"] = {"htmls"};tinkws["img"] = {"name", "url", "size"};tinkws["image"] = {"name", "url", "size"};tinkws["lnk"] = {"text", "url", "description"};tinkws["link"] = {"text", "url", "description"};tinkws["a"] = {"text", "url", "description"};tinkws["ls"] = {"lists"};tinkws["list"] = {"lists"};tinkws["n"] = {"notes"};tinkws["note"] = {"notes"};tinkws["nl"] = {"lists"};tinkws["numlist"] = {"lists"};tinkws["p"] = {"texts"};tinkws["pages"] = {"name"};tinkws["/page"] = {""};tinkws["/pages"] = {""};// 展示到这里位置,当然,这不是完整的//...
}
由于之前已经生成了类似如下的数据结构:
(("<title>",("title","1"),),("<p>",("paragraph",)),...
)
现在,就需要把这些已经分割好的标记文本转为有意义的内容:
(("<title>",("title": "title", "level": "1"),),("<p>",("paragraph": ("paragraph",))),...
)
这里采用的逻辑是对一个标签所拥有的所有参数一一对应,多出来的统一给最后一个参数。
这部分逻辑实现如下:
map<string, string> tokeywords(string tag, vector<string> contents)
{// 将列表顺序参数转为键、值类参数map<string, string> keywords;cout << "Tag: " << tag << endl;keywords["tag"] = tag;int args_num = contents.size();int index = 0;for (auto kws : tinkws[tag]){if (index >= args_num){// 剩下键全为空keywords[kws] = "";continue;}keywords[kws] = contents[index];// cout << kws << " = " << contents[index] << endl;index++;}if (index < args_num){// 将剩下的参数全部放入最后一个键中for (int i = index; i < args_num; i++){keywords[tinkws[tag].back()] += "\n" + contents[i];}}for (auto kw : keywords){if (kw.first == "tag"){ // 跳过tag键continue;}cout << kw.first << " = " << kw.second << endl;}return keywords;
}
class TinParser
{
public:TinParser(vector<map<string, vector<string>>> contents){this->contents = contents;}void parse();void render(string file);private:vector<map<string, vector<string>>> contents;vector<map<string, string>> result;
};void TinParser::parse()
{// 判断tag是否存在contents = this->contents;for (auto content : contents){string tag = content.begin()->first;if (tinkws.find(tag) == tinkws.end()){cerr << "\033[33;1mtag not found: " << tag << "\033[0m" << endl;// //string转char// char c_tag;// strcpy(&c_tag, tag.c_str());// throw NoTagName(c_tag);continue;}map<string, string> results = tokeywords(tag, content.begin()->second);result.push_back(results);}
}
转译html
经过前面的准备,这里已经准备好了将TinML转译为html的所有信息。
由于在之前的文章中已经给出了HtmlRender
的实现和使用示例,这里便不再赘叙。
这部分的代码和TinText的tin2html.py
实现在逻辑上完全一致,甚至可以看作是python到c++的翻译。
类定义
class TinRender {//tin->html类
public:TinRender(const string &file) : file_(file){}HtmlRender* render(vector<map<string,string>> content, bool _style = true, bool _code_style = true);//转译为htmlvoid output(vector<map<string,string>> content);//输出
private:string file_;//html文件路径ofstream fout_;//html文件输出流
};
开头html
就是html头中包含的样式、额外引入js、标题这些。
HtmlRender* TinRender::render(vector<map<string, string>> content, bool _style, bool _code_style){//转译为HtmlRender。可以直接使用output方法转译+输出保存//注意,CPPTinParser只是用c++实现的tinml转译器//不作为TinML规范的检查器string tag;HtmlRender* html = new HtmlRender("html", "", {});HtmlRender* head = new HtmlRender("head", "", {});HtmlRender* title = new HtmlRender("title", "TinML", {});head->add(title);HtmlRender* meta = new HtmlRender("meta", "", {{"charset","UTF-8"}}, true);head->add(meta);//读取css文件if (_style){string filename = "blubook.css";ifstream infile;infile.open(filename.data()); //将文件流对象与文件连接起来 assert(infile.is_open()); //若失败,则输出错误消息,并终止程序运行 string s, strAllLine;while (getline(infile, s)){strAllLine += s + "\n";}infile.close(); //关闭文件输入流HtmlRender* style = new HtmlRender ("style", strAllLine, {}, false, true);head->add(style);}//<code>样式if (_code_style){HtmlRender* codestyle = new HtmlRender("link", "", {{"rel","stylesheet"}, {"type", "text/css"}, {"href","https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs.min.css"}});head->add(codestyle);HtmlRender* codescript = new HtmlRender("script", "", {{"src","https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"}});head->add(codescript);HtmlRender* codestartscript = new HtmlRender("script", "hljs.highlightAll();", {});head->add(codestartscript);}HtmlRender* _body = new HtmlRender("body", "", {});HtmlRender* body = new HtmlRender("div", "", {{"id","content"}});_body->add(body);html->add(head);html->add(_body);// 提前定义类型变量HtmlRender* table;HtmlRender* tbody;bool tablehead = false;HtmlRender* tabsview;bool pagestag = false;vector<string> pagesnames;vector<map<string, string>> pagescontent;int pagescount = 0;// ...
}
然后就是对每一个标记的单独转译,这些都在上面的那个方法里,这里给几个例子:
// ...//常规转译TinML解析结果if (tag == "ac" || tag == "anchor"){string name = item["name"];if (name[0]=='#'){//锚点链接HtmlRender* anchor = new HtmlRender("a", "🔗", {{"href", name}});HtmlRender* lastitem = body->children().back();lastitem->add(anchor);}else{//锚点定义HtmlRender* anchor = new HtmlRender("a", "", {{"id", name}});body->add(anchor);}}else if (tag == "code"){//代码块//使用highlight.js,不支持tinml代码块string type = item["type"];string codes = item["codes"];if (type == "tin"){type = "nohighlight";}else{type = "language-" + type;}HtmlRender* pre = new HtmlRender("pre", "", {});HtmlRender* code = new HtmlRender("code", codes, {{"class",type}});pre->add(code);body->add(pre);}else if (tag == "html"){//html文本string htmltext = item["htmls"];HtmlRender* htmlcont = new HtmlRender("", htmltext, {}, false, true);body->add(htmlcont);}else if (tag == "img" || tag == "image"){//图片string name = item["name"];string url = item["url"];if (url.empty()){//因为CppTinParser只是用c++实现的tinml转译器//不作为TinML规范的检查器,也不作为tin文件渲染环境//所以不会有额外的资源文件储存位置//因此,不支持从本地文件中导入图片continue;}vector<string> size = render_split(item["size"], 'x');string width = size[0];string height = size[1];HtmlRender* img = new HtmlRender("img", "", {{"alt", ""},{"src", url}, {"width", width}, {"height", height}});body->add(img);}// ...
// ...
CppTinParser甚至给对<p>
的解析也单独移植了TinText中的逻辑:
void load_para(HtmlRender* p, vector<string> lines){//添加<p>内容vector<char> tags = {'*','-','_','/','=','!','^','&','#'};regex reg(".*?!\\[(.*)\\]\\((.*)\\)");smatch result;int count = 0;if (lines.size()==0){//空行HtmlRender* br = new HtmlRender("br", "", {}, true);p->add(br);return;}for (auto &line : lines) {HtmlRender* last = p;//p最后一个元素if (line[0] == ' '){//空格string text = line.substr(1);HtmlRender* p_item = new HtmlRender("", text, {});p->add(p_item);continue;}string head;if (line.size() <=9){head = line;}else{head = line.substr(0, 9);}//<p>开头标记需要连续count = 0;for (auto &tag_char : head){if (find(tags.begin(), tags.end(), tag_char) != tags.end()){count++;}else{break;}}head = head.substr(0, count);if (count == 0){HtmlRender* p_item = new HtmlRender("", line, {});p->add(p_item);continue;}if (head.find('*')!=string::npos){HtmlRender* nowlast = new HtmlRender("b", "", {});last->add(nowlast);last = nowlast;}if (head.find('/')!=string::npos){HtmlRender* nowlast = new HtmlRender("i", "", {});last->add(nowlast);last = nowlast;}if (head.find('_')!=string::npos){HtmlRender* nowlast = new HtmlRender("u", "", {});last->add(nowlast);last = nowlast;}if (head.find('-')!=string::npos){HtmlRender* nowlast = new HtmlRender("s", "", {});last->add(nowlast);last = nowlast;}if (head.find('=')!=string::npos){// = 和 # 只能存在一个HtmlRender* nowlast = new HtmlRender("mark", "", {});last->add(nowlast);last = nowlast;}else if (head.find('#')!=string::npos){HtmlRender* nowlast = new HtmlRender("code", "", {});last->add(nowlast);last = nowlast;}if (head.find('^')!=string::npos){// ^ 和 & 只能存在一个HtmlRender* nowlast = new HtmlRender("sup", "", {});last->add(nowlast);last = nowlast;}else if (head.find('&')!=string::npos){HtmlRender* nowlast = new HtmlRender("sub", "", {});last->add(nowlast);last = nowlast;}if (head.find('!')!=string::npos){bool ismatch = regex_match(line, result, reg);if (ismatch) {string text = result[1];string url = result[2];if (text == ""){text = url;}HtmlRender* p_item = new HtmlRender("a", text, {{"href",url}});last->add(p_item);}else{string text = line.substr(count);HtmlRender* p_item = new HtmlRender("", text, {});last->add(p_item);}}else{last->configcnt(line.substr(count));}}
}
结果
这里用的是TinText项目中的test.tin
。效果上来看,除了<code>
中对TinML的缺失以及对在线图片、tinfile不支持外,其他几乎一样,而且对于大文件的转译绝对比python实现要快。
这是源文件(部分):
这是最终效果(部分):

相关文章:

c++TinML转html
cTinML转html 前言解析解释转译html类定义开头html 结果这是最终效果(部分):  前言 在python.tkinter设计标记语言(转译2-html)中提到了将Ti…...
STM32硬件SPI函数解析与示例
1. SPI 简介 SPI(Serial Peripheral Interface)即串行外设接口,是一种高速、全双工、同步的通信总线,常用于微控制器与各种外设(如传感器、存储器等)之间的通信。STM32 系列微控制器提供了多个 SPI 接口&a…...

滤波器:卡尔曼滤波
卡尔曼滤波(Kalman Filter)是一种高效的递归算法,主要用于动态系统的状态估计。它通过结合系统模型和噪声干扰的观测数据,实现对系统状态的最优估计(在最小均方误差意义下)。以下从原理、使用场景和特点三个…...

深度学习框架探秘|TensorFlow vs PyTorch:AI 框架的巅峰对决
在深度学习框架中,TensorFlow 和 PyTorch 无疑是两大明星框架。前面两篇文章我们分别介绍了 TensorFlow(点击查看) 和 PyTorch(点击查看)。它们引领着 AI 开发的潮流,吸引着无数开发者投身其中。但这两大框…...

Windows环境管理多个node版本
前言 在实际工作中,如果我们基于Windows系统开发,同时需要维护老项目,又要开发新项目,且不同项目依赖的node版本又不同时,那么就需要根据项目切换不同的版本。本文使用Node Version Manager(nvm࿰…...
opencascade 源码学习BRepBuilderAPI-BRepBuilderAPI
BRepBuilderAPI BRepBuilderAPI 是一个用于构建和操作 BRep(边界表示法,Boundary Representation)拓扑数据结构的工具类。它提供了高级接口,用于创建几何形状(如顶点、边、面、实体等)以及进行扫掠&#x…...
Vue 2 + Webpack 项目中集成 ESLint 和 Prettier
在 Vue 2 Webpack 项目中集成 ESLint 和 Prettier 可以帮助你规范代码风格并自动格式化代码。以下是详细的步骤: 1. 安装 ESLint 和 Prettier 相关依赖 在项目根目录下运行以下命令,安装 ESLint、Prettier 和相关插件: npm install --save…...

Renesas RH850 EEL库的优点
文章目录 1. 磨损均衡(Wear Leveling)2. 数据抽象与易用性3. 后台维护与自动刷新4. 多优先级操作5. ECC 错误处理与数据完整性EEL 与 FDL 的协作机制1. 分层架构2. 存储池划分3. 协作流程4. 同步与互斥5. 性能优化实际应用场景示例场景:车辆里程存储总结1. 磨损均衡(Wear L…...
torch导出ONNX模型报错:OnnxExporterError: Module onnx is not installed
问题: 使用torch 导出模型为onnx文件时报错:torch.onnx.OnnxExporterError: Module onnx is not installed! 环境: 操作系统 Win10 python运行环境 Anacoda3 torch 2.6.0 torchvision …...

LabVIEW 用户界面设计基础原则
在设计LabVIEW VI的用户界面时,前面板的外观和布局至关重要。良好的设计不仅提升用户体验,还能提升界面的易用性和可操作性。以下是设计用户界面时的一些关键要点: 1. 前面板设计原则 交互性:组合相关的输入控件和显示控件&#x…...

使用Python爬虫实时监控行业新闻案例
目录 背景环境准备请求网页数据解析网页数据定时任务综合代码使用代理IP提升稳定性运行截图与完整代码总结 在互联网时代,新闻的实时性和时效性变得尤为重要。很多行业、技术、商业等领域的新闻都可以为公司或者个人发展提供有价值的信息。如果你有一项需求是要实时…...
qt QTextEdit用法总结
1. 基本介绍 QTextEdit 是 Qt 中用于显示和编辑富文本(支持 HTML 子集)和纯文本的控件。 支持文本格式(字体、颜色、对齐)、列表、表格、图片插入等富文本功能。 底层通过 QTextDocument 管理内容,提供强大的文本处理…...

《open3d qt 网格采样成点云》
open3d qt 网格采样成点云 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionMeshUniformSample_triggered();//均匀采样 void MainWindow::...

企业数据安全:切实有效的数据安全保障措施分享:
确保企业数据安全是一项持续不懈的任务,鉴于技术的飞速发展,网络攻击者持续探索新型手段以窃取敏感信息并谋取利益。若企业欲避免成为数据泄露的下一个牺牲品,就必须始终保持警觉,预先规划,以不变应万变。为了帮助企业…...

rocketmq-netty通信设计-request和response
1、NettyRemotingServer启动分析 org.apache.rocketmq.remoting.netty.NettyRemotingServer#start public void start() {this.defaultEventExecutorGroup new DefaultEventExecutorGroup(nettyServerConfig.getServerWorkerThreads(),new ThreadFactory() {private AtomicI…...

DeepSeek 助力 Vue 开发:打造丝滑的卡片(Card)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
计算机组成原理—— 总线系统(十一)
在追求梦想的旅途中,我们常常会遇到崎岖的道路和难以预料的风暴。然而,正是这些挑战塑造了我们的坚韧和毅力,使我们能够超越自我,触及那些看似遥不可及的目标。不要因为一时的困境而气馁,也不要因为他人的质疑而动摇自…...

电商小程序(源码+文档+部署+讲解)
引言 随着移动互联网的快速发展,电商小程序成为连接消费者与商家的重要桥梁。电商小程序通过数字化手段,为消费者提供了一个便捷、高效的购物平台,从而提升购物体验和满意度。 系统概述 电商小程序采用前后端分离的架构设计,服…...

8、k8s的pv和pvc
pv和pvc的概念 静态 动态——>自动分配 pv:persistent volume,持久化存储卷,描述或者定义存储卷的类型。集群范围内的存储概念,代表的是实际的存储空间(本地磁盘、网络系统文件nfs,云存储)…...

【limit 1000000,10 加载很慢该怎么优化?】
在 SQL 数据库中,使用 LIMIT 子句进行分页查询时,如果偏移量(offset)很大,查询性能可能会变得非常差。 这是因为数据库需要扫描和跳过大量的记录才能到达所需的起始位置,然后再取出所需的记录数。 例如,LIMIT 1000000, 10 表示跳过前 100 万条记录,然后取接下来的 10…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...