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

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;
      }
    
  • 输出结果
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

原型模式详解

原型模式简单的理解来说,就是复制品,用一个已经做好的成品作为原型,然后通过复制它得到新的产品。就好像细胞分裂一样。用Java来说,就是用对象创建对象,而不是通过类来创建对象。 原型模式的目的是从原型实例克隆出新的实例 ,对于那些有非常复杂的初始化过程的对象或者是…

第三方短信-短信合作-106短信

第三方短信-短信合作-106短信 1、你是否也有需要发送短信的业务、但是自己没有短信服务的情况 2、是否需要大量发送短信、但是又不想通过自己的真实手机号发 3、您是否是开发者、但是没有自己的短信服务&#xff0c;需要发送短信可以使用/借用我的短信服务进行发送&#xff0c…

Docker 哲学 - docker swarm

Docker Swarm 模式下的集群管理和服务恢复机制 Docker Swarm 是 Docker 的集群管理和编排功能。在 Swarm 模式下&#xff0c;你可以将多个 Docker 主机组合成一个虚拟主机&#xff0c;称为 Swarm 集群。Swarm 集群由一个或多个管理节点&#xff08;manager nodes&#xff09;和…

Flutter-发布插件到pub上传不上问题

问题1&#xff1a; 尝试指令&#xff1a; flutter packages pub publish --serverhttps://pub.dartlang.org问题2&#xff1a; 问题1解决后&#xff0c;进入验证身份&#xff0c;点击终端显示的链接&#xff0c;跳转到google验证&#xff0c;记得这里要科*学上网&#xff0c;点…

理解pytorch的广播语义

目录 什么是广播运算 广播的条件 示例 示例1 示例2 示例3 补1 示例4 原位运算 示例5 参与广播运算的两个tensor&#xff0c;必须是从右向左对齐 总结规律 两个tensor可以做广播运算的条件&#xff1a; 两个可以互相广播的tensor运算的步骤&#xff1a; 例子&#x…

pycharm pyspark连接虚拟机的hive表 读取数据

方法&#xff1a; hive配置hiveserver2和metastore url <!-- 指定hiveserver2连接的host --> <property><name>hive.server2.thrift.bind.host</name><value>hadoop111</value> </property><!-- 指定hiveserver2连接的端口号 -…

接口的总结与面试题

接口本身不能创建对象&#xff0c;只能创建接口的实现类对象&#xff0c;接口类型的变量可以与实现类对象构成多态引用。 声明接口用interface&#xff0c;接口的成员声明有限制&#xff1a; &#xff08;1&#xff09;公共的静态常量 &#xff08;2&#xff09;公共的抽象方…

车载电子电器架构 —— 车辆数据配置开发

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

基于k8s的高性能综合web服务器搭建

目录 基于k8s的高性能综合web服务器搭建 项目描述&#xff1a; 项目规划图&#xff1a; 项目环境&#xff1a; k8s&#xff0c; docker centos7.9 nginx prometheus grafana flask ansible Jenkins等 1.规划设计整个集群的架构&#xff0c;k8s单master的集群环境&…

matlab的歧视:simulink不能使用stm32f4系列的ADC?

2023b的matlab&#xff0c;stm32f407芯片&#xff0c;运行内容Using the Analog to Digital Converter Block to Support STMicroelectronics STM32 Processor Based Boards Using the Analog to Digital Converter Block to Support STMicroelectronics STM32 Processor Base…

深入浅出 -- 系统架构之单体到分布式架构的演变

一、传统模式的技术改革 在很多年以前&#xff0c;其实没有严格意义上的前后端工程师之分&#xff0c;每个后端就是前端&#xff0c;同理&#xff0c;前端也可以是后端&#xff0c;即Ajax、jQuery技术未盛行前的年代。 起初&#xff0c;大部分前端界面很简单&#xff0c;显示的…

Linux云计算之网络基础9——园区网络架构项目

要求构建大型园区网络架构&#xff0c;方案如下&#xff1a; 园区A 园区c 公司B 要求&#xff1a; 1、A公司园区网络 一台汇聚层三层交换机&#xff0c;两台接入层二层交换机。 出口有一台路由器。 2、A园区有五台服务器。 分别为两台 WEB 服务器&#xff0c;…

VB 通过COM接口解析PSD文件

最近有PS测评的需求&#xff0c;故而想到了解析psd文件&#xff0c;目的就是为了获取文档信息和图层信息&#xff1b;获取PS的图像信息有很多方式&#xff0c;有过程性的&#xff0c;比如监听PS的各种操作事件&#xff1b;有结果性的&#xff0c;比如本文写的解析PSD文件。 0.…

【Apache Doris】周FAQ集锦:第 2 期

【Apache Doris】周FAQ集锦&#xff1a;第 2 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

ChatGPT 4.0报错 :“Hmm…something seems to have gone wrong.”

ChatGPT报错&#xff0c;GPT-3.5模型正常&#xff0c;GPT-4.0报错&#xff1a;“Hmm…something seems to have gone wrong.” 说明&#xff1a;嗯…好像出了什么问题。 原因&#xff1a; 部分用户在使用GPT-3.5模型时提问正常&#xff0c;GPT-4.0模型提问时&#xff0c;出现这…

如何使用 ChatGPT

原文&#xff1a;How To Use Chatgpt 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 总体介绍 在人工智能和在线创业不断扩张的世界中&#xff0c;ChatGPT 的出现为寻求利用 AI 推动在线成功的个人和企业开辟了令人兴奋的新途径。本书《如何使用 ChatGPT&#xff1a;…

prompt 工程案例

目录 prompt 工程是什么&#xff1f; 案例 vllm 推理加速框架 prompt 工程是什么&#xff1f; prompt&#xff1a;提示词&#xff0c;也就是我们使用网页版输入给大模型的内容就叫 prompt&#xff0c;那什么是 prompt 工程呢&#xff1f; 简单理解其实就是利用编写的 prom…

数据仓库面试总结

文章目录 1.什么是数据仓库&#xff1f;2.ETL是什么&#xff1f;3.数据仓库和数据库的区别&#xff08;OLTP和OLAP的区别&#xff09;4.数据仓库和数据集市的区别5.维度分析5.1 什么是维度&#xff1f;5.2什么是指标&#xff1f; 6.什么是数仓建模&#xff1f;7.事实表7.维度表…

基于springboot实现社区医院信息平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现社区医院信息平台系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了社区医院信息平台的开发全过程。通过分析社区医院信息平台管理的不足&#xff0c;创建了一个计算机管理社区医院信…

gpt国内怎么用?最新版本来了

claude 3 opus面世后&#xff0c;这几天已经有许多应用&#xff0c;而其精确以及从不偷懒&#xff08;截止到2024年3月11日还没有偷懒&#xff09;的个性&#xff0c;也使得我们可以用它来首次完成各种需要多轮对话的尝试。 今天我们想要进行的一项尝试就是—— 如何从一个不知…