使用c语言libexpat开源库解析XML数据

news2025/1/17 6:12:17

1 libexpat简介

  • Expat 是一个用 C 语言编写的开源 XML 解析库,以其高性能和小巧的体积著称。Expat 兼容多种操作系统平台,包括但不限于 Windows、Linux、macOS 等。由于其跨平台特性和简单易用的API,Expat 成为了许多C/C++程序员解析XML文档的首选工具之一。
  • 主要特性:
    • 面向流的解析器:Expat 不像 DOM 解析器那样把整个XML文档加载到内存中形成树状结构,而是采用逐行解析的方式处理XML数据。这意味着它适合处理大型或者无限流式的XML数据输入,因为它不需要一次性加载整个文档到内存。
    • 事件驱动解析:Expat 使用回调函数机制来报告解析过程中的事件,如元素开始、元素结束、字符数据块等。开发者需要提供这些回调函数,并通过 Expat API 注册,以便在解析过程中接收并处理这些事件。
    • 轻量级和高效:Expat 因其简洁的设计和快速的解析速度而受到青睐,尤其对于资源受限的环境或者对性能要求较高的应用来说是一个理想的选择。

2 环境部署

  • 如果自己不想编译源代码,可使用我已经编译好的 expat使用,直接跳过环境部署介绍。
  • expat源码下载地址

2.1 Windows平台编译

  • Winodws平台编译需要安装Visual Studio,推荐使用2015及以上版本。
  • 下载源码后解压进入代码根目录下的expat目录中,创建一个build_x84文件夹,在build_x86文件夹中执行以下命令
  •   cmake -G "Visual Studio 14 2015" ..
      cmake --build ./ --config Release
    
  • 编译成功后,会在expat\build_x86\Release目录下生成对应的静态库和动态库
  • 还需要用到3个头文件,expat\build_x86目录下会生成一个expat_config.h头文件,expat\lib目录下有expat.h和expat_external.h这两个头文件。
  • 将对应的库文件和这三个头文件拷贝到我们的工程中。

2.2 Linux平台编译

  • Linux平台推荐使用Centos7编译
  • 同样解压后进入代码根目录下的expat目录中,创建一个build_x64文件夹,在build_x64文件夹中依次执行以下命令
  •   ./buildconf.sh     # 执行后会生成configure文件
      ./configure --prefix=${PWD}/_install 
      sudo make #编译
      sudo make install # 安装,会安装到执行configure时--prefix参数指定目录下,不指定会安装到默认目录下
    
  • 执行完以上命令在expat/_install目录下会生成头文件、库文件和可执行程序等。
  • 将头文件和库文件拷贝到我们的工程目录下。

3 接口介绍

  • 介绍下常用的几个API接口,有几个函数可能不好理解,在4章节的demo中会结合实例说明。

3.1 创建XML解析器实例

  •   /*
      * encoding: 规定输出编码,填NULL默认为UTF-8,支持ISO-8859-1, UTF-8, US-ASCII 这三种编码方式
      * 返回值: 创建成功返回一个XML解析器实例,创建失败返回NULL
      */
      XML_Parser XML_ParserCreate(const XML_Char *encoding);
    

3.2 设置用户自定义的数据

  •   /*
      * parser: XML解析器实例
      * userData: 指向任意类型数据的指针。可以指向用户自定义的数据结构,通常是为了在解析过程中传递上下文信息或者存储解析结果
      */
      void XML_SetUserData(XML_Parser parser, void *userData);
    

3.3 注册处理XML数据开始和结束事件的回调函数

  •   /*
      * parser: XML解析器实例
      * start: 处理元素开始事件的回调函数,可查看3.7
      * end: 处理元素结束事件的回调函数,可查看3.8
      */
      void XML_SetElementHandler(XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end);
    

3.4 注册处理XML文本内容事件的回调函数

  •   /*
      * parser: XML解析器实例
      * handler: 处理XML数据中的文本内容的回调函数,可查看3.9
      */
      void XML_SetCharacterDataHandler(XML_Parser parser, XML_CharacterDataHandler handler);
    

3.6 解析缓冲区中的XML数据

  •   /*
      * parser: XML解析器实例
      * buffer: XML数据的缓冲区
      * isFinal: 指示本次调用是否代表了整个XML输入的结束
      * 返回值: 成功返回 XML_STATUS_OK
      */
      XML_Status XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) ;
    

