<
  • 主题:
  • + -
  • 清除背景
  • 禁用背景

Json/toml/yaml/xml

字数:19739 写于:2022-07-14
最新更新:2022-07-14 阅读本文预计花费您57分钟

Json

JSON全称JavaScript Object Notation(JavaScript 对象表示法) ,是一种轻量级的、基于文本的数据交换格式

JSON 脱胎于 JavaScript 语言中对象字面量(object literal)的语法,早期开发者发现,JavaScript 里描述对象的 {key: value} 写法既直观又紧凑,非常适合用来在系统之间传递数据,于是它被广泛用于浏览器与服务器之间的数据交互,以替代笨重的 XML,并随着 Ajax 技术的兴起迅速流行。现在已成为事实上的通用数据交换格式,被广泛用于:

  • API 的请求/响应体,浏览器中通过 Content-Type: application/json 标识文件
  • 配置文件,如 package.json
  • NoSQL 数据库(如 MongoDB)的存储格式
  • 部分日志、微服务通信

特点

  • JS 对象字面量允许不加引号的键、单引号、函数、undefined、注释、尾随逗号,因此 JSON 也不允许这些值
  • YAML 是JSON的超集,可读性更强、支持注释,如果需要注释参考yaml
  • JSON 规范不限定数字精度,但很多语言(如 JavaScript)用 IEEE 754 双精度浮点表示,大整数(超过 2^53)会丢失精度。这也是为什么很多 API 用字符串传大 ID
  • JSON作为文本文件,解析开销较大,大数据量/高性能场景建议选用其他二进制格式,如:BSON等

数据类型

JSON 文本由值(value)构成,一个合法的 JSON 文件整体就是一个值(通常是一个对象或数组)。文件扩展名为.json,编码必须是UTF-8,Json 支持以下数据类型

  • 对象 Object无序的键值对集合,用 { } 包裹,键必须是字符串,如:{"name": "Tom", "age": 18}
  • 数组 Array有序的值列表,用 [] 包裹,如:["a", "b", 1, true]
  • 字符串 String用双引号包裹的文本,如:"hello"
  • 数字 Number整数或浮点数,不支持八进制/十六进制,如:42、3.14、-1e5
  • 布尔值 Boolean真或假,如:true、false
  • 空值 Null表示空
  • 不支持:注释、undefined、日期/函数/NaN/Infinity 等类型

文件格式

  • 键必须用双引号包裹(单引号非法)
  • 键值对之间用逗号分隔,最后一个元素后不能有逗号(不允许尾随逗号)
  • 值可以是上面 6 种类型的任意一种,且可以嵌套(对象里放数组、数组里放对象等)
  • 数字不能有前导零(01 非法),不能以 . 开头(.5 非法)
  • 空白(空格、换行、制表符)在结构之间可自由使用,用于美化排版
{
  "id": 1001,
  "name": "Alice",
  "isActive": true,
  "score": 95.5,
  "tags": ["admin", "developer"],
  "address": {
    "city": "Beijing",
    "zip": null
  }
}

TOML

TOML全称Tom's Obvious, Minimal Language(Tom 的显明、极简语言),由 Tom Preston-Werner(GitHub 联合创始人)于 2013 年创建,以解决 NI 太简陋、YAML 缩进易错且类型隐晦、JSON 不支持注释且对人不友好的痛点,是一种语义清晰、易于阅读的配置文件格式,被广泛用于:

  • 项目配置文件,尤其受 Rust 生态推崇(Cargo.toml)
  • Python 打包/配置文件

数据类型

