当前位置: 首页 > article >正文

c++TinML转html

c++TinML转html

  • 前言
  • 解析
  • 解释
  • 转译html
    • 类定义
    • 开头html
  • 结果
    • 这是最终效果(部分): ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6cf6c3e3c821446a84ae542bcc2652d4.png)

前言

在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会改成\0
            Lines = 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);//转译为html
    void 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实现要快。

这是源文件(部分):
在这里插入图片描述


在这里插入图片描述

这是最终效果(部分):
在这里插入图片描述

在这里插入图片描述


http://www.kler.cn/a/545309.html

相关文章:

  • Broker: Unknown topic or partition 问题解决
  • 爬虫代码中如何设置请求间隔?
  • Android 原生层SurfaceView截屏
  • PL/SQL 变量以及数据类型(上篇)
  • 【React】react-redux+redux-toolkit实现状态管理
  • 基于WebAssembly的后端服务突破:打造高性能、安全的新型微服务架构
  • 27、深度学习-自学之路-NLP自然语言处理-做一个简单的项目识别一组电影评论,来判断电影评论是积极的,还是消极的。
  • Golang 进阶训练营
  • Rocky Linux系统修改网卡全攻略
  • 【清晰教程】本地部署DeepSeek-r1模型
  • C语言插入排序之直接插入排序
  • 【网络安全 | 漏洞挖掘】价值3133美元的Google IDOR
  • VS Code 通知中一直显示“Reactivating terminals...”的问题解决
  • 解决 paddle ocr 遇到 CXXABI_1.3.13 not found 的问题
  • JAVA DDD设计模式:用策略模式干掉满屏if-else
  • 吉祥汽车泰国首发,用 Unity 实现行业首创全 3D 座舱虚拟世界
  • 【深度学习】计算机视觉(CV)-目标检测-SSD(Single Shot MultiBox Detector)—— 单次检测多框检测器
  • 了解卷积神经网络(Convolutional Neural Network,CNN)
  • React组件重新渲染机制
  • App UI自动化--Appium学习--第二篇