当前位置:   article > 正文

c++ 解析html与htmlcxx库

htmlcxx

目录

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 文件:

  1. #Makefile 文件
  2. CPP = @echo "g++ $@"; g++ -std=c++11
  3. CC = @echo "gcc $@"; gcc
  4. LD = @echo "ld $@";ld
  5. AR = @echo "ar $@";ar
  6. RM = rm -f
  7. STRIP = @echo "strip $@";strip
  8. CFLAGS += -Wall -O2 -Os
  9. CFLAGS += -g
  10. LDFLAGS = "-Wl", -pthread -lc -static
  11. AFLAGS += -r
  12. include ./allRules.mk

 以及 allRules.mk 文件

  1. #allRules.mk 文件
  2. WORK_DIR = $(shell pwd)
  3. #源码目录
  4. SRCS_PATH = css \
  5. html
  6. COMPILE_PATH ?= Compile
  7. LIB_NAME = Htmlcxx
  8. LIB_PATH = Lib
  9. ##生成目标库目录
  10. STATIC_LIB_TARGET = $(LIB_PATH)/lib$(LIB_NAME).a
  11. TARGET = $(STATIC_LIB_TARGET)
  12. #cpp源文件
  13. LIB_SRCS_CPP = $(foreach dir, $(SRCS_PATH), $(wildcard $(dir)/*.cc))
  14. LIB_SRCS_C = $(foreach dir, $(SRCS_PATH), $(wildcard $(dir)/*.c))
  15. SRCS = $(LIB_SRCS_CPP) $(LIB_SRCS_C)
  16. #目标文件
  17. LIB_CPP_OBJS = $(patsubst %.cc, $(COMPILE_PATH)/%.o, $(LIB_SRCS_CPP))
  18. LIB_C_OBJS = $(patsubst %.c, $(COMPILE_PATH)/%.o, $(LIB_SRCS_C))
  19. LIB_OBJS = $(LIB_CPP_OBJS) $(LIB_C_OBJS)
  20. DEP_CPP := $(LIB_CPP_OBJS:%.o=%.cc.d)
  21. DEP_C := $(LIB_C_OBJS:%.o=%.c.d)
  22. DEP_ALL = $(DEP_CPP) $(DEP_C)
  23. all: $(TARGET)
  24. @echo $(TARGET)
  25. $(foreach dir, $(SRCS_PATH), $(shell mkdir -p $(COMPILE_PATH)/$(dir)))
  26. $(shell mkdir -p $(LIB_PATH))
  27. -include $(DEP_ALL)
  28. $(TARGET): $(LIB_OBJS)
  29. $(RM) $@
  30. $(AR) $(AFLAGS) -o $@ $(LIB_OBJS)
  31. test:
  32. $(MAKE) -C Test
  33. ############################################
  34. $(COMPILE_PATH)/%.o: %.cc
  35. $(CPP) -c $(CFLAGS) $< -o $@ -lpthread
  36. $(COMPILE_PATH)/%.o: %.c
  37. $(CC) -c $(CFLAGS) $< -o $@ $(LDFLAGS)
  38. ###################################################
  39. $(COMPILE_PATH)/%.cc.d: %.cc
  40. $(CPP) $(CFLAGS) -MM -E $^ > $@
  41. @sed 's/.*\.o/$(subst /,\/, $(dir $@))&/g' $@ >$@.tmp
  42. @mv $@.tmp $@
  43. $(COMPILE_PATH)/%.c.d: %.c
  44. $(CPP) $(CFLAGS) -MM -E $^ > $@
  45. @sed 's/.*\.o/$(subst /,\/, $(dir $@))&/g' $@ >$@.tmp
  46. @mv $@.tmp $@
  47. ############################ clean ############################
  48. PHONY: clean
  49. clean:
  50. $(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 及源码如下:

  1. #中间文件存放目录,如.o 和 .d 文件
  2. COMPILE_DIR = compile
  3. BIN_DIR = bin
  4. # 可编译arm版本
  5. # CROSS = arm-himix200-linux-
  6. CC = $(CROSS)gcc
  7. CPP = $(CROSS)g++ -std=c++11
  8. CFLAGS = -g -Wall
  9. CFLAGS += -I../html
  10. CFLAGS += -I../css
  11. LIB_DIR = -L../Lib -lHtmlcxx
  12. # INCLUDE = -I../threadpool/include
  13. # LIB = -L../threadpool/lib/x86 -lpthread -lThread
  14. # SRCS_CPP = $(wildcard *.cpp)
  15. SRCS_CPP = $(shell ls -t | grep "\.cpp$$" | head -1)
  16. OBJS = $(patsubst %.cpp, $(COMPILE_DIR)/%.o, $(SRCS_CPP))
  17. DEP = $(patsubst %.o, %.d, $(OBJS))
  18. $(shell if [ ! -d $(COMPILE_DIR) ]; then mkdir $(COMPILE_DIR); fi)
  19. $(shell if [ ! -d $(BIN_DIR) ]; then mkdir $(BIN_DIR); fi)
  20. TARGET=$(BIN_DIR)/a.out
  21. all: $(TARGET)
  22. -include $(DEP)
  23. $(TARGET): $(OBJS)
  24. $(CPP) $(INCLUDE) $(CFLAGS) $^ -o $@ $(LIB) $(LIB_DIR)
  25. $(COMPILE_DIR)/%.o: %.cpp $(COMPILE_DIR)/%.d
  26. $(CPP) $(INCLUDE) $(CFLAGS) -c $< -o $@ $(LIB)
  27. $(COMPILE_DIR)/%.d: %.cpp
  28. @$(CPP) $(INCLUDE) $(CFLAGS) -MM -E -c $< -o $@
  29. @sed 's/.*\.o/$(subst /,\/,$(dir $@))&/g' $@ > $@.tmp
  30. @mv $@.tmp $@
  31. .PHONY: clean
  32. clean:
  33. rm -rf $(COMPILE_DIR) $(BIN_DIR)
  1. #include <string.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <iostream>
  5. #include <fstream>
  6. #include <memory>
  7. #include "ParserDom.h"
  8. void initMap();
  9. bool isAllSpace(std::string &str);
  10. void eraseAllSpace(std::string &str);
  11. int getLineInfo(std::string &lineStr);
  12. bool htmlParse(std::string &htmlStr, FILE *file);
  13. bool readFile(const char *fileName, const char *outFile);
  14. bool findLineInfo(std::string &lineStr, std::string &findStr);
  15. using namespace std;
  16. using namespace htmlcxx;
  17. #define TABSTOP 4
  18. struct ESCAPECHAR_INFO
  19. {
  20. ESCAPECHAR_INFO(size_t len, std::string ch): mLen(len), mOriCh(ch)
  21. {
  22. }
  23. size_t mLen; //转义字符的长度
  24. std::string mOriCh;//真实的字符
  25. };
  26. //原本想定义成<ESCAPECHAR_INFO, st::string> 但自定义类型要重载"<",否则编译不过的
  27. std::map<std::string, ESCAPECHAR_INFO> escapeCharMap;
  28. int main(int argc, char *argv[])
  29. {
  30. if(argc != 2)
  31. {
  32. printf("Usage: %s file\n", argv[0]);
  33. return -1;
  34. }
  35. std::string outFile(argv[1]);
  36. size_t pos = outFile.find(".");
  37. size_t len = outFile.length();
  38. if(pos != std::string::npos)
  39. {
  40. outFile.replace(pos + 1, len - pos, "cc");
  41. }
  42. initMap();
  43. readFile(argv[1], outFile.c_str());
  44. return 0;
  45. }
  46. //常用的转义字符表,遇到再添加吧
  47. void initMap()
  48. {
  49. escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&lt;", ESCAPECHAR_INFO(4, "<")));
  50. escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&gt;", ESCAPECHAR_INFO(4, ">")));
  51. escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&quot;", ESCAPECHAR_INFO(6, "\"")));
  52. escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&amp;", ESCAPECHAR_INFO(5, "&")));
  53. escapeCharMap.insert(std::pair<std::string, ESCAPECHAR_INFO>("&#39;", ESCAPECHAR_INFO(5, "'")));
  54. }
  55. bool readFile(const char *fileName, const char *outFile)
  56. {
  57. FILE *fp = nullptr;
  58. if((fp = fopen(fileName, "r")) == nullptr)
  59. {
  60. printf("fopen error: %s", strerror(errno));
  61. return false;
  62. }
  63. FILE *saveFp = fopen(outFile, "w");
  64. fseek(fp, 0, SEEK_END);
  65. long len = ftell(fp);
  66. fseek(fp, 0, SEEK_SET);
  67. char buf[4] = {0};
  68. size_t ret = 0;
  69. size_t readSize = 0; //已经读取的字符总数
  70. size_t totalLine = 0; //总行数
  71. size_t curPos = 0; //当前位置
  72. size_t tempPos = 0; //保存上一次位置
  73. size_t curLineLen = 0; //当前行长度,用于申请内存
  74. size_t nilLine = 0; //空行总数
  75. size_t srcTotalLine = 0; //源码总行数
  76. size_t lineIndex = 1; //源码行计数
  77. bool found = false; //是否找到源码行数信息
  78. bool lineIdxFound = false; //每行源码都会有一个对应行号
  79. while(len - readSize > 0)
  80. {
  81. if((ret = fread(buf, 1, 1, fp)) != 0)
  82. {
  83. readSize += ret;
  84. if(strcmp(buf, "\n") == 0)
  85. {
  86. tempPos = curPos;
  87. curPos = ftell(fp);
  88. totalLine++;
  89. curLineLen = curPos - tempPos;
  90. if(curLineLen > 1)
  91. {
  92. fseek(fp, -(curLineLen), SEEK_CUR);
  93. std::shared_ptr<char> ptr(new char[curLineLen], std::default_delete<char[]>());
  94. memset(ptr.get(), 0, curLineLen);
  95. fread(ptr.get(), curLineLen, 1, fp);
  96. std::string str(ptr.get(), curLineLen);
  97. //已经找到这里不再进来
  98. if(srcTotalLine == 0 && found)
  99. {
  100. found = false;
  101. srcTotalLine = getLineInfo(str);
  102. }
  103. //同上
  104. std::string tmp("text-mono");
  105. if(srcTotalLine == 0 && findLineInfo(str, tmp))
  106. {
  107. // printf("find the src line = %u\n", totalLine);
  108. found = true;
  109. }
  110. //当下面找到倒数第2个的时候,这里的lineIndex已经是+1的值了,如果
  111. //直接和srcTotalLine比较就直接break了,实际是少了2行,因为我们总是
  112. //取下一行的内容
  113. if(lineIndex == srcTotalLine + 2)
  114. {
  115. break;
  116. }
  117. if(srcTotalLine)
  118. {
  119. char buf[256] = {0};
  120. //这里找到之后取的是下一行的内容,因为下面是lineIndex++,在这里用的时候已经是+1后的值
  121. snprintf(buf, sizeof(buf), "data-line-number=\"%u\"", lineIndex);
  122. if(lineIdxFound)
  123. {
  124. lineIdxFound = false;
  125. htmlParse(str, saveFp);
  126. }
  127. if(str.find(buf) != std::string::npos)
  128. {
  129. lineIdxFound = true;
  130. lineIndex++;
  131. }
  132. }
  133. }
  134. else
  135. {
  136. nilLine++;
  137. }
  138. }
  139. memset(buf, 0, sizeof(buf));
  140. }
  141. }
  142. fclose(fp);
  143. fclose(saveFp);
  144. return true;
  145. }
  146. //找到html里行数相关的信息
  147. bool findLineInfo(std::string &lineStr, std::string &findStr)
  148. {
  149. return (lineStr.find(findStr) != std::string::npos);
  150. }
  151. //取得源码总行数,html里有标示源码总行籹
  152. int getLineInfo(std::string &lineStr)
  153. {
  154. eraseAllSpace(lineStr);
  155. //这里直接用 atoi 比较合适,它在遇到第一个不是数字时返回
  156. //正好是我需要的
  157. int srcLine = atoi(lineStr.c_str());
  158. printf("src Total Line = %d\n", srcLine);
  159. return srcLine;
  160. }
  161. //清空所有空格
  162. void eraseAllSpace(std::string &str)
  163. {
  164. size_t index = 0;
  165. while((index = str.find_first_of(" ")) != std::string::npos)
  166. {
  167. str.erase(index, 1);
  168. }
  169. }
  170. //转义字符
  171. void escapeChar(std::string &str)
  172. {
  173. size_t index = 0;
  174. for(auto ite : escapeCharMap)
  175. {
  176. while((index = str.find(ite.first.c_str())) != std::string::npos)
  177. {
  178. str.replace(index, ite.second.mLen, ite.second.mOriCh.c_str());
  179. }
  180. }
  181. }
  182. //是否全部是空格,太长空格不写入文件
  183. bool isAllSpace(std::string &str)
  184. {
  185. size_t index = 0;
  186. size_t len = str.length();
  187. if(len <= TABSTOP)
  188. {
  189. return false;
  190. }
  191. for(; index < len; index++)
  192. {
  193. if(str[index] != ' ')
  194. {
  195. break;
  196. }
  197. }
  198. return index == len;
  199. }
  200. //用htmlcxx里的例子,稍等修改一下
  201. bool htmlParse(std::string &htmlStr, FILE *saveFp)
  202. {
  203. //Parse some html code
  204. HTML::ParserDom parser;
  205. tree<HTML::Node> dom = parser.parseTree(htmlStr);
  206. //Dump all links in the tree
  207. tree<HTML::Node>::iterator it = dom.begin();
  208. tree<HTML::Node>::iterator end = dom.end();
  209. //Dump all text of the document
  210. it = dom.begin();
  211. end = dom.end();
  212. for (; it != end; ++it)
  213. {
  214. if ((!it->isTag()) && (!it->isComment()))
  215. {
  216. std::string srcStr(it->text());
  217. if(isAllSpace(srcStr))
  218. {
  219. continue;
  220. }
  221. // 转义字符处理一下
  222. escapeChar(srcStr);
  223. fwrite(srcStr.c_str(), srcStr.length(), 1, saveFp);
  224. }
  225. }
  226. return true;
  227. }

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

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

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

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

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

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

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号