TOML 文件由键值对和表(table)组成,扩展名.toml编码必须是UTF-8

  • 字符串 String基本用双引号;也支持单引号(字面量)、三引号(多行),如:"hello"、'C:\path'、"""多行"""
  • 整数 Integer支持十进制、十六进制、八进制、二进制,可用下划线分隔,如:42、0xFF、1_000
  • 浮点数 Float含小数或指数,支持 inf、nan,如:3.14、-1e5、inf
  • 布尔值 Boolean真或假,必须小写,如:true、false
  • 日期时间 Datetime支持 4 种时间形式:偏移日期时间、本地日期时间、本地日期、本地时间,如:1979-05-27T07:32:00Z
  • 数组 Array用 [] 包裹,元素可混合类型,如:[1, 2, 3]、["a", "b"]
  • 表 Table用 [table] 定义,类似分区/命名空间,如:[server]
  • 内联表 Inline Table用 {} 写在一行内,如:point = { x = 1, y = 2 }
# 全局配置
title = "示例配置"
enabled = true
timeout = 30.5
created = 2024-01-01T12:00:00Z
ports = [8000, 8001, 8002]

# 表
[server]
host = "localhost"
port = 8080

# 嵌套表
[server.limits]
max_connections = 100

# 表数组
[[users]]
name = "Alice"
admin = true

[[users]]
name = "Bob"
admin = false

文件格式

  • 值使用键值对形式key = value,而不是冒号
  • 键可用裸键(name)、引号键("my key")、点分键(a.b.c)
  • 表用[表名]声明,其下的键都属于该表;子表用[父.子],一个表不能重复定义;子表必须在父表之后定义
  • 表数组用[[数组名]],表示可重复的一组表
  • 支持注释,用#开头
  • 允许尾随逗号,缩进仅影响可读性、无语法意义

yaml

YAML Ain’t Markup Language,小猫咪都会写的yaml文件

YAML官方文档:YAML 1.2.2

关于

YAML是一种数据序列化语言,可用于存储和传输数据,常用于编写配置文件、数据文件、日志文件,以及跨语言进行数据共享等环境,大多数编程语言都可以使用 YAML 进行数据序列化。

YAML文件后缀为.yml或.yaml

基本语法

1. 通常使用键值对形式

  • 键与值通常不需要引号包裹 (这点与json不同)
  • 值可以为:数组、对象、标量值
  • 标量值是指:整数、浮点数、字符串、布尔值、Null、时间、日期

2. 通过缩进或者样式流表示数据结构

  • 数据可以通过空格缩进来表示层级关系,但不能用制表符(Tab)
  • 子结构相较父结构必须缩进至少一个空格,同一级数据须缩进相同空格数,即左端对齐
  • 也可以使用花括号{ },中括号[ ]等样式流表示层级关系
    3. 大小写敏感
    4. 使用#号注释

对象

最为常用的数据类型,又称为字典、哈希、映射,以 key:  value 键值对的形式存储数据,key: 后需要有空格!

对象写法

name: JayChou album: 十一月的萧邦

或者使用样式流:

{ name: JayChou, album: 十一月的萧邦 }
等同于json格式下的
{ "name": "JayChou", "album": "十一月的萧邦" }

缩进与嵌套

可以通过缩进至少一个空格表示层级关系,从而嵌套其他对象、数组等

student: name: aaa age: 16 hobby: - basketball - swimming
多级嵌套时,每层嵌套都应当缩进,且每个子元素左边应该缩进相等的空格数,即左端对齐
使用样式流:
student: { name: aaa, age: 16, hobby: [ basketball , swimming ] }

复杂对象格式

使用?加空格可以指定多个key值,使用:加空格指定多个value

? - key1 - key2 : - value1 - value2
表示key是一个数组,值也是一个数组,样式流:
key1,key2: ["value1","value2"]

数组

使用-开头表示数组元素

数组写法

- value1 - value2 - value3
样式流:
[value1,value2,value3]

缩进与嵌套

可以通过缩进表示层级关系,来嵌套数组、对象等

- name: 周杰伦, age: 42, songs: - 夜曲 - 米兰的小铁匠 - name: 林肯公园, age: 41, songs: - In the End - Numb
样式流
[ { name: 周杰伦, age: 42, songs: [ 夜曲 , 米兰的小铁匠 ] }, { name: 林肯公园, age: 41, songs: [ In the End , Numb ] } ]

