整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript 数组进行拼接的函数

时候,我们希望在 JavaScript 中将 2 个已经存在的数组串拼接成 1 个数组。

简单来说就是将数组进行合并。

这个时候,我们可以使用 JavaScript 的 concat 函数。

考察下面的代码:

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

console.log(array3);
// expected output: Array ["a", "b", "c", "d", "e", "f"]

上面的代码将 2 个数组 array1 和 array2 合并成了一个新的数组为 array3, 在这个新的数组中的元素就是
array2 在 array1 后面添加得到的。

如果你需要合并的数组不只有 2 个,你还有多个的话,你可以同样使用上面的方法,但是在参数中传递进多个数组就行。

concat()
concat(value0)
concat(value0, value1)
concat(value0, value1, ... , valueN)

如上面的代码,你并不需要将 concat 多次运行来进行合并,concat 这个方法允许传递多个需要合并数组为参数。

网盗概念^-^相同的测试脚本使用不同的测试数据来执行,测试数据和测试行为完全分离, 这样的测试脚本设计模式称为数据驱动。(网盗结束)当我们测试某个网站的登录功能时,我们往往会使用不同的用户名和密码来验证登录模块对系统的影响,那么如果我们每一条数据都编写一条测试用例,这无疑是增加了代码量,代码重复,且显得那么臃肿(谁不喜欢身材好的呢?你懂的),这时候我们可以使用不同数据驱动代码执行相同的用例测试不同的场景。

一、实施数据驱动步骤

我们再来说说实施数据驱动测试的步骤:

1.创建/准备测试数据

2.封装读取数据的方法,保留测试脚本调用的接口/属性(我们需要传递给脚本什么参数)

3.编写自动化测试脚本

4.脚本中调用封装好的处理数据文件的模块并引入测试数据

5.执行测试脚本并分析测试结果

二、数据驱动测试环境准备

1.安装python3.x开发环境(能看到此文章的应该都有这个环境,没有的自行百度吧)

2.安装数据驱动模块ddt

安装方式1:cmd下执行命令 pip install ddt

安装方式2:https://pypi.org/simple/ddt/ 下载 并解压任意目录,cmd 运行命令python setup.py install

3.验证安装 pycharm 新建python文件并输入 import ddt 运行无报错信息既表示安装成功或者cmd 命令依次输入python回车 import ddt回车 无保存信息表示安装成功

4.unittest框架和ddt进行数据驱动

三、测试步骤

1.访问地址:https://mail.sohu.com/fe/#/login

2.输入用户名和密码

3.点击登录按钮

4.判断是否登录成功

四、数据存储

数据存在当前脚本中

数据准备

我们要实现的是用户登录的操作,所以用户名和密码是必须有的,期望结果可以有也可以没有。数据类型看源代码!

五、实例代码 

 1 from selenium import webdriver
 2 from ddt import ddt, data, unpack
 3 import unittest
 4 import time
 5 from selenium.common.exceptions import NoSuchWindowException
 6 '''
 7 简单数据驱动测试
 8 '''
 9 @ddt
10 class ddtTest(unittest.TestCase):
11     # 数据 可以是元祖, 列表, 字典(可迭代对象)
12     value = [['12691569846@sohu.com', 'xiacha11520','https://mail.sohu.com/fe/#/homepage'],
13              ['12691569844@sohu.com', 'xiacha11520','https://mail.sohu.com/fe/#/homepage']]
14     # value = [{'uname':'******@sohu.com', 'password':'xiacha11520','expected':'https://mail.sohu.com/fe/#/homepage'},
15     #          {'uname':'******@sohu.com', 'password':'xiacha11520','expected':'https://mail.sohu.com/fe/#/homepage'}]
16     def setUp(self):
17         self.testUrl = 'https://mail.sohu.com/fe/#/login'
18         self.driver = webdriver.Firefox()
19         self.driver.get(self.testUrl)
20 
21     @data(*value) # * 解析数据
22     @unpack# 用来解包, 将每组数据的第一个数据传递给uname依次类推, 当数据为字典时,形参需和字段的key值相同
23     def test_case1(self, uname, password, expected):
24         try:
25             username = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的邮箱']")
26             username.send_keys(uname)
27             time.sleep(1)
28             userpassword = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的密码']")
29             userpassword.send_keys(password)
30             self.driver.find_element_by_xpath("//input[@type='submit']").click()
31             time.sleep(2)
32             currenturl = self.driver.current_url
33             self.assertEqual(expected, currenturl,'登录失败')
34         except NoSuchWindowException as e:
35             print(e)
36             raise
37         except AssertionError:
38             print('期望值是{}, 实际值是{}'.format(expected,currenturl))
39             raise
40         except Exception:
41             raise
42     def tearDown(self):
43         self.driver.quit()
44         # pass
45 if __name__ == '__main__':
46     unittest.main()

