c++ 解析html与htmlcxx库

news2024/11/28 12:53:00

目录

1,htmlcxx Github 版本源码下载         

2,htmlcxx Linux 版本源码下载

3,htmlcxx 解析例子


1,htmlcxx Github 版本源码下载         

        正如在前一篇文章 c++ CFile 类  里提到的,我想要从指定的 html 文件里提取代码,今天终于实现了,用到了开源的 htmlcxx 库。这个开源库可以在 github htmlcxx 上下载,但这个github 上的代码似乎是 window 版本的,它带了 window 的项目文件,而没有 Linux 下的 configure 或是Makefile,如:

如果你是Linux 开发的话,得自己写 Makefile 来编译了,这里是我的 Makefile 文件:

#Makefile 文件
CPP = @echo "g++ $@"; g++ -std=c++11
CC = @echo "gcc $@"; gcc
LD =  @echo "ld $@";ld
AR =  @echo "ar $@";ar
RM =  rm -f
STRIP = @echo "strip $@";strip

CFLAGS += -Wall -O2 -Os
CFLAGS += -g 

LDFLAGS = "-Wl", -pthread -lc -static
AFLAGS += -r

include ./allRules.mk

 以及 allRules.mk 文件

#allRules.mk 文件
WORK_DIR = $(shell pwd)

#源码目录
SRCS_PATH = css	\
			html



COMPILE_PATH ?= Compile
LIB_NAME = Htmlcxx
LIB_PATH = Lib

##生成目标库目录
STATIC_LIB_TARGET = $(LIB_PATH)/lib$(LIB_NAME).a
TARGET = $(STATIC_LIB_TARGET)		