标量

标量指最基本的,不可再分的值,这里指:整数、浮点数、字符串、布尔值、Null、时间、日期

基本使用方法:

1. 整数可以为非十进制数 num1: 120 #普通十进制数 num2: 0b10_1011 #二进制以0b开头 num3: 07100 #八进制以0开头 num4: 0xf1ab #十六进制以0x开头 2. 浮点数可以使用科学计数法 float1: 3.14 float2: 3.1415926e+5 #科学计数法314159.26 float3: -.inf #负无穷大 flota4: .nan #不是数字 3. 布尔值大小写均可 bool: true 值为:true、True、TRUE均可,但TRUe将被解析为字符串 4. Null可用~表示 data: ~ 5. 日期必须使用ISO 8601格式,即yyyy-MM-dd date: 2021-07-15 6. 时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区 time: 2021-07-15T00:42:31+08:00 在JavaScript中解析为Thu Jul 15 2021 00:42:31 GMT+0800 (中国标准时间) 7. 使用!!数据类型 值进行类型转换 date: !!str 2021-01-15

字符串处理

一般情况下,字符串不需要添加引号,但如果字符串中间包含特殊字符,尤其是冒号加空格组合时,为了避免被yaml解析为键值对,需要添加引号

str: 'said: meet you like wind'

给数值等标量添加引号将解析为字符串

str: '99.99'

多行字符串,第二行开始所有行需要缩进至少一个空格,但解析时将被解析为一行字符串,且换行符会被替换为空格

str: aaaa bbbb cccc 将被解析为 str: "aaaa bbbb cccc"

单引号与双引号

单引号和双引号均可以用于包裹字符串

  • 单引号会原样输出,将转义字符作为普通字符串输出
  • 双引号会转义字符串,如将 \n 转义为换行
如: content: '不会\n换行' 内容输出为: 不会\n换行 content: "进行\n换行" 内容输出为: 进行 换行

区块字符串处理

  1. 多行字符串可以使用|保留所有换行符,内容从第二行开始需要缩进至少一个空格,且最后一行的换行符也会保留。

    lyrics: | aaaa bbbb cccc dddd
    输出为:
    aaaa bbbb cccc dddd
  2. 可以使用>去除换行,换行符将被空格代替 (如果某一行是空行,该空行会被额外保留)

    lyrics: > aaaa bbbb cccc dddd
    输出为:
    aaaa bbbb cccc dddd
  3. 可以使用+保留文字块末尾的换行

    lyrics: |+ aaaa bbbb cccc dddd
    输出为:
    aaaa bbbb cccc dddd
  4. 可以使用-去除文字块末尾的换行

    lyrics: |- aaaa bbbb cccc dddd
    输出为:
    aaaa bbbb cccc dddd
    在yml文件中配置好字符串格式后,读取到html中时往往需要添加如white-space: pre-line;等css属性来保留换行符和空格,否则读入的数据可能会被css默认样式去除换行符,从而无法展示原生yml指定的数据格式
  5. 在样式流中,无法使用 | > + -
    如:以上数据写为样式流 { lyrics: |- aaaa bbbb cccc dddd } |- 将无法解析,抛出错误

添加HTML标签

yaml允许在值中添加HTML标签,并且标签会被正确地解析为DOM

- tag: web content: 推荐网站<a href='SHIWIVI'>SHIWIVI</a>
或
- tag: web content: |- 推荐 网站<a href='SHIWIVI'>SHIWIVI</a>
解析为:
推荐网站 SHIWIVI
使用时需要注意,插入的HTML标签是否会被其他语言解析为字符串,但yaml本身是可以输出为DOM元素的

锚点与引用

可以使用&定义一个锚点,然后使用*引用锚点

first: &anchor #定义锚点 - aaa - bbb second: *anchor #调用锚点
解析为
first: [aaaaa,bbb] second: [aaaaa,bbb]