六、源码分析

1.@ddt来装饰测试类(ddt数据驱动的规范写法,记住就ok)

2.@data(*value)装饰测试用例(也是一种规范,这边又涉及到装饰器,不懂的可以百度,这边不再赘述,一句话两句话也说不清楚)记住:*value作用是打散数据,比如上面代码是用一个大列表存储两个小列表存放数据的,那么*value会得到两个小列表,每个小列表是一组测试数据

3.@unpack 解析*value数据,会把两个小列表里面的每一个数据取出来分别传递给我们测试用例的形参

方式1缺点

存储大量数据时,需查看源代码,不利于脚本的维护    

七、json文件读取测试数据进行数据驱动测试

数据准备

新建一个json文件(也可以是txt文件),将我们需要的两组测试数据以列表的形式写到json文件中,每组数据的每一项参数用相同的符号分割开(方便脚本读取数据)

实例代码

{
  "value1":"******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage",
  "value2":"******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage"
}
 1 from selenium import webdriver
 2 from ddt import ddt, file_data
 3 import unittest, time
 4 from selenium.common.exceptions import NoSuchWindowException
 5 import HTMLTestRunner
 6 '''
 7 从文件中读测试数据
 8 '''
 9 
10 @ddt # ddt装饰测试类
11 class Testdata(unittest.TestCase):
12 
13     def setUp(self):
14         self.driver = webdriver.Firefox()
15         self.driver.get('https://mail.sohu.com/fe/#/login')
16 
17     @file_data('test_data.json') # 读取文件的 文件中数据可以是一个列表,也可以是一个字典
18     def test_data(self,value):
19         uname, password, expected = tuple(value.strip().split('||')) # value是一个字符串
20         # print(type(value),value)
21         try:
22             username = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的邮箱']")
23             username.send_keys(uname)
24             time.sleep(1)
25             userpassword = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的密码']")
26             userpassword.send_keys(password)
27             self.driver.find_element_by_xpath("//input[@type='submit']").click()
28             time.sleep(2)
29             currenturl = self.driver.current_url
30             self.assertEqual(expected, currenturl,'登录失败')
31         except NoSuchWindowException as e:
32             raise e
33         except AssertionError:
34             print('期望值是{}, 实际值是{}'.format(expected,currenturl))
35             raise
36         except Exception:
37             raise
38 
39     def tearDown(self):
40         self.driver.quit()
41 if __name__ == '__main__':
42     unittest.main()
43     # import os
44     # from datetime import date
45     # currentPath = os.path.dirname(os.path.abspath(__file__))# 获取当前文件目录
46     # reportPath = os.path.join(currentPath,'report') # 创建一个report目录
47     # if not os.path.exists(reportPath):
48     #     os.mkdir(reportPath) # 判断目录是否存在, 不存在就创建
49     # reportName = os.path.join(reportPath, str(date.today())+'report.html') # 拼接html报告
50     # with open(reportName,'wb') as f:
51     #     suite = unittest.TestLoader().loadTestsFromTestCase(Testdata)
52     #     runner = HTMLTestRunner.HTMLTestRunner(stream=f,verbosity=1, title='数据驱动测试报告', description='数据驱动')
53     #     runner.run(suite)

源码分析

1.相对上个实例,这里使用了@file_data(文件路径), 参数必须是一个文件,这里是一个json文件, 数据可以是一个列表,也可以是一个字典

# 列表形式
[
  "*******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage",
  "*******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage"
]

2.测试用例接收的是一个字符串,需要对字符串进行处理,把用户名,密码,期望值解析出来

方式2优缺点

测试数据存在文件中,方便管理修改,添加数据,易于维护,缺点呢?emmm个人认为这种方式最好!

八、从xml读取数据进行数据驱动测试

数据准备

新建一个xml格式的文件,按照xml格式的语法需求,填写数据(xml文档我也不是很懂,简单的显示个文字啥的还可以^-^!)