3.7 处理XML数据字段开始的回调函数

  •   /*
      * 说明: 首先通过3.3接口注册这个回调函数,然后执行3.6接口开始解析,每碰到一个新字段这个函数就被回调一次
      * userData: 通过3.2接口传递进来的参数,可通过这个值将解析出来的数据返回出去
      * name: 开始字段名称
      * atts: 指向NULL结尾的XML_Char指针数组, 每两个连续的元素构成一个键值对,分别表示元素的属性名和属性值
      */
      void(XMLCALL *XML_StartElementHandler)(void *userData, const XML_Char *name, const XML_Char **atts);
    

3.8 处理XML数据字段结束的回调函数

  •   /*
      * 说明: 首先通过3.3接口注册这个回调函数,然后执行3.6接口开始解析,每碰到一个字段结束这个函数就被回调一次
      * userData: 通过3.2接口传递进来的参数,可通过这个值将解析出来的数据返回出去
      * name: 开始字段名称
      */
      void(XMLCALL *XML_EndElementHandler)(void *userData, const XML_Char *name);
    

3.9 处理XML数据文本内容的回调函数

  •   /*
      * 说明: 首先通过3.4接口注册这个回调函数,然后执行3.6接口开始解析,每碰到文本内容这个函数就被回调一次
      * userData: 通过3.2接口传递进来的参数,可通过这个值将解析出来的数据返回出去
      * s: 文本内容
      * len: 文本内容长度 
      */
      void(XMLCALL *XML_CharacterDataHandler)(void *userData, const XML_Char *s, int len);
    