如果将多个键值对引用到其他地方,可以使用<<合并数据

first: &data #定义锚点 name: lol age: 18 hobby: - sing - swing second: <<: *data #将数据插入此处 address: zzz
解析为:
{ "first": { "name": "lol", "age": 18, "hobby": "sing swimming" }, "second": { "name": "lol", "age": 18, "hobby": "sing swimming", "address": "zzz" } }
如果多次定义了同名的锚点(重定义),则先定义的锚点会被覆盖

文件格式

一个完整的yml文档以-‌-‌-标识文档开始,以...标识文档结束,该标识非必须,无标识的称为裸文档,文件开头可以添加%YAML来识别文件的YAML版本

%YAML 1.2 # 开头注释,注明文档作者等信息 --- ip: 45.77.77.49 port: 443 ...

一个yml文件中可能有多个文档

--- ip: 45.77.77.10 port: 80 ... --- ip: 45.77.77.11 port: 443 ...
但如果在一个yml文件中写入多个文档,在某些环境下读入可能报错,需要分割为多个文件
err: YAMLException: expected a single document in the stream, but found more
更多语法和拓展详见官方文档

xml

关于

XML 指可扩展标记语言(EXtensible Markup Language),其文件以.xml为后缀名,主要用于数据存储、数据传输、编写配置文件,语法格式与HTML相似,二者主要差异有:

  • XML标签没有被预定义,需要自行定义标签;HTML虽然支持自定义标签,但主要使用预定义标签
  • XML主要用于传输和存储数据,HTML主要用于显示数据
  • XML语法要求比HTML严格

组成

xml文档基本由以下模块组成

  • 标签(元素):文档主要部分,将被解析为XML DOM
  • 标签属性:提供关于标签的信息
  • 转义字符(实体):XML中预用字符的实体引用
  • PCDATA(Parsed Character Data):需要由解析器解析的文本
  • CDATA(Character Data):解析器不会解析的文本,内容将视为字符串

语法

xml标签定义规范

xml中的标签需要自定义,标签名需要遵循以下规范

  • 可以包含字母、数字和其他符号,包括连字符(-)、下划线(_)和点号(.)
  • 不能以数字或者标点符号开始
  • 不能以xml、 XML、Xml开始
  • 不能包含空格
  • 尽量避免使用-和.字符,该类字符在某些XML解析器中中可能会被错误解析
  • 避免使用:,该字符用于命名空间

xml基本语法格式

<?xml version="1.0" encoding="utf-8"?> <web> <author>SHIWIVI</author> <license>CC BY-NC</license> <heading><type>Document</type></heading> <body>Meet you like the wind</body> </web>
  • 首行进行xml文档声明,version属性定义版本,encoding声明编码格式
  • 必须包含一个根标签,标签名自定义
  • XML标签大小写敏感
  • 所有XML标签都必须闭合,自闭合标签禁止省略/
  • XML的属性值须加引号,即便属性值是数值,单双引号均可用
  • XML标签禁止交叉嵌套
  • XML中多个连续空格也会被保留,HTML中会被合并
  • XML 以 LF 存储换行
xml正确嵌套: <a><b>content</b></a> 严禁交叉嵌套: <a><b>content</a></b>
空标签

xml允许空标签,即标签中无内容

<element></element> 空标签可以使用自闭合,但禁止省略 / <element />

转义字符

以下字符在XML中被预定义,使用时需要转义

字符 转义 字符 转义
< &lt; ' &apos;
> &gt; " &quot;
& &amp;

CDATA字符数据

CDATA(character data)指不被xml解析器解析的文本数据,当文档中出现需要大量转义的字符时,可以直接使用CDATA进行引用,这样,这些字符将不会被xml解析器解析,

CDATA 部分由<![CDATA[开始,]]>结束

<root> <title>标题</title> <![CDATA[ if(a<b&&b<c){ .... } ]]> </root>