<?xml version="1.0"?>
<bookList type="technolog">
    <book>
        <uname>******@sohu.com</uname>
        <password>xiaochao11520</password>
        <expected>https://mail.sohu.com/fe/#/homepage</expected>
    </book>
    <book>
        <uname>******@sohu.com</uname>
        <password>xiaochao11520</password>
        <expected>https://mail.sohu.com/fe/#/homepage</expected>
    </book>
</bookList>

实例代码

 1 from xml.etree import ElementTree
 2 
 3 class ParseXml(object):
 4     def __init__(self, xmlpath):
 5         self.xmlpath = xmlpath
 6 
 7     # 获取根节点
 8     def getRoot(self):
 9         tree = ElementTree.parse(self.xmlpath)
10         root = tree.getroot()
11         return root
12 
13     # 根据根节点查找子节点
14     def findNodeByName(self, parentNode, nodeName):
15         nodes = parentNode.findall(nodeName)
16         return nodes
17 
18     def getNodeOfChildText(self, node):
19         # 获取节点node下所有子节点的节点名作为key
20         # 本节点作为value组成的字典对象
21         childrenTextDict = {}
22         for i in list(node.iter())[1:]: # node 节点下的所有节点组成的列表
23             childrenTextDict[i.tag] = i.text
24         # print(list(node.iter())[1:])
25         return childrenTextDict
26 
27     # 获取节点node下面的节点的所有数据
28     def getDataFromXml(self, node):
29         root = self.getRoot()
30         books = self.findNodeByName(root, node)
31         dataList=[]
32         for book in books:
33             childrentext = self.getNodeOfChildText(book)
34             dataList.append(childrentext)
35         return dataList
36 if __name__=='__main__':
37     xml = ParseXml('./xmlData.xml')
38     root = xml.getRoot()
39     print(root.tag)
40     books = xml.findNodeByName(root, 'book') # 查找所有的book节点
41     for book in books:
42         # print(book[0].tag, book[0].text)
43         print(xml.getNodeOfChildText(book))
44     print(xml.getDataFromXml('book'))
 1 from dataDdt.doXML import ParseXml
 2 from selenium import webdriver
 3 from selenium.common.exceptions import NoSuchWindowException, TimeoutException
 4 import unittest
 5 from ddt import ddt, data,unpack
 6 import time
 7 from selenium.webdriver.support import expected_conditions as EC
 8 from selenium.webdriver.support.ui import WebDriverWait
 9 from selenium.webdriver.common.by import By
10 values = ParseXml('./xmlData.xml')
11 @ddt
12 class xmltest(unittest.TestCase):
13 
14     def setUp(self):
15         self.driver = webdriver.Firefox()
16         self.driver.get('https://mail.sohu.com/fe/#/login')
17     @data(*values.getDataFromXml('book'))
18     @unpack
19     def test_xml(self,uname, password, expected):
20         try:
21             wait = WebDriverWait(self.driver,5)
22             wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit']")))
23             username = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的邮箱']")
24             username.send_keys(uname)
25             time.sleep(1)
26             userpassword = self.driver.find_element_by_xpath("//input[@placeholder='请输入您的密码']")
27             userpassword.send_keys(password)
28             self.driver.find_element_by_xpath("//input[@type='submit']").click()
29             time.sleep(2)
30             currenturl = self.driver.current_url
31             self.assertEqual(expected, currenturl, '登录失败')
32         except TimeoutException as e:
33             raise e
34         except NoSuchWindowException as e:
35             raise e
36         except AssertionError as e:
37             print('期望值是{}, 实际值是{}'.format(expected, currenturl))
38             raise e
39         except Exception:
40             raise
41     def tearDown(self):
42         self.driver.quit()
43 
44 if __name__=='__main__':
45     unittest.main()

源码分析

1.xml文档编写(深入了解需百度)有点像html,但又有不同,xml中的节点可以是任意名称,每个节点同样是成双出现

2.增加了doXML.py文档,用来解析xml文件,方便脚本获取数据(注释写的很详细,不懂的化可以慢慢调试,哪里不懂print哪里)

3.测试脚本和上面的实例大致相同

方式3优缺点

优点是做到了数据与测试的分离,方便数据维护,缺点也比较明显,需要对xml文档有一定的了解

总结

上面的数据驱动测试步骤是我自己总结的,看了上面的实例对于步骤应该还算合理,下面是我在网上找到的数据驱动测试步骤(感觉比较官方!大家可以参考)