#cpp源文件
LIB_SRCS_CPP = $(foreach dir, $(SRCS_PATH), $(wildcard $(dir)/*.cc))
LIB_SRCS_C = $(foreach dir, $(SRCS_PATH), $(wildcard $(dir)/*.c))
SRCS = $(LIB_SRCS_CPP) $(LIB_SRCS_C)

#目标文件
LIB_CPP_OBJS = $(patsubst %.cc, $(COMPILE_PATH)/%.o, $(LIB_SRCS_CPP))
LIB_C_OBJS = $(patsubst %.c, $(COMPILE_PATH)/%.o, $(LIB_SRCS_C))
LIB_OBJS = $(LIB_CPP_OBJS) $(LIB_C_OBJS)

DEP_CPP := $(LIB_CPP_OBJS:%.o=%.cc.d)
DEP_C := $(LIB_C_OBJS:%.o=%.c.d)
DEP_ALL = $(DEP_CPP) $(DEP_C)


all: $(TARGET)
	@echo $(TARGET)
	
$(foreach dir, $(SRCS_PATH), $(shell mkdir -p $(COMPILE_PATH)/$(dir)))
$(shell mkdir -p $(LIB_PATH))

-include $(DEP_ALL)

$(TARGET): $(LIB_OBJS)
	$(RM) $@
	$(AR) $(AFLAGS) -o $@ $(LIB_OBJS) 

test:
	$(MAKE) -C Test
############################################
$(COMPILE_PATH)/%.o: %.cc
	$(CPP) -c $(CFLAGS) $< -o $@ -lpthread

$(COMPILE_PATH)/%.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@	 $(LDFLAGS)

	
###################################################
$(COMPILE_PATH)/%.cc.d: %.cc
	$(CPP) $(CFLAGS) -MM -E $^ > $@
	@sed 's/.*\.o/$(subst /,\/, $(dir $@))&/g' $@ >$@.tmp
	@mv $@.tmp $@
$(COMPILE_PATH)/%.c.d: %.c
	$(CPP) $(CFLAGS) -MM -E $^ > $@
	@sed 's/.*\.o/$(subst /,\/, $(dir $@))&/g' $@ >$@.tmp
	@mv $@.tmp $@

############################ clean ############################
PHONY: clean
clean:
	$(RM) -r $(TARGET) $(COMPILE_PATH)


	

编译成静态库,如下:

编译过程会遇到一个错误,这个在 window 下应该不会遇到, 搜索了一下这个宏是没地方用到的,所以直接注释掉就行了。

2,htmlcxx Linux 版本源码下载

        这个地址  htmlcxx linux 版本源码 下载的就是 Linux 版本的,没有依赖库,解压后,进到目录里执行: ./configure;make 直接编译出来的是动态库,然后 make install,或者你不想 install 的话,那在编译例子的时候就要指定头文件路径,库路径,因为是动态库,所以在运行前还得设置动态库搜索路径: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库的绝对路径,否则会提示找不到 so 库文件而运行失败的。

3,htmlcxx 解析例子

        下面是源码,方法很简单,就是从文件里一行一行的提取出来,然后调用 htmlcxx 库接口进行解析,解析出来后把内容写到文件里。Makefile 及源码如下:


#中间文件存放目录,如.o 和 .d 文件
COMPILE_DIR = compile
BIN_DIR = bin

# 可编译arm版本
# CROSS = arm-himix200-linux-
CC = $(CROSS)gcc
CPP = $(CROSS)g++ -std=c++11
CFLAGS = -g -Wall
CFLAGS += -I../html
CFLAGS += -I../css

LIB_DIR = -L../Lib -lHtmlcxx
# INCLUDE = -I../threadpool/include
# LIB = -L../threadpool/lib/x86 -lpthread -lThread
# SRCS_CPP = $(wildcard *.cpp)
SRCS_CPP = $(shell ls -t | grep "\.cpp$$" | head -1)
OBJS = $(patsubst %.cpp, $(COMPILE_DIR)/%.o, $(SRCS_CPP))


DEP = $(patsubst %.o, %.d, $(OBJS))

$(shell if [ ! -d $(COMPILE_DIR) ]; then mkdir $(COMPILE_DIR); fi)
$(shell if [ ! -d $(BIN_DIR) ]; then mkdir $(BIN_DIR); fi)


TARGET=$(BIN_DIR)/a.out

all: $(TARGET)

-include $(DEP)

$(TARGET): $(OBJS)
	$(CPP) $(INCLUDE) $(CFLAGS) $^ -o $@ $(LIB) $(LIB_DIR)
$(COMPILE_DIR)/%.o: %.cpp $(COMPILE_DIR)/%.d
	$(CPP) $(INCLUDE) $(CFLAGS) -c $< -o $@ $(LIB)


$(COMPILE_DIR)/%.d: %.cpp
	@$(CPP) $(INCLUDE) $(CFLAGS) -MM -E -c $< -o $@
	@sed 's/.*\.o/$(subst /,\/,$(dir $@))&/g' $@ > $@.tmp
	@mv $@.tmp $@


.PHONY: clean
clean:
	rm -rf $(COMPILE_DIR) $(BIN_DIR)



#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <memory>
#include "ParserDom.h"

void initMap();
bool isAllSpace(std::string &str);
void eraseAllSpace(std::string &str);
int getLineInfo(std::string &lineStr);
bool htmlParse(std::string &htmlStr, FILE *file);
bool readFile(const char *fileName, const char *outFile);
bool findLineInfo(std::string &lineStr, std::string &findStr);

using namespace std;
using namespace htmlcxx;

#define TABSTOP 4

struct ESCAPECHAR_INFO
{
    ESCAPECHAR_INFO(size_t len, std::string ch): mLen(len), mOriCh(ch)
    {

    }

    size_t mLen; //转义字符的长度
    std::string mOriCh;//真实的字符
    
};

//原本想定义成<ESCAPECHAR_INFO, st::string> 但自定义类型要重载"<",否则编译不过的
std::map<std::string, ESCAPECHAR_INFO> escapeCharMap;

int main(int argc, char *argv[])
{
    if(argc != 2) 
    {
        printf("Usage: %s file\n", argv[0]);
        return -1;
    }

    std::string outFile(argv[1]);
    size_t pos = outFile.find(".");
    size_t len = outFile.length();

    if(pos != std::string::npos)
    {
        outFile.replace(pos + 1, len - pos, "cc");
    }

    initMap();
    readFile(argv[1], outFile.c_str());
    return 0;
}

//常用的转义字符表,遇到再添加吧
void initMap()
{
    escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&lt;", ESCAPECHAR_INFO(4, "<")));
    escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&gt;", ESCAPECHAR_INFO(4, ">")));
    escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&quot;", ESCAPECHAR_INFO(6, "\"")));
    escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&amp;", ESCAPECHAR_INFO(5, "&")));
    escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&#39;", ESCAPECHAR_INFO(5, "'")));
}

bool readFile(const char *fileName, const char *outFile)
{
    FILE *fp = nullptr;
    if((fp = fopen(fileName, "r")) == nullptr)
    {
        printf("fopen error: %s", strerror(errno));
        return false;
    }

    FILE *saveFp = fopen(outFile, "w");

    fseek(fp, 0, SEEK_END);
    long len =  ftell(fp);
    fseek(fp, 0, SEEK_SET);

    char buf[4] = {0};
    size_t ret = 0;
    size_t readSize = 0;    //已经读取的字符总数
    size_t totalLine = 0;   //总行数
    size_t curPos = 0;      //当前位置
    size_t tempPos = 0;     //保存上一次位置
    size_t curLineLen = 0;  //当前行长度,用于申请内存
    size_t nilLine = 0;     //空行总数
    size_t srcTotalLine = 0; //源码总行数
    size_t lineIndex = 1;   //源码行计数
    bool found = false;     //是否找到源码行数信息
    bool lineIdxFound = false; //每行源码都会有一个对应行号

    while(len - readSize > 0)
    {
        if((ret = fread(buf, 1, 1, fp)) != 0)
        {
            readSize += ret;
            if(strcmp(buf, "\n") == 0)
            {
                tempPos = curPos;
                curPos = ftell(fp);
                totalLine++;

                curLineLen = curPos - tempPos;
                if(curLineLen > 1)
                {
                    fseek(fp, -(curLineLen), SEEK_CUR);
                    std::shared_ptr<char> ptr(new char[curLineLen], std::default_delete<char[]>());
                    memset(ptr.get(), 0, curLineLen);
                    fread(ptr.get(), curLineLen, 1, fp);
                    std::string str(ptr.get(), curLineLen);
                    
                    //已经找到这里不再进来
                    if(srcTotalLine == 0 && found)
                    {
                        found = false;
                        srcTotalLine = getLineInfo(str);
                    }

                    //同上
                    std::string tmp("text-mono");
                    if(srcTotalLine == 0 && findLineInfo(str, tmp))
                    {
                        // printf("find the src line = %u\n", totalLine);
                        found = true;
                    }

                    //当下面找到倒数第2个的时候,这里的lineIndex已经是+1的值了,如果
                    //直接和srcTotalLine比较就直接break了,实际是少了2行,因为我们总是
                    //取下一行的内容
                    if(lineIndex == srcTotalLine + 2)
                    {
                        break;
                    }

                    if(srcTotalLine)
                    {
                        char buf[256] = {0};
                        
                        //这里找到之后取的是下一行的内容,因为下面是lineIndex++,在这里用的时候已经是+1后的值
                        snprintf(buf, sizeof(buf), "data-line-number=\"%u\"", lineIndex);
                        if(lineIdxFound)
                        {
                            lineIdxFound = false;
                            htmlParse(str, saveFp);
                        }
                        
                        if(str.find(buf) != std::string::npos)
                        {
                            lineIdxFound = true;
                            lineIndex++;
                        }                      
                    }
                }
                else
                {
                    nilLine++;
                }
            }
            memset(buf, 0, sizeof(buf));
        }
    }

    fclose(fp);
    fclose(saveFp);

    return true;
}

//找到html里行数相关的信息
bool findLineInfo(std::string &lineStr, std::string &findStr)
{
    return (lineStr.find(findStr) != std::string::npos);
}

//取得源码总行数,html里有标示源码总行籹
int getLineInfo(std::string &lineStr)
{
    eraseAllSpace(lineStr);

    //这里直接用 atoi 比较合适,它在遇到第一个不是数字时返回
    //正好是我需要的
    int srcLine = atoi(lineStr.c_str());
    printf("src Total Line = %d\n", srcLine);

    return srcLine;
}

//清空所有空格
void eraseAllSpace(std::string &str)
{
    size_t index = 0;
    while((index = str.find_first_of(" ")) != std::string::npos)
    {
        str.erase(index, 1);
    }
}

//转义字符
void escapeChar(std::string &str)
{
    size_t index = 0;
    for(auto ite : escapeCharMap)
    {
        while((index = str.find(ite.first.c_str())) != std::string::npos)
        {
           str.replace(index, ite.second.mLen, ite.second.mOriCh.c_str());
        }
    }
}

//是否全部是空格,太长空格不写入文件
bool isAllSpace(std::string &str)
{
    size_t index = 0;
    size_t len = str.length();
    if(len <= TABSTOP)
    {
        return false;
    }

    for(; index < len; index++)
    {
        if(str[index] != ' ')
        {
            break;
        }
    }
    
    return index == len;
}

//用htmlcxx里的例子,稍等修改一下
bool htmlParse(std::string &htmlStr, FILE *saveFp)
{
    //Parse some html code
    HTML::ParserDom parser;
    tree<HTML::Node> dom = parser.parseTree(htmlStr);

    //Dump all links in the tree
    tree<HTML::Node>::iterator it = dom.begin();
    tree<HTML::Node>::iterator end = dom.end();

    //Dump all text of the document
    it = dom.begin();
    end = dom.end();
    for (; it != end; ++it)
    {
        if ((!it->isTag()) && (!it->isComment()))
        {
            std::string srcStr(it->text());
            if(isAllSpace(srcStr))
            {
               continue;
            }

            // 转义字符处理一下
            escapeChar(srcStr);
            fwrite(srcStr.c_str(), srcStr.length(), 1, saveFp);
        }
    }

    return true;
}

首先看一下实际 github 上源码的那个页面,如下:

红框那里指明了源码是 154 行,用上面代码执行的结果如下:

因为代码里是通过搜索"text-mono" 来找到源码的行数的,那搜索的这个文件里明显有两个"text-mono",但这里执行也没有关系,因为0也不会做什么:

最终写到文件里的代码就是这样子了:

其实相差得不多,基本就是格式问题。下面再以这个1357 行代码的 debug.cc 为例,先下载再解析。

然后我从网页上拷贝源码下来,然后跟程序执行的结果进行一下比较,内容是一样的,只是格式上有点区别:

总结:这个好像没有多大的实际意义,因为很少有人想单个文件下载,即使想要单个文件的源码,也是在页面上去拷贝,但如果这个源码比较大,如上面的1357行源码,拷贝起来也是不容易的,这个时候有个自动提取代码的程序应该是个不错的选择。只需要用 wget 下载下来,再用程序执行一下就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/620433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【裸机开发】镜像文件内部信息构成

由于当前使用的是裸板&#xff0c;没有OS&#xff0c;DDR的初始化、文件保存地址都尚未确定&#xff0c;所以我们生成二进制文件以后&#xff0c;是无法直接放到开发板上运行的。 因此&#xff0c;我们一般会在二进制文件之前加一些头部信息&#xff0c;这些头部信息会告诉开发…

xsync集群分发脚本

脚本&#xff1a; #!/bin/bash #1. 判断参数个数 if [ $# -lt 1 ] thenecho Not Enough Arguement!exit; fi #2. 遍历集群所有机器 for host in hadoop02 hadoop03 doecho $host #3. 遍历所有目录&#xff0c;挨个发送for file in $do#4. 判断文件是否存在if [ -e $file ]th…

文本三剑客awk:命令讲解

awk-命令讲解&#xff1a; 一、awk&#xff1a; 1.定义&#xff1a; &#xff08;1&#xff09;在 Linux/UNIX 系统中&#xff0c;awk 是一个功能强大的编辑工具&#xff0c;逐行读取输入文本&#xff0c;默认以空格或tab键作为分隔符作为分隔。并按模式或者条件执行编辑命令…

006-从零搭建微服务-注册中心(二)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff1a;https://gitee.com/csps/mingyue 文档地址&#xff1a;https://gitee.com/csps/mingyue/wikis 核心依赖 需要注册配置中心的服务引入下面 …

步进电机基本原理及驱动方式详解

步进电机基本原理及驱动方式详解 步进电机相关概念基本原理类型和结构转子结构定子结构 励磁方式ATD9800 驱动使用实例 参考文献 步进电机相关概念 步进电机是一种常用于控制和定位应用的电动机&#xff0c;其独特的工作方式使其在许多领域中都具有广泛的应用。步进电机以其 精…

正态总体下常见的抽样分布

1.正态总体下常见的抽样分布 本人博客&#xff1a;总体分布、样本分布、抽样分布的区别 本人博客&#xff1a;三大抽样分布 正态总体下常见的抽样分布意思是&#xff1a;样本来自服从正态分布的总体中&#xff0c;从样本中抽样后得到的分布 1.1 单个正态总体下的抽样分布&…

【高危】GitLab CE/EE 存在存储型XSS漏洞

漏洞描述 GitLab 是一款基于Git的代码托管、版本控制、协作开发平台。 在 GitLab CE/EE 15.11 至 15.11.6 版本以及 16.0 至 16.0.1 版本中&#xff0c;当GitLab导入GitHub仓库时&#xff0c;如果GitHub仓库中包含由用户构造的恶意JavaScript代码的标签颜色&#xff0c;解析这…

为什么说程序员和产品经理一定要学一学PMP

要回答为什么说程序员和产品经理一定要学一学PMP&#xff1f;我们得先看一下PMP包含的学习内容。PMP新版考纲备考参考资料绝大多数涉及IT项目的敏捷管理理念。主要来源于PMI推荐的10本参考书&#xff1a; 《敏捷实践指南&#xff08;Agile Practice Guide&#xff09;》 《项目…

Java匿名内部类、invoke方法、动态代理

一、匿名内部类 匿名内部类一般作为方法的参数&#xff0c;这个方法的形参为接口&#xff0c;而实参为匿名内部类&#xff08;可以理解为接口的对象&#xff09;并且重写了接口中的方法。 例如&#xff1a; 定义了一个接口Star&#xff1a; 类型为Star的引用数据类型作为方法s…

关于docker中Nacos启动成功访问不了的情况

书接上回&#xff0c;这段时间在忙学成在线的微服务项目&#xff0c;上次出现了IP修改的问题&#xff0c;有了一定的解决方案&#xff0c;复制别人的虚拟机后如何修改IP_SSSEdward的博客-CSDN博客。 这次docker正常启动了&#xff0c;但是出现了访问不了的情况&#xff0c;但是…

短视频矩阵系统源码打包附赠分享

矩阵系统源码主要有三种框架&#xff1a;Spring、Struts和Hibernate。Spring框架是一个全栈式的Java应用程序开发框架&#xff0c;提供了IOC容器、AOP、事务管理等功能。Struts框架是一个MVC架构的Web应用程序框架&#xff0c;用于将数据模型、Web应用程序的用户界面和控制器逻…

【备战秋招】Java异常处理

Java程序在执行过程中所发生的异常事件可分为两类&#xff1a; Error&#xff1a;Java虚拟机无法解决的严重问题。如&#xff1a;JVM系统内部错误、资源 耗尽等严重情况。 比如&#xff1a;StackOverflowError和OOM。一般不编写针对性的代码进行处理。 Exception: 其它因编程错…

使用pytorch进行FFT和STFT

首先&#xff0c;我们定义一个波形&#xff0c;幅值分别为20和38&#xff0c;频率为2和13&#xff1a; y 20 sin ⁡ ( 2 π 2 x ) 38 sin ⁡ ( 2 π 13 x ) y20 \sin (2\pi \times 2x)38\sin (2\pi \times 13x) y20sin(2π2x)38sin(2π13x) 采样频率为200Hz&#xff0c;采…

十四届蓝桥杯省赛CB

hinhin可否不要老&#xff0c;令创新高 A 日期统计B 01 串的熵C 冶炼金属D: 飞机降落E: 接龙数列F: 岛屿个数G: 子串简写H: 整数删除I: 景区导游J: 砍树 A 日期统计 #include <bits/stdc.h> using namespace std; #define int long long int #define pii pair<int,int…

VR云探校:解锁不一样的视角,“沉浸式”打卡校园

仲夏六月&#xff0c;又是一年高考时&#xff0c; 高考首战已经正式拉开帷幕&#xff0c; 超千万考生赴考&#xff0c;更有千万考生家长花式赴考。 少年的你披星戴月走过的路&#xff0c;终将会繁华遍地&#xff0c;今天VR全景就为千万考生和家长解锁不一样的视角&#xff0…

java观察者模式例子

观察者模式&#xff1a; 是一种常用的设计模式&#xff0c;它定义了一种一对多的关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当主题对象状态发生变化时&#xff0c;它的所有观察者都会收到通知并且自动更新。 在观察者模式中&#xff0c;主题对象通…

Minicap编译及简单使用

什么是MiniCap 官方说法&#xff1a;Minicap提供了一个套接字接口&#xff0c;用于从Android设备流式传输实时屏幕捕获数据。它旨在用作更大程序中的组件&#xff0c;因此仅凭其本身并不是非常有用。例如&#xff0c;它在STF中用于远程控制。 个人理解&#xff1a;Minicap是用…

如何检查 Android 设备是否支持 Widevine DRM

想知道您的 Android 设备是否可以流式传输来自 Netflix 和 Amazon Prime Video 的高清视频&#xff1f;这是检查 Widevine DRM 支持的方法&#xff01; 许多流媒体服务&#xff0c;如 Netflix、Disney、Hulu 等&#xff0c;使用各种类型的数字版权管理 (DRM) 来保护其内容不被…

VulnHub项目:Hogwarts dobby

靶机地址&#xff1a;Hogwarts: Dobby ~ VulnHub 哈利波特也是初中时候最喜欢的电影~Dobby多比是马尔福加的奴隶精灵&#xff0c;出现在第二部密室中&#xff0c;后来被哈利波特的袜子所拯救&#xff0c;成为了一只快乐自由的小精灵&#xff0c;最后它在死亡圣器中&#xff0c…

keil从非0x08000000地址调试STM32单片机程序方法

在使用stm32单片机的串口IAP升级功能时&#xff0c;需要调试bootloader程序和app程序&#xff0c;一般bootloader的程序都是从0x08000000地址开始运行的&#xff0c;使用keil软件可以直接进行仿真&#xff0c;但是如果调试app程序的话&#xff0c;由于它不是从0x08000000地址开…