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…...
Gentleman:优雅的Go语言HTTP客户端工具包
gentlemen介绍,特点等 插件驱动架构:Gentleman的核心特点是其插件系统,允许用户注册和重用各种自定义插件,如重试策略或动态服务器发现,以增强HTTP客户端的功能。 中间件层:项目内置了一个上下文感知的层次…...
JDK1.8新特性面试题
lambda表达式 Lambda表达式极大地简化了匿名内部类的创建,促进了函数式编程风格。开发者可以更简洁地定义只有一行代码的函数对象,并将其作为参数传递给方法或者赋值给变量。 三要素: 形式参数、箭头、代码块 (形式参数…...
Windchill-PDM-设置环境
Web 浏览器和 Java 插件版本 请确保 Windchill PDMLink 支持您使用的 Web 浏览器及 Java 插件的版本。PTC 软件对照表内列出了所支持的浏览器和插件。 软件一览表列出了平台、操作系统以及经认证可以在 Windows 和 UNIX 操作系统上与 此版本一起使用的第三方产品的组合。产品和…...
《蓝耘服务器:领先架构、技术创新与行业应用全景解析》
🌟 嗨,我是Lethehong!🌟 🌍 立志在坚不欲说,成功在久不在速🌍 🚀 欢迎关注:👍点赞⬆️留言收藏🚀 🍀欢迎使用:小智初学…...
前端【技术方案】重构项目
1. 明确重构目标 优化性能 减少页面加载时间降低资源占用 提升代码可维护性 更规范的代码风格更清晰的代码结构更明确的模块设计 扩展功能 为项目添加新功能改进现有功能 2. 评估项目现状 审查代码 全面检查现有代码,找出代码中的问题,如代码冗余、耦合…...
C#中反射的原理介绍及常见的应用场景介绍
反射(Reflection)是C#中的一种机制,允许程序在运行时获取类型信息并动态调用其成员。通过反射,程序可以访问程序集、模块、类型及其成员(如方法、属性、字段等),并能在运行时创建对象、调用方法…...
uniapp webview嵌入外部h5网页后的消息通知
最近开发了个oa系统,pc端的表单使用form-create开发,form-create 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成组件。移动端使用uniapp开发,但是因为form-create移动端只支持vant,不支持uniapp。官…...
java听书项目
项目的架构 网关:1路由转发 2.认证鉴权(token)3.统一处理(跨域) Mysql:关系型数据库 ES:搜索数据库 Redis:页面级缓存,会话状态存储 GitLab:私有托管平台 K8S:自动化部署、扩展和管理容器化应用程序的开源系统 Jenkins:自动化部署 1.环境搭建 创建一个父工程…...
iOS 获取设备占用内存
获取应用占用内存 获取应用进程占用内存 - (NSUInteger)memoryUsage {task_vm_info_data_t vmInfo;mach_msg_type_number_t count TASK_VM_INFO_COUNT;kern_return_t result task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo, &count);if (result …...
C#功能测试
List 内部元素为引用 src[0]为"11" List<Source> src new List<Source>(); src.Add(new Source() { Name "1", Age 1, Description "1" }); src.Add(new Source() { Name "2", Age 2, Description "2"…...
js第八题
题八:滚动弹幕 要求: 1.页面上漂浮字体大小不一、颜色不一,从左向右滚动的弹幕; 2.底部中间有一个发送功能,可以发送新的弹幕; 3.底部的发送部分可以向下收起和弹出。 html <div id"danmu-con…...
第35次CCF计算机软件能力认证 python 参考代码
题目列表1. 密码2. 字符串变换3. 补丁应用4. 通讯延迟5. 木板切割题目列表 第35次CCF计算机软件能力认证 1. 密码 n = int(input()) for _ in range(n):s =...
2025百度快排技术分析:模拟点击与发包算法的背后原理
一晃做SEO已经15年了,2025年还有人问我如何做百度快速排名,我能给出的答案就是:做好内容的前提下,多刷刷吧!百度的SEO排名算法一直是众多SEO从业者研究的重点,模拟算法、点击算法和发包算法是百度快速排名的…...
国产FPGA开发板选择
FPGA开发板是学习和开发FPGA的重要工具,选择合适的开发板对学习效果和开发效率至关重要。随着国产FPGA的发展,淘宝上的许多FPGA开发板店铺也开始进行国产FPGA的设计和销售,本文将对国产FPGA和相关店铺做个简单梳理,帮助有需要使用…...
DeepSeek+即梦 做AI视频
DeepSeek做AI视频 制作流程第一步:DeepSeek 生成视频脚本和分镜 第二步:生成分镜图片绘画提示词第三步:生成分镜图片第四步:使用可灵 AI 工具,将生成的图片转成视频。第五步:剪映成短视频 DeepSeek 真的强&…...
【R语言】聚类分析
聚类分析是一种常用的无监督学习方法,是将所观测的事物或者指标进行分类的一种统计分析方法,其目的是通过辨认在某些特征上相似的事物,并将它们分成各种类别。R语言提供了多种聚类分析的方法和包。 方法优点缺点适用场景K-means计算效率高需…...
LVS相关原理
一、LVS集群的体系结构 1.1 LVS简介 LVS 是 Linux Virtual Server 的简称,也就是 Linux 虚拟服务器 , 是一个由章文嵩博士发起的自由软件项目,它的官方站点是 www.linuxvirtualserver.org 。现在 LVS 已经是 Linux标准内核的一部分,在Linux2…...
linux--关于makefile
makefile文件 可以指定编译顺序,这样方便一个项目的多个文件要编译的挨个操作的麻烦。 makefile文件的命名:makefile 或者 Makefile 必须是这俩,系统才能识别 规则的书写语法如下: 一个makefile内可以有多个规则 目标:依赖a 依…...
从2025年起:数字化建站PHP 8.1应成为建站开发的基准线
在数字化浪潮席卷全球的今天,PHP语言仍然保持着Web开发领域的核心地位。根据W3Techs最新统计,PHP驱动着全球78.9%的已知服务端网站。当时间指向2025年,这个拥有28年历史的编程语言将迎来新的发展里程碑——PHP 8.1版本应成为网站开发的最低基准要求,这不仅是技术迭代的必然…...
根据deepseek模型微调训练自动驾驶模型及数据集的思路
以下是使用DeepSeek模型微调训练自动驾驶模型的详细步骤和代码示例。本流程假设你已有自动驾驶领域的数据集(如驾驶指令、传感器数据等),并基于PyTorch框架实现。 Step 1: 环境准备 # 安装依赖库 pip install torch transformers datasets n…...
蓝桥杯篇---IAP15F2K61S2定时器
文章目录 前言简介定时器的工作模式1.模式02.模式13.模式24.模式3 定时器的寄存器1.TMOD2.TCON3.THO/TL04.TH1/TL1 定时器的使用步骤1.配置TMOD2.设置初值3.启动定时器4.使能中断5.编写中断服务函数 示例代码:定时器的基本使用代码说明示例代码:定时器1用…...
Java发展史
JavaEE的由来 语言的诞生 Java的前身是Oak语言,其目的是搞嵌入式开发开发智能面包机 叮~~~🍞🍞🍞 产品以失败告终 巅峰 网景公司需要网景浏览器打开网页,Oak->Java,进行前端开发(相关技…...
Jenkins 新建配置 Freestyle project 任务 六
Jenkins 新建配置 Freestyle project 任务 六 一、新建任务 在 Jenkins 界面 点击 New Item 点击 Apply 点击 Save 回到任务主界面 二、General 点击左侧 Configure Description:任务描述 勾选 Discard old builds Discard old builds:控制何时…...
Electron视图进程和主进程通讯
快速创建基于vue的electron项目:quick-start/create-electron - npm 视图线程也就index.html是无法直接访问这个api的(如果没有开启视图层访问nodejs的功能,现在几乎没法直接开启,开启了一堆警告提示) 所以需要通过r…...
【湖南-益阳】《益阳市市本级政府投资信息化项目预算编制与财政评审工作指南》益财评〔2024〕346号-省市费用标准解读系列40
《益阳市市本级政府投资信息化项目预算编制与财政评审工作指南(试行)》(益财评〔2024〕346号)由益阳市财政局主编,2024年10月17日起正式执行,本指南主要规定了政府投资信息化项目费用的构成、测量过程和方法…...
springboot+mybatis按条件分页查询多张表
文章目录 背景方案推荐创建 DTO创建 Mapper创建对应 xmlService 代码 背景 假如同 mysql 数据源下有如下几张表: 用户基础信息表用户地址表用户学历信息表 我希望做分页查询用户数据,用户数据为各个表内信息的汇总,并且这个分页查询会根据…...
探索Java中的集合类_特性与使用场景
1. 引言 1.1 Java集合框架概述 Java集合框架(Java Collections Framework, JCF)是Java中用于存储和操作一组对象的类和接口的统称。它提供了多种数据结构来满足不同的需求,如列表、集合、映射等。JCF的核心接口包括Collection、List、Set、Queue和Map,以及它们的各种实现…...
具身智能在智能巡检机器人中的应用——以开关柜带电操作机器人为例
随着机器人技术和人工智能的迅速发展,具身智能在各行业的应用日益广泛,尤其是在电力行业中的智能巡检领域。传统的电力巡检和维护工作通常需要人工操作,存在着高温、高压、强电磁场等危险环境,且效率较低。开关柜带电操作机器人作…...
C#+SqlSugar实现主从库读写分离
在使用 **SqlSugar** 进行分库操作时,可以通过配置多个数据库连接,并根据业务逻辑动态切换数据库。以下是一个完整的分库示例,展示如何实现分库功能。 --- ### **1. 安装 NuGet 包** 安装 SqlSugarCore: bash dotnet add packag…...