命名空间

由于xml中标签是自定义的,多个xml文档同时使用可能出现标签名相同导致命名冲突的情况,此时需要使用命名空间来解决标签名冲突问题

<person> <name>周杰伦</name> <song>米兰的小铁匠</song> </person>
<person> <name>东野圭吾</name> <writing>解忧杂货店</writing> </person>

添加前缀

在标签名前添加前缀,来定义不同的标签,从而对不同文档的同名标签加以区分

<j:person> <j:name>周杰伦</j:name> <j:song>米兰的小铁匠</j:song> </j:person> <h:person> <h:name>东野圭吾</h:name> <h:writing>解忧杂货店</h:writing> </h:person>

添加XML Namespace(xmlns)属性

可以在父标签中添加命名空间属性,以此定义一个命名空间,之后带有相同前缀的子标签都将同属于该命名空间
xmlns:命名前缀="namespaceURI"

<j:person xmlns:j="Jay Chou"> <j:name>周杰伦</j:name> <j:song>米兰的小铁匠</j:song> </j:person> <h:person xmlns:h="Higashino Keigo"> <h:name>东野圭吾</h:name> <h:writing>解忧杂货店</h:writing> </h:person>

使用默认的命名空间

当父元素定义默认的命名空间时,我们可以省略子元素中的前缀
xmlns="namespaceURI"

<person xmlns="Jay Chou"> <name>周杰伦</name> <song>米兰的小铁匠</song> </person> <person xmlns="Higashino Keigo"> <name>东野圭吾</name> <writing>解忧杂货店</writing> </person>
URI指统一资源标识符(Uniform Resource Identifier),用于标识因特网资源,最常用的 URI 是用来标示因特网域名地址的统一资源定位器(URL),另一个不那么常用的 URI 是统一资源命名(URN),在使用命名空间时,很多xml文档会将名称写为一个链接形式,但该链接仅作为该命名空间的字符串名称,并不是要”链接”到某个地址,不会引入任何资源

DTD验证

DTD(Document Type Definition):文档类型定义,是一套为了进行程序间的数据交换而建立的关于标记符的语法规则

xml的标签是可以自由定义的,每个人写的xml将完全不同。但在一个团队内部,我们往往希望拥有一个统一的文件格式来进行数据交换。此时,我们可以使用DTD来对xml文件进行约束,使用一个标准的 DTD 来交换数据

使用IntelliJ IDEA写xml文件可以即时校验文档内容是否满足DTD验证

标签声明