1.编写测试脚本, 脚本需要支持从程序对象, 文件,或者数据库读入数据。(个人观点:如果脚本先编写完,测试数据还未准备,后期还要做修改)

2.将测试脚本使用的测试数据存入程序对象,文件,或者数据库等外部介质中。(个人观点:这个阶段实为准备数据的阶段,也就是我们数据要存在哪里,理应放在第一步)

3.运行脚本过程中,循环调用存储在外部介质中的测试数据。(个人观点:这里要考虑我们如何读取,使用数据)

4.验证所有的测试结果是否符合预期结果

最后今天的文章就到这里了,喜欢的可以点赞收藏加关注。

阅读本文大概需要 6 分钟

日常开发软件可能会遇到这类小众需求,导出数据到 WordExcel 以及 PDF文件,如果你使用 C++ 编程语言,那么可以选择的方案不是很多,恰好最近刚好有这部分需求,整理下这段时间踩过的坑,方便后人

读写 Word

日常开发的软件使用最多的应该是导出数据到 Word 文档中,目前可以用的方案有这几种

没有十全十美的方案,任何方案都存在优点和缺点,下面来详细看下这几种方案的优缺点以及适用场景

XML 模板替换

原理:事先编辑好一份 Word 模板,需要替换内容的 地方预留好位置,然后使用特殊字段进行标记,后面使用代码进行全量替换即可完成

优点

  • 代码量相对较少、导出速度快
  • 跨平台,支持多个系统,系统不安装 office 也能导出;
  • 支持图片以及固定格式导出;

缺点

  • 导出格式固定,可扩展性不强,如果需求变化导出格式变了,那么模板也要跟着改变;
  • 一种格式对应一份模板,如果导出格式较多,需要准备的模板文件较多,这样比较繁琐;
  • 需要 Word 2003 以上版本;

举个栗子

我们先编辑一份 Word 模板文档,内容大概如下所示:

  • 将该文档另存为 Word XML 文档 XML-Template.xml
  • 读取文档内容进行变量替换
    QFile file("XML-Template.xml");
    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug() << "open xxml file fail. " << file.errorString();
        return 0;
    }
    QByteArray baContent = file.readAll();
    file.close();
    QString strAllContent = QString::fromLocal8Bit(baContent);

    strAllContent.replace("$VALUE0", "1");
    strAllContent.replace("$VALUE1", QString::fromLocal8Bit("法外狂徒张三"));
    strAllContent.replace("$VALUE2", QString::fromLocal8Bit("考试不合格"));
    strAllContent.replace("$VALUE3", "2");
    strAllContent.replace("$VALUE4", QString::fromLocal8Bit("李四"));
    strAllContent.replace("$VALUE5", QString::fromLocal8Bit("合格"));

    QFile newFile("export.doc");
    if (!newFile.open(QIODevice::WriteOnly))
    {
        qDebug() << "file open fail." << newFile.errorString();;
        return 0;
    }

    newFile.write(strAllContent.toLocal8Bit());
    newFile.close();
  • 保存替换后的内容,写入文件

可以看出来这种方式比较繁琐,重点是编辑固定的模板格式,而且编辑好后保存成XML格式后还需要继续调整,这种 XML 格式标签很多,不小心就修改错了,导致导出的文档打不开

这种方式适合模板内容不太复杂,内容较少的情况下使用

COM 组件方式