4 实例演示

  • XML测试数据
  •   <?xml version="1.0"?>
      <data>
      	<header hattr="http">
      		<type>Post</type>
      		<host>127.0.0.1</host>
      	</header>
      		<body battr="base64">
      		<data1>aGVsbG8=</data1>
      		<data2>ZXhwYXQ=</data2>
      	</body>
      </data>
    
  • 测试代码
  •   #include <stdio.h>
      #include <expat.h>
      #include <iostream>
      #include <vector>
      #include <map>
      #ifndef _WIN32
      #include <string.h>
      #endif
      
      // 定义一个结构,保存字段名和字段值,这里为了演示简洁属性值就不保存了
      typedef struct USERDATA {
      	std::string strName; //字段名
      	std::string strValue; // 字段值
      }StUserData;
      
      // 调用 XML_Parse 开始解析数据后,只要碰到字段名,这个函数就会被调用
      // 比如碰到data开始时,该函数会被回调一次,碰到header开始时,会再次被回调
      void startElement(void *userData, const XML_Char *name, const XML_Char **atts)
      {
      	// 将字段名保存
      	std::vector<StUserData> *vecData = (std::vector<StUserData>*)userData;	
      	StUserData stData;
      	stData.strName.assign(name);
      	vecData->insert(vecData->end(), stData);
      	
      	// 打印字段名
      	printf("startElement name : %s\n", name);
      	
      	// 打印属性
      	for (int i = 0; atts[i]; i += 2) {
      		// 属性名和属性值
      		printf("%s:%s\n", atts[i], atts[i + 1]);
      	}
      }
      
      
      // 调用 XML_Parse 开始解析数据后,只要碰到字段名结束,这个回调函数就会被调用
      // 比如碰到header结束时,该函数会被回调一次
      void endElement(void *userData, const XML_Char *name)
      {
      	printf("endElement name : %s\n", name);
      }
      
      // 调用 XML_Parse 开始解析数据后,只要碰到文本,这个函数就会被回调
      // 比如碰到data和header时,并没有文本内容,下一层还有数据,因此不会被调用
      // 碰到type时,有文本内容了,是Post,因此该函数会被调用
      void characterData(void *userData, const XML_Char *s, int len) {
      	// startElement 被调用后,只要对应的字段名有值,这个函数就会被调用
      	// 所以文本值保存到最后一个数据中,保证字段名和文本内容对应
      	std::vector<StUserData> *vecData = (std::vector<StUserData>*)userData;
      	StUserData stData;
      	stData.strName = vecData->at(vecData->size() - 1).strName;
      	stData.strValue.assign(s, len);
      	vecData->at(vecData->size() - 1) = stData;
      
      	// 打印文本内容
      	printf("value : ");
      	for (int i = 0; i < len; i++) {
      		printf("%c", s[i]);
      	}
      	printf("\n");
      }
      
      int main(int argc, const char *argv[])
      {
      	std::vector<StUserData> vecData;
      
      	XML_Parser parser = XML_ParserCreate(NULL);
      	if (parser == NULL) {
      		return -1;
      	}
      	
      	// 设置用户自定义的数据
      	XML_SetUserData(parser, &vecData);
      	// 注册两个回调函数,分别处理元素的开始和结束事件
      	XML_SetElementHandler(parser, startElement, endElement);
      	// 注册一个回调函数来处理 XML 文档中元素内的文本内容
      	XML_SetCharacterDataHandler(parser, characterData);
      
      	// 开始解析数据
      	const char* xmlData = "<?xml version=\"1.0\"?><data><header hattr=\"http\"><type>Post</type><host>127.0.0.1</host></header><body battr=\"base64\"><data1>aGVsbG8=</data1><data2>ZXhwYXQ=</data2></body></data>";
      	if(!XML_Parse(parser, xmlData, strlen(xmlData), false)){
      		printf("XML_Parse failed : %s at line %lu\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
      		system("pause");
      		return -1;
      	}
      
      	printf("==================================================\n");
      
      	// 打印我们在自己定义的数据结构中保存的数据
      	for (int i = 0; i < vecData.size(); i++) {
      		// 没有文本内容时只打印字段值
      		if (vecData.at(i).strValue.empty()) {
      			std::cout << vecData.at(i).strName.c_str() << std::endl;
      		}
      		else {
      			std::cout <<"	"<< vecData.at(i).strName.c_str() << " : " << vecData.at(i).strValue.c_str() << std::endl;
      		}
      
      	}
      
      	// 释放xml解析器
      	XML_ParserFree(parser);
      
      	system("pause");
      
      	return 0;
      }
    
  • 输出结果
    在这里插入图片描述

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

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

相关文章

【项目实战】【Docker】【Git】【Linux】部署V2rayA项目

今天着手了一个全新领域的项目&#xff0c;从完全没有头绪到成功运行&#xff0c;记录一下具体的部署流程 github项目链接V2rayA 一开始拿到以后完全没有抓手&#xff0c;去阅读了一下他的帮助文档 写着能用docker运行&#xff0c;就去下载了一个Docker配置了一下 拉取代码到…

LeetCode-543. 二叉树的直径【树 深度优先搜索 二叉树】

LeetCode-543. 二叉树的直径【树 深度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;DFS解题思路二&#xff1a;另一种写法DFS解题思路三&#xff1a;0 题目描述&#xff1a; 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任…

【35分钟掌握金融风控策略3】场景概述3

目录 ​编辑 场景概述 贷前、贷中、贷后的划分及对应的风控场景 贷前风控场景简介 预授信 授信审批 定额 定价 人工审核 场景概述 贷前、贷中、贷后的划分及对应的风控场景 在金融风控全生命周期中&#xff0c;贷前主要是指授信成功及之前的阶段、贷中主要是指授信成…

ubuntu18.04图形界面卡死,鼠标键盘失灵, 通过MAC共享网络给Ubuntu解决!

ubuntu18.04图形界面卡死&#xff0c;鼠标键盘失灵&#xff0c; 通过MAC共享网络给Ubuntu解决&#xff01; 1. 尝试从卡死的图形界面切换到命令行界面2. 进入bios和grub页面3. 更改Grub中的设置&#xff0c;以进入命令行4. 在命令行页面解决图形界面卡死的问题5. Mac共享WI-FI网…

【QT+QGIS跨平台编译】056:【pdalcpp+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdalcpp介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdalcpp介绍 pdalcpp 是 PDAL(Point Data Abstraction Library)的 C++ 接口,它允许开发人员在他们的 C++ 项目中直接使用 PDAL 的功能和特性。PDAL 是一个开源的库,用于处理…

探索未来外贸电商系统的创新架构

在全球化、数字化的时代背景下&#xff0c;外贸电商行业呈现出蓬勃发展的态势。为了适应市场竞争的激烈和用户需求的多样化&#xff0c;外贸电商系统的架构设计显得尤为重要。本文将深入探讨未来外贸电商系统的创新架构&#xff0c;以期为行业发展提供新的思路和方向。 随着全…

IDEA2023.1.1中文插件

1.启动IDEA 选中Customize 2.选择All settings 3.选中Plugins,再搜索栏里输入Chinese,找到 "Chinese (Simplified) Language"插件&#xff0c;点击 Install 进行安装。 4. 安装完成后&#xff0c;重启IntelliJ IDEA&#xff0c;即可看到界面语言已经变为中文。

Java 开发者必备:JDK 版本详解与选择策略(含安装与验证)

1. JDK 版本 (Oracle Java SE 支持路线图) 数据来源&#xff1a;Oracle Java SE 支持路线图 | 甲骨文中国: https://www.oracle.com/cn/java/technologies/java-se-support-roadmap.html 版本GA DatePremier Support UntilExtended Support Until&#xff08;限 LTS&#xff09…

[C#]OpenCvSharp改变图像的对比度和亮度

目的 访问像素值mat.At<T>(y,x) 用0初始化矩阵Mat.Zeros 饱和操作SaturateCast.ToByte 亮度和对比度调整 g(x)αf(x)β 用α(>0)和β一般称作增益(gain)和偏置(bias)&#xff0c;分别控制对比度和亮度 把f(x)看成源图像像素&#xff0c;把g(x)看成输出图像像素…

如何利用待办事项清单提高工作效率?

你是否经常因为繁重的工作量而感到不堪重负&#xff1f;你是否在努力赶工期或经常忘记重要的电子邮件&#xff1f;你并不是特例。如何利用待办事项清单提高工作效率&#xff1f;这里有一个简单的方法可以帮你理清混乱并更高效地完成任务—待办事项清单。 这种类型的清单可以帮…

基于机器学习的木马检测模型的设计与实现(论文)_kaic

摘 要 科技的发展带来了人们生活的改变&#xff0c;近年来我国网民已突破十亿人口&#xff0c; 而且在后疫 情时代&#xff0c; 经历了疫情时期的一系列线上活动&#xff0c; 人们对网络的依赖比以往任何时期都要高 得多。高频次的上网行为也带来了一系列安全问题&#xff…

FPGA实现Canny算法(Verilog)

在边缘检测算法里面Sobel是比较简单的一个算法&#xff0c;但是其检测出来的边缘往往是比较粗的&#xff0c;效果不是很好&#xff0c;因为我们最理想的边缘肯定就是一个宽度为1的细线。 Canny算法在此基础上进行了改进&#xff0c;通过使用边缘的梯度信息进行非最大值抑制(NM…

面向作家的 ChatGPT 教程

原文&#xff1a;ChatGPT for authors 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 介绍我坐在电脑前几个小时&#xff0c;盯着一张空白的 Word 文档。文字就是无法流畅地表达出来。这并不是什么新鲜事&#xff1b;称之为写作障碍&#xff0c;称之为缺乏灵感&#x…

大创项目推荐 深度学习 python opencv 火焰检测识别

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

51单片机入门_江协科技_19~20_OB记录的笔记

19. 串口通讯 19.1. 串口介绍&#xff1a; •串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信。 •单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信&#xff0c;极大的…

STM32的CAN外设

我们的CAN控制器支持最高的通讯速率为1Mb/s&#xff0c;可以自动地接收和发送CAN报文&#xff0c;支持使用标准ID和扩展ID地报文&#xff0c;外设中具有3个发送邮箱&#xff0c;发送报文的优先级可以使用软件控制&#xff0c;还可以记录发送的时间&#xff0c;具有两个3级深度的…

langchain + azure chatgpt组合配置并运行

首先默认你已经有了azure的账号。 最重要的是选择gpt-35-turbo-instruct模型、api_version&#xff1a;2023-05-15&#xff0c;就这两个参数谷歌我尝试了很久才成功。 我们打开https://portal.azure.com/#home&#xff0c;点击更多服务&#xff1a; 我们点击Azure OpenAI&#…

MacBook安装使用XMind

MacBook安装使用XMind XMind简介 官方地址: https://www.xmind.cn/ XMind 是一个全功能的思维导图和头脑风暴软件,为激发灵感和创意而生。作为一款有效提升工作和生活效率的生产力工具,受到全球百千万用户的青睐。 XMind 是一款非常实用的商业思维导图软件&#xff0c;应用…

《QT实用小工具·十一》Echart图表JS交互之仪表盘

1、概述 源码放在文章末尾 该项目为Echart图表JS交互之炫酷的仪表盘&#xff0c;可以用鼠标实时改变仪表盘的读数。 下面为demo演示&#xff1a; 该项目部分代码如下&#xff1a; #include "widget.h" #include "ui_widget.h" #include "qurl.h&q…

【Java程序员面试专栏 综合面试指南】5年资深程序员面试指南

基础知识对于5年内工作经验的同学考察相对比较多。包括编程语言、计算机网络、操作系统、设计模式、分布式知识、MySQL、Redis这种。其中随着年限的增长,基础知识考察的会越来越少,例如操作系统基本上只在学生阶段考察,计算机网络对于5年经验来说也考察的相对较少。5年以上对…