限制标签内可以写入哪些内容

  • <!ELEMENT 标签名 (child1,child2...)> 标签中可以包含的子标签
    父标签中按顺序声明其中可以包含的子标签,子标签必须以相同的顺序出现在xml文档中,并且所有子标签也要声明其中可以写入的内容,子标签可以嵌套地拥有子标签
  • <!ELEMENT 标签名 (child1,(child2|child3)...)> 标签中可以包含的子标签并可选
  • <!ELEMENT 标签名 (#PCDATA)> 标签中仅包含需要解析的字符数据(可以为空)
  • <!ELEMENT 标签名 EMPTY> 声明为空标签
  • <!ELEMENT 标签名 ANY> 标签中可包含任何内容
  • <!ELEMENT 标签名 (#PCDATA|child1|child2|...)*> 标签中可包含PCDATA数据,也可以包含指定子标签必须添加*号
限制子标签的数量
  • <!ELEMENT 标签名 (child)> 子标签必须出现一次,且只能出现一次
  • <!ELEMENT 标签名 (child+)> 子元素至少出现一次
  • <!ELEMENT 标签名 (child*)> 子元素出现零次或多次
  • <!ELEMENT 标签名 (child?)> 子元素出现零次或一次
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web [ <!ELEMENT web (author,license,(body|content))> <!ELEMENT author (#PCDATA)> <!ELEMENT license (#PCDATA)> <!ELEMENT heading (#PCDATA|type)*> <!ELEMENT type (#PCDATA)> <!ELEMENT body (#PCDATA)> <!ELEMENT content (#PCDATA)> ]> <web> <author>SHIWIVI</author> <license>lol</license> <heading><type>Document</type></heading> <body>Meet you like the wind</body> </web> 1. !DOCTYPE web - 声明文档的根标签是web 2. !ELEMENT web - 声明web元素必须包含元素:author,license标签,body和content标签可二选一 3. !ELEMENT author - 声明author标签内容为 "#PCDATA"(其余语句同理) 4. !ELEMENT heading - 声明author标签内容为 "#PCDATA"或type标签,由于标签内容受限,需添加*号表示可出现零次或多次
声明实体

<!ENTITY 实体引用名称 "实体实际代表含义"> 定义一个实体
引用实体需要添加&和;

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web [ <!ELEMENT web (author,license)> <!ELEMENT author (#PCDATA)> <!ELEMENT license (#PCDATA)> <!ENTITY sm "shiwivi.com">//实体声明 <!ENTITY license "License CC BY-NC 4.0"> ]> <web> <author>&sm;</author>//将解析为<author>shiwivi.com</author> <license>&license;</license> </web>

属性声明

限制标签可添加的属性以及属性值,语法格式为:
<!ATTLIST 标签名 属性名 属性类型 属性值>

属性类型 说明 属性类型 说明
CDATA 字符 NMTOKEN 该值是有效的XML名称
(v1|v2|..) 值只能为指定内容(枚举) NMTOKENS 该值是有效XML名称的列表
ID 值为唯一ID ENTITY 值是一个实体
IDREF 值是另一个元素的id ENTITIES 值是实体列表
IDREFS 该值是其他ID的列表 xml: 该值是预定义的xml值
属性值 说明 属性值 说明
字符串 属性默认值 #REQUIRED 属性是必需的
#IMPLIED 属性是可选的 #FIXED 值 属性值是固定的
1. 限制为test标签添加的属性只能为code,如果开发者不指定属性值,则默认值为java <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web [ <!ELEMENT web (test)> <!ELEMENT test EMPTY> <!ATTLIST test code "java">//属性声明 ]> <web> <test code="javaScript"/> </web> 2. 该标签必须添加code属性,并且不指定默认值 <!ATTLIST test code CDATA #REQUIRED> 3. 该标签不强制要求添加code属性,也不指定默认值 <!ATTLIST test code CDATA #IMPLIED> 4. 该标签强制拥有code属性并指定属性值为java,且不允许修改 <!ATTLIST test code CDATA #FIXED "java">

DTD文件引入

内部DTD

如果DTD被包含在xml文件中,需要被包装于DOCTYPE声明中
<!DOCTYPE 根标签 [标签声明内容]>

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web [ <!ELEMENT web (author,license)> <!ELEMENT author (#PCDATA)> <!ELEMENT license (#PCDATA)> ]> <web> ..... </web>
外部私有DTD

可以将DTD声明为一个外部文件,然后引用到xml文档中
<!DOCTYPE 根标签 SYSTEM "DTD的URL">

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE web SYSTEM "./check.dtd"> <web> .... </web>
<!--check.dtd文件--> <!ELEMENT web (author,license)> <!ELEMENT author (#PCDATA)> <!ELEMENT license (#PCDATA)>
外部公用DTD

也可以使用一些标准化组织或权威机构提供的DTD
<!DOCTYPE 根标签 PUBLIC "DTD公共名称" "DTD的URL">

DTD公共名称格式为:"是(否)ISO注册//注册组织//DTD所描述的文件的信息//语言"

  • 是否由国际标准化组织(ISO)注册,+表示是,-表示不是
  • 注册组织或个人
  • 该DTD所验证文件的描述性信息,经常还会添加版本号
  • 语言标识,如:ZH为中文,EN为英文
    lt;!DOCTYPE web PUBLIC "-//shiwivi//blog config//CN" "http://www.shiwivi.com/blog/check.dtd">

XPath

XPath(XML Path Language) 是一门在 XML/HTML 文档中查找和定位节点的查询语言,可以理解成"文档的路径表达式",类似文件系统的路径 /home/user/docs,它由 W3C 标准化,最初为 XSLT 设计,后来被广泛用于各种解析库(lxml、Selenium、Scrapy 等)

路径符号

符号 含义 示例
/ 从根节点开始(绝对路径) /html/body
// 从任意位置查找后代(相对路径) //div
. 当前节点 ./span
.. 父节点 ../div
@ 属性 @href
* 通配符(任意节点) //*
//div 所有 div 节点 //div/a 所有 div 下的 a 子节点 //div//a 所有 div 下的所有 a 后代 /html/body/div 绝对路径逐层定位 //a/@href 所有 a 的 href 属性值 //p/text() 所有 p 的文本内容

筛选条件

可以通过谓词[]过滤节点

//div[@id="main"] id 为 main 的 div //a[@href="/b"] href 为 /b 的 a //div[@class="box"] class 为 box 的 div //div[1] 第 1 个 div //div[last()] 最后一个 div //div[position()<3] 前 2 个 div //div[@id and @class] 同时有 id 和 class //div[@id="a" or @id="b"] id 是 a 或 b

逻辑运算

支持and、or、 取反not()、括号分组、逻辑分组

//div[not(@class)] 没有 class 属性的 div //div[not(@id="main")] id 不是 main 的 div //div[not(contains(@class,"item"))] class 不含 item 的 div //div[@id and not(@class)] 有 id 但没有 class 的 div //div[@id="a" or @id="b" or @id="c"] id 是 a/b/c 之一 //div[(@id="a" or @id="b") and @class="box"] 括号分组 //div[not(@id="a" or @id="b")] id 既不是 a 也不是 b

通配符

注意:* 只匹配元素,不匹配属性、文本、注释等,匹配全类型内容需要使用 node(),或使用 @*、text()、comment()单独匹配属性、文本、注释

//* 文档中所有元素 //div/* div 的所有直接子元素 //div//* div 的所有后代元素 //div/@* div 的所有属性 //div[@*] 带任意属性的 div //div[not(@*)] 不带任何属性的 div //div/node() div 的所有子节点(元素+文本+注释) //div/text() div 的直接文本节点 //div/comment() div 的注释节点 //*:div 任意命名空间下的 div(XPath 2.0+) //*[local-name()="div"] 本地名为 div,忽略命名空间(1.0 变通) //*[namespace-uri()="http\://www.w3.org/1999/xhtml"] 指定命名空间

属性匹配

//div[@id] 有 id 属性的 div //div[not(@id)] 没有 id 属性的 div //a[@href="/b" and @target] 同时满足两个属性条件 //div[@class="box"][@id="main"] 多个谓词连写(等价于 and) //*[@id="main"] 不限标签,任意带 id=main 的元素 //div[contains(@class,"item") and contains(@class,"active")] class 同时含两个词 //div[starts-with(@id,"box-")] id 以 box- 开头

字符串匹配

//div[contains(@class,"item")] class 含 "item" //a[starts-with(@href,"http")] href 以 http 开头 //div[text()="确定"] 文本正好是"确定" //div[contains(text(),"关键词")] 文本含"关键词" //div[ends-with(@id,"box")] ID结尾为box,XPath 2.0 支持 //a[ends-with(@href,".pdf")] href 以 .pdf 结尾(XPath 2.0) //div[normalize-space(text())="确定"] 忽略首尾空白后比较 //div[string-length(text())>5] 文本长度大于 5 //div[translate(text(),"ABC","abc")="abc"] 大小写转换后比较 //div[matches(text(),"^\d{4}$")] 正则匹配(XPath 2.0+) //div[replace(text(),"a","b")] 正则替换(XPath 2.0+) //div[substring(@id, string-length(@id)-2)="box"] 模拟 ends-with(XPath 1.0)

取文本

//a/text() 只取直接文本 //div//text() 取所有后代文本 string(//a) 取第一个 a 的完整文本 normalize-space(//a/text()) 去首尾空格 //a/@href 取属性值(返回属性节点) string(//a/@href) 取属性值(返回字符串) concat(//a/text(),"-",//b/text()) 拼接 substring(//a/text(),1,3) 截取前 3 个字符

位置函数

注意 //li[3] 是“每个父节点下第 3 个 li”,(//li)[3] 才是“全文档第 3 个 li”。

//li[1] 第一个 li //li[last()] 最后一个 li //li[position()<=3] 前 3 个 //li[last()-1] 倒数第二个 //li[position()>1] 除第一个外的所有 li //li[position() mod 2 = 1] 奇数位置的 li //li[position() mod 2 = 0] 偶数位置的 li (//li)[3] 全文档第 3 个 li(注意括号) //li[position()=last()] 等价于 last()

数值与存在判断

//div[count(a)=2] 恰好含 2 个 a 子元素的 div //div[count(*)>0] 至少有一个子元素的 div //div[count(@*)=2] 恰好有 2 个属性的 div //div[price>100] 子元素 price 数值大于 100 //div[price>=100 and price<=200] 数值区间 //ul[li] 含 li 子元素的 ul //ul[not(li)] 不含 li 子元素的 ul

节点测试与类型

//div/node() 所有子节点 //div/text() 文本节点 //div/comment() 注释节点 //div/processing-instruction() 处理指令节点 //div/element() 元素子节点(XPath 2.0+,等价 *) //div[@id]/@id 属性节点 node() 任意节点 * 任意元素 @* 任意属性

轴(Axes)

除了向下查找,XPath 还能沿"轴"定位父、兄弟等

//a/parent::div a 的父 div //a/ancestor::div a 的所有祖先 div //a/following-sibling::a a 之后的兄弟 a //a/preceding-sibling::a a 之前的兄弟 a //a/child::span a 的子 span //a/descendant::span a 的后代 span //a/following::div a 之后所有 div //a/preceding::div a 之前所有 div //a/self::a 自身(判断类型常用) //a/ancestor-or-self::div a 的祖先及自身中的 div //a/descendant-or-self::a a 的后代及自身中的 a //a/parent::* a 的父节点(任意类型) //a/ancestor::*[1] a 的最近一个祖先元素 //a/following-sibling::*[1] a 的下一个兄弟元素 //a/preceding-sibling::*[1] a 的上一个兄弟元素 //div/attribute::id 等价 //div/@id //div/child::text() 等价 //div/text()
#轴简写对照 child:: 可省略,如 //div/a 就是 child::a attribute:: 可写成 @ self:: 可写成 . parent:: 可写成 .. descendant:: 可写成 //

其他实用写法

//div[.="文本"] 当前节点的字符串值等于"文本" //div[contains(.,"关键词")] 当前节点(含后代)文本含关键词 //div[normalize-space(.)="a b"] 忽略空白后的整体文本比较 //div[@id="a"]/following::*[1] id=a 之后第一个任意元素 //div[position()=1][@class] 先取第1个,再判断 class //div[@class][position()<=2] 先判断有 class,再取前2个 //*[local-name()="div" and @id] 忽略命名空间 + 属性条件

XPath 版本差异

浏览器 document.evaluate()、Python lxml 默认都是 XPath 1.0,2.0+ 写法会直接报错;需要时用 substring + string-length 模拟 ends-with。

ends-with(@id,"box") 仅 2.0+ matches(text(),"\d+") 仅 2.0+ replace(...) / tokenize(...) 仅 2.0+ *:div 仅 2.0+ element() 仅 2.0+
上一篇:路由与路由守卫
下一篇:Grid栅格布局
z z z z z