原理:采用 Micro Soft公开的接口进行通讯,进行读写时会打开一个 `Word进程来交互

COM 技术概述

Qt 为我们提供了专门进行交互的类和接口,使用 Qt ActiveX框架就可以很好的完成交互工作

优点

  • 实现简单,快速上手;

缺点

  • 导出写入速度慢,因为相当于打开 word 文档操作;
  • Windows平台可用,其它平台失效;
  • 需要程序运行的电脑安装 office word,否则调用失败

举个栗子

使用时需要引入对应的模块,在 pro 文件引入模块

QT  *= axcontainer

打开文档写入内容

QAxObject *pWordWidget = new(std::nothrow) QAxObject;

bool bResult = pWordWidget->setControl("word.Application");

if (!bResult)
{
    return false;
}

// 设置是否显示
pWordWidget->setProperty("Visible", false);

QAxObject *pAllDocuments = pWordWidget->querySubObject("Documents");

if(nullptr == pAllDocuments)
{
    return false;
}

// 新建一个空白文档
pAllDocuments->dynamicCall("Add (void)");

// 获取激活的文档并使用
QAxObject *pActiveDocument = pAllDocuments->querySubObject("ActiveDocument");
if(nullptr == pActiveDocument)
{
    return false;
}

// 插入字符串
QAxObject *pSelectObj = pWordWidget->querySubObject("Selection");
if (nullptr != pSelectObj)
{
    pSelectObj->dynamicCall("TypeText(const QString&)", "公众号:devstone");
}

……

可以看出来使用起来不难,对于新手友好一点,很多写入操作方法比较繁琐,需要自己重新封装一套接口

  • 这种方案比较适合那些排版比较复杂,图片、文字、表格混排的场景下,而且内容都是动态变化的,可以很好的实现定制化
  • 当然了它的缺点也不少,也有一些坑,有时候莫名其妙会失败,还有就是比如你电脑安装的 Word 没有激活,那么每次启动会弹激活窗口
  • 还有就是这种方式要求所有的路径必须是本地化的,比如 D:\Soft\test.png
  • 使用前最好读取注册表判断当前电脑是否安装了 Office Word,如果没有安装,直接读取操作肯定会崩溃

这种方式同样适用于写入 Excel 文件,后面再说

HTML 方式

原理:这种方式得益于 Word支持 HTML格式导出渲染显示,那么反向也可以支持,需要我们拼接 HTML格式内容,然后写入文件保存成 .doc格式

优点

  • 跨平台,不仅限于 Windows平台,代码可扩展性比较好
  • 导出速度快、代码可扩展;

缺点

  • 字符串拼接 HTML 容易出错,缺失标签导出后无法显示;
  • 插入的图片是本地图片文件的链接,导出的 word文档拷贝到其它电脑图片无法显示

举个栗子

QString HTML2Word::getHtmlContent()
{
    QString strHtml = "";
    strHtml += "<html>";
    strHtml += "<head>";
    strHtml += "<title>测试生成word文档</title>";
    strHtml += "<head>";
    strHtml += "<body style=\"bgcolor:yellow\">";
    strHtml += "<h1 style=\"background-color:red\">测试qt实现生成word文档</h1>";
    strHtml += "<hr>";
    strHtml += "<p>这里是插入图片<img src=\"D:\\title.jpg" alt=\"picture\" width=\"100\" height=\"100\"></p>";
    strHtml += "</hr>";
    strHtml += "</body>";
    strHtml += "</html>";

    return strHtml;
}

// 保存写入文件
QFile file("D:/htmp2Word.doc");
if (!file.open(QIODevice::WriteOnly))
{
    return false;
}

QTextStream out(&file);
out << getHtmlContent();
file.close();

这种方式难点在于 HTML格式拼接,任何缺失字段都会导致导出失败,适合小众需求下导出

图片问题其实可以手动进行转化,文档导出成功后手动拷贝内容到新的文档,这样图片就真正插入到文档中,文档发送给别人也不会丢失图片了

还有一个坑就是:如果你使用 WPS 打开导出的文档,默认显示的是 web视图,需要手动进行调整

某些电脑分辨率变化也会导致生成的文档中字体等产生变化

第三方开源库

可以使用的第三方库几乎没有,网络上找到的有这么几个

  • OpenOffice: 兼容性差,集成调用难度大
  • LibOffice: 太庞大,不容易集成
  • DuckX: 太小众,只能简单的使用
  • docx:小众库

DuckX库 docx库

在读写 Word这部分,C++ 基本没有可以使用的第三方库,不像其他语言JavaC#Python有很多可以选择,这个痛苦也只有 C++ 程序员能够理解了吧

所以怎么选择还是看自己项目需求吧,没有十全十美的方案


上面说了这么多,都是导出生成 Wrod,那么下面来看看有那些方式可以读取显示 Word内容

这种需求应该不会很多,而且显示难度更大一些

使用 COM组件方式,即采用 QAxWidget框架显示 office 文档内容,本质上就是在我们编写的 Qt 界面上嵌入 office 的软件,这种方式其实和直接打开 Word查看没有啥区别,效果、性能上不如直接打开更好一些

目前一般都会采用折中方案,把 Word 转为 PDF 进行预览加载显示,我们知道 PDF 渲染库比较多,生态相对来说要好一些,在选择上就更广泛些,如何使用后面部分有专门介绍 PDF章节

读写 Excel

目前有一个支持比较好的第三方库可以使用,整体使用基本可以满足日常使用

QXlsx

这款开源库支持跨平台,Linux、Windows、Mac、IOS、Android,使用方式支持动态库调用和源码直接集成,非常方便

编译支持 qmakecmake,可以根据你自己的项目直接集成编译,读写速度非常快

QXlsx::Document xlsx;

// 设置一些样式
QXlsx::Format titleFormat;
titleFormat.setBorderStyle(QXlsx::Format::BorderThin);  // 边框样式
titleFormat.setRowHeight(1,1,30);   // 设置行高
titleFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter);   // 设置对齐方式

// 插入文本
xlsx.write(1,1, "微信公众号:devstone", titleFormat);

// 合并单元格
xlsx.mergeCells(QXlsx::CellRange(2,1,4,4), titleFormat);

// 导出保存
xlsx.saveAs("D:/xlsx_export.xlsx");

// 添加工作表
xlsx.addSheet("devstone");

可以看到上手非常容易、各个函数命名也贴近 Qt Api,是一款非常良心的开源软件

PS:注意该软件使用 MIT 许可协议,这样对于很多个人或者公司来说非常良心,意味着你可以无偿使用、修改该项目,但是必须在你项目中也添加同样的 MIP许可

上面也提到了,还可以使用 COM 组件的方式读写 Excel,不过有了这款开源库基本就可以告别 COM组件方式了

读写 PDF

PDF相关开源库挺多的,给了 C++ 程序员莫大的帮助,目前可用的主要有这些

其中 mupdfpoppler 属于功能强大但是很难编译的那种,需要有扎实的三方库编译能力,否则面对 n 个依赖库会无从下手

不过可喜的是 Github 上有两个开源库可以供选择

qpdf 库

这个库其实封装了 pdf.js库,使用 WebEngine来执行 JavaScript进而加载文件

项目地址

  • 直接从本地文件加载;
  • 支持从内存数据直接加载渲染 PDF 内容;

这种方式对环境有特殊要求了,如果你的项目使用的 Qt 版本不支持 WebEngine,那么就无法使用

qtpdf 库

这个库是 Qt 官方亲自操刀对第三方库进行了封装,暴露的 APIQt 类似,使用起来非常舒服

Qt 官方

代码结构以及使用 Demo

小试牛刀

关于如何使用,官方已经给了我们非常详细的步骤了,直接跟着下面几步就 OK 了

官方教程

git clone git://code.qt.io/qt-labs/qtpdf
cd qtpdf
git submodule update --init --recursive
qmake
make
cd examples/pdf/pdfviewer
qmake
make

./pdfviewer /path/to/my/file.pdf

可以看到使用了谷歌开源的 pdfium 三方库,编译时需要单独更新下载这个库,因为某些原因可能你无法下载,不过好在有人在 GitHub上同步了这个仓库的镜像,有条件还是建议直接下载最新稳定版的

可正常访问的仓库地址:https://github.com/PDFium/PDFium

相关类可以看这个文档:https://developers.foxit.com/resources/pdf-sdk/c_api_reference_pdfium/modules.html

最后还要注意项目开源协议:pdfium引擎开始来自于福昕,一个中国本土的软件公司,Google与其合作最终进行了开源,目前采用的是 BSD 3-Clause 协议,这种协议允许开发者自由使用、修改源代码,也可以修改后重新发布,允许闭源进行商业行为,不过需要你在发布的产品中包含原作者代码中的 BSD 协议

总结

以上就是项目中常用的文档处理方法总结,当然了肯定也还有其它方案可以实现,毕竟条条大路通罗马,如果你还要不错的方案和建议欢迎留言

PS: 以上方案和对应的源码编译、使用例子会统一上传到 GitHub对应的仓库,方便后人使用

取之互联网、回报互联网

原创不易,如果觉得对你有帮助,欢迎点赞、在看、转发

推荐阅读

  • Qt Creator 源码学习笔记01,初识QTC
  • Qt Creator 源码学习笔记02,认识框架结构结构
  • Qt Creator 源码学习笔记03,大型项目如何管理工程
  • Qt Creator 源码学习笔记04,多插件实现原理分析
  • Qt Creator 源码学习笔记 05,菜单栏是怎么实现插件化的?