新增项目详情与开发文档网页

This commit is contained in:
HapeLee
2026-05-27 01:00:58 +08:00
parent 65d5c84b56
commit b7826b54a7
35 changed files with 3499 additions and 15 deletions
+129
View File
@@ -0,0 +1,129 @@
# 认证与登录 (Authentication)
[[toc]]
Legado 支持通过 CookieJar、登录 UI 表单和登录 URL 脚本三种方式实现网站认证。
## 1. CookieJar
启用后自动保存每次响应头中的 `Set-Cookie` 值,适用于需要 session 的网站(如验证码图片)。
在源编辑器中勾选「启用 CookieJar」即可,无需额外配置。
## 2. 登录 UI (Login UI)
通过 JSON 数组定义登录表单,替代内置 WebView 登录方式。
### 字段定义
| 字段 | 类型 | 必须 | 说明 |
|:---------|:---------|:---|:-----------------------|
| `name` | `String` | 是 | 字段名称,显示为输入框标签 |
| `type` | `String` | 是 | 字段类型 |
| `action` | `String` | 否 | 仅 `button` 类型:点击时执行的动作 |
| `style` | `Object` | 否 | Flexbox 布局配置 |
### type 取值
| 值 | 说明 |
|:-----------|:---------------------------------------|
| `text` | 文本输入框 |
| `password` | 密码输入框 |
| `button` | 可点击按钮,`action` 为 URL 时打开浏览器,为函数名时调用 JS |
### JSON 示例
```json
[
{ "name": "telephone", "type": "text" },
{ "name": "password", "type": "password" },
{
"name": "注册",
"type": "button",
"action": "http://www.example.com/register"
},
{
"name": "获取验证码",
"type": "button",
"action": "getVerificationCode()",
"style": {
"layout_flexGrow": 0,
"layout_flexShrink": 1,
"layout_alignSelf": "auto",
"layout_flexBasisPercent": -1,
"layout_wrapBefore": false
}
}
]
```
### style 布局属性
| 属性 | 类型 | 说明 |
|:--------------------------|:----------|:---------------|
| `layout_flexGrow` | `Int` | 弹性增长比例 |
| `layout_flexShrink` | `Int` | 弹性收缩比例 |
| `layout_alignSelf` | `String` | 交叉轴对齐方式 |
| `layout_flexBasisPercent` | `Int` | 基础尺寸百分比,-1 为自动 |
| `layout_wrapBefore` | `Boolean` | 是否强制换行 |
::: tip 版本变更
从版本 20221113 起,按钮支持调用「登录 URL」规则中的函数,必须实现 `login` 函数。
:::
## 3. 登录 URL (Login URL)
可填写登录链接或实现登录逻辑的 JavaScript。配合登录 UI 使用时,需要实现 `login` 函数。
### JS 示例
```js
function login() {
java.log("模拟登录请求");
java.log(source.getLoginInfoMap());
}
function getVerificationCode() {
java.log("登录UI按钮:获取到手机号码" + result.get("telephone"))
}
```
### 获取登录信息
在登录按钮函数和 `login` 函数中,可通过以下方式获取用户输入:
```js
// 登录按钮函数中
result.get("telephone")
// login 函数中
source.getLoginInfo()
source.getLoginInfoMap().get("telephone")
```
### source 登录相关方法
| 方法 | 返回值 | 说明 |
|:-------------------------------|:----------|:----------|
| `login()` | — | 执行登录 |
| `getHeaderMap(hasLoginHeader)` | `Map` | 获取请求头 |
| `getLoginHeader()` | `String?` | 获取登录头字符串 |
| `getLoginHeaderMap()` | `Map?` | 获取登录头 Map |
| `putLoginHeader(header)` | — | 保存登录头 |
| `removeLoginHeader()` | — | 清除登录头 |
| `setVariable(variable)` | — | 设置源变量 |
| `getVariable()` | `String?` | 获取源变量 |
### AnalyzeUrl 函数
以下函数仅在「登录检查 JS」规则中有效:
| 方法 | 返回值 | 说明 |
|:---------------------------------------------------|:--------------|:--------------------|
| `initUrl()` | — | 重新解析 URL |
| `getHeaderMap().putAll(source.getHeaderMap(true))` | — | 重新设置登录头 |
| `getStrResponse(jsStr, sourceRegex)` | `StrResponse` | 返回文本类型的访问结果 |
| `getResponse()` | `Response` | 返回 Response 类型的访问结果 |
## 4. 登录检查 JS (Login Check)
在源的「登录检查 JS」字段中填写 JavaScript,用于判断当前登录状态。返回 `true` 表示已登录,返回其他值会触发重新登录流程。
+62
View File
@@ -0,0 +1,62 @@
# 源调试
编写源后,可以通过 Legado 内置的调试功能逐项验证各规则是否正确。调试入口位于源编辑页面底部的「调试」按钮。
[[toc]]
## 调试搜索
输入搜索关键字,验证搜索规则能否正确返回书籍列表。
**输入示例:**
```
系统
```
## 调试发现
输入发现页 URL,支持 `{{page}}` 分页变量。格式为 `标题::URL`
**输入示例:**
```
排行榜::https://www.example.com/rank?page={{page}}
```
## 调试详情页
直接输入书籍详情页的 URL,验证详情页规则能否正确提取书名、作者、简介等信息。
**输入示例:**
```
https://www.example.com/book/12345
```
## 调试目录页
输入目录页 URL,前缀 `++` 表示使用正则匹配方式。
**输入示例:**
```
++https://www.example.com/read/12345
```
## 调试正文页
输入正文页 URL,前缀 `--` 表示使用正则匹配方式。
**输入示例:**
```
--https://www.example.com/chapter/12345/67890
```
::: tip 调试技巧
- 每个调试项独立运行,可以逐个排查问题
- 调试结果会直接显示提取到的内容,方便对比预期
- 如果规则使用了 js,调试时可以看到 `java.log()` 输出的日志
:::
+22
View File
@@ -0,0 +1,22 @@
# 字典规则
字典规则用于正文阅读时的「选中文字 → 字典/翻译」功能。用户选中文字后,Legado 会调用字典规则查询结果并显示在弹窗中。
[[toc]]
## 字段说明
| 字段 | 说明 |
|------------|-----------------------|
| `urlRule` | 请求 URL 规则,语法同源 URL 规则 |
| `showRule` | 从响应结果中提取显示内容的规则 |
## 配置示例
`urlRule` 中可以使用 `{{key}}` 引用用户选中的文字:
```
https://dict.example.com/s?wd={{key}}&ptype=zici
```
`showRule` 使用标准的源规则语法提取页面中的释义内容。
+77
View File
@@ -0,0 +1,77 @@
# 发现 URL (Discovery URL) 配置规范
[[toc]]
源的「发现 URL」字段支持通过 JSON 数组定义多个发现页入口,每个入口包含标题、URL
和可选的布局样式。这使得一个源可以提供多个分类或榜单的快捷入口。
## 1. 字段位置
在源编辑器的 **发现** 选项卡中:
```
发现 → 发现 URL
```
## 2. 数据结构
发现 URL 是一个包含多个入口对象的 JSON 数组。
### 入口字段
| 字段 | 类型 | 必须 | 说明 |
|:--------|:---------|:---|:--------------------------|
| `title` | `String` | 是 | 入口标题,显示在发现页列表中 |
| `url` | `String` | 是 | 目标 URL,支持 `{{page}}` 分页变量 |
| `style` | `Object` | 否 | Flexbox 布局配置 |
### style 布局属性
| 属性 | 类型 | 说明 |
|:--------------------------|:----------|:---------------|
| `layout_flexGrow` | `Int` | 弹性增长比例 |
| `layout_flexShrink` | `Int` | 弹性收缩比例 |
| `layout_alignSelf` | `String` | 交叉轴对齐方式 |
| `layout_flexBasisPercent` | `Int` | 基础尺寸百分比,-1 为自动 |
| `layout_wrapBefore` | `Boolean` | 是否强制换行 |
## 3. 完整 JSON 示例
```json
[
{
"title": "热门榜",
"url": "https://example.com/rank/hot?page={{page}}"
},
{
"title": "新书榜",
"url": "https://example.com/rank/new?page={{page}}"
},
{
"title": "完本榜",
"url": "https://example.com/rank/finish?page={{page}}"
}
]
```
## 4. 两种格式
发现 URL 支持两种填写格式:
| 格式 | 说明 | 适用场景 |
|:--------|:-----------------------------------------|:----------|
| 字符串 | 直接填写 URL,如 `https://example.com/explore` | 单个发现页 |
| JSON 数组 | 定义多个入口 | 多个分类/榜单入口 |
## 5. 分页变量
URL 中可使用 `{{page}}` 变量实现分页,Legado 会自动递增页码:
```
https://example.com/rank?page={{page}}
```
## 6. 与首页模块的关系
发现 URL 定义的入口可被 [首页模块](../spec/homepage-modules) 的 `kindTitle` 字段引用。当首页模块的
`kindTitle` 与某个发现入口的 `title` 完全匹配时,模块会自动使用该入口的 URL 和规则。
+201
View File
@@ -0,0 +1,201 @@
# 源示例 (Source Examples)
[[toc]]
本文档提供三种典型源的完整 JSON 示例,分别演示 CSS+正则、XPath+正则、JSONPath 的用法。
## 1. CSS + 正则
使用 JSOUP CSS 选择器和正则表达式的源示例。
```json
{
"bookSourceComment": "",
"bookSourceGroup": "CSS; 正则",
"bookSourceName": "示例源",
"bookSourceType": 0,
"bookSourceUrl": "https://www.example.com",
"bookUrlPattern": "",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"exploreUrl": "",
"lastUpdateTime": 0,
"loginUrl": "",
"ruleBookInfo": {
"author": "##:author\"[^\"]+\"([^\"]*)##$1###",
"coverUrl": "##og:image\"[^\"]+\"([^\"]*)##$1###",
"intro": "##:description\"[^\"]+\"([\\w\\W]*?)\"/##$1###",
"kind": "##:category\"[^\"]+\"([^\"]*)##$1###",
"lastChapter": "##_chapter_name\"[^\"]+\"([^\"]*)##$1###",
"name": "##:book_name\"[^\"]+\"([^\"]*)##$1###",
"tocUrl": ""
},
"ruleContent": {
"content": "@css:.chapter-content p@textNodes##广告文字|本站声明.*|<!\\[CDATA\\[|\\]\\]>",
"nextContentUrl": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "@css:p:eq(2)>a@text",
"bookList": "@css:li.clearfix",
"bookUrl": "@css:.name>a@href",
"coverUrl": "@css:img@src",
"intro": "@css:.note.clearfix p@text",
"kind": "@css:.note_text,p:eq(4)@text",
"lastChapter": "@css:p:eq(3)@text",
"name": "@css:.name@text"
},
"ruleToc": {
"chapterList": "-:<li><a[^\"]+\"([^\"]*)\">([^<]*)",
"chapterName": "$2",
"chapterUrl": "$1",
"nextTocUrl": ""
},
"searchUrl": "/search?q={{key}}&page={{page}}",
"weight": 0
}
```
**要点:**
- 详情页规则使用 OnlyOne 正则(`##...###`)从 meta 标签提取信息
- 正文规则使用 CSS 选择器取文本节点,后接净化正则移除广告
- 目录规则使用 AllInOne 正则(`:` 开头),`-` 前缀使列表倒序
## 2. XPath + 正则
使用 XPath 和正则表达式的源示例。
```json
{
"bookSourceComment": "",
"bookSourceGroup": "XPath; 正则",
"bookSourceName": "示例移动源",
"bookSourceType": 0,
"bookSourceUrl": "https://m.example.com",
"bookUrlPattern": "",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"exploreUrl": "",
"lastUpdateTime": 0,
"loginUrl": "",
"ruleBookInfo": {
"author": "//*[@property=\"og:novel:author\"]/@content",
"coverUrl": "//*[@property=\"og:image\"]/@content",
"intro": "//*[@property=\"og:description\"]/@content",
"kind": "//*[@property=\"og:novel:category\"]/@content",
"lastChapter": "//*[@id=\"latest-chapter\"]//li[1]/a/text()",
"name": "//*[@property=\"og:novel:book_name\"]/@content",
"tocUrl": "//a[text()=\"阅读\"]/@href"
},
"ruleContent": {
"content": "//*[@id=\"content\"]",
"nextContentUrl": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "//dd[2]/text()",
"bookList": "//*[@id=\"search-result\"]/dl",
"bookUrl": "//dt/a/@href",
"coverUrl": "//img/@src",
"kind": "//dd[2]/span/text()",
"lastChapter": "",
"name": "//h3/a/text()"
},
"ruleToc": {
"chapterList": ":href=\"(/read[^\"]*html)\">([^<]*)",
"chapterName": "$2",
"chapterUrl": "$1",
"nextTocUrl": "//*[@id=\"chapter-list\"]/*[position()>1]/@value"
},
"searchUrl": "/search,{\n \"method\": \"POST\",\n \"body\": \"q={{key}}\"\n}",
"weight": 0
}
```
**要点:**
- 详情页使用 XPath 从 `og:novel` meta 标签提取信息
- 目录使用 AllInOne 正则提取链接和标题
- 目录下一页使用 XPath 的 `position()` 谓语条件
- 搜索使用 POST 方法
## 3. JSONPath
使用 JSON API 的源示例。
```json
{
"bookSourceComment": "",
"bookSourceGroup": "JSON",
"bookSourceName": "示例 API 源",
"bookSourceType": 0,
"bookSourceUrl": "http://api.example.com",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"header": "{\n \"User-Agent\": \"Mozilla/5.0\"\n}",
"lastUpdateTime": 0,
"ruleBookInfo": {},
"ruleContent": {
"content": "$.chapter.body"
},
"ruleExplore": {},
"ruleSearch": {
"author": "$.author",
"bookList": "$..books[*]",
"bookUrl": "/book/detail?id={$._id}",
"coverUrl": "$.cover",
"intro": "$.shortIntro",
"kind": "$.minorCate",
"lastChapter": "$.lastChapter",
"name": "$.title"
},
"ruleToc": {
"chapterList": "$.chapterInfo.chapters.[*]",
"chapterName": "$.title",
"chapterUrl": "$.link"
},
"searchUrl": "/book/search?query={{key}}&start={{(page-1)*20}}&limit=20",
"weight": 0
}
```
**要点:**
- 详情页为空(`ruleBookInfo: {}`),直接从搜索结果获取信息
- 目录 URL 使用 JSONPath 表达式 `{$._id}` 拼接
- 书列表使用递归查找 `$..books[*]`
- 搜索 URL 使用 `{{(page-1)*20}}` 计算偏移量
## 4. 调试技巧
### JS 错误处理
使用 try-catch 包裹 JS 逻辑,方便调试:
```javascript
(function(result){
try{
// 处理 result
return result;
}
catch(e){
return "" + e; // 返回错误信息
}
})(result);
```
### 调试入口
| 调试项 | 输入示例 |
|:----|:--------------------------------------------------|
| 搜索 | `系统` |
| 发现 | `排行榜::https://www.example.com/rank?page={{page}}` |
| 详情页 | `https://www.example.com/book/12345` |
| 目录页 | `++https://www.example.com/read/12345` |
| 正文页 | `--https://www.example.com/chapter/12345/67890` |
详细调试方法参见 [源调试](./debug)。
+61
View File
@@ -0,0 +1,61 @@
# 开发文档
欢迎来到 Legado 开发文档。本文档为源开发者和贡献者提供参考。
::: tip 快速上手
如果你是第一次接触 Legado 源开发,建议按以下顺序阅读:
1. [规则语法详解](./syntax) — 了解所有支持的规则语法
2. [URL 参数详解](./url-options) — 掌握请求参数配置
3. [源字段速查](./source-fields) — 查看各部分可用字段
4. [源示例](./examples) — 参考完整源 JSON
:::
## 入门
从零开始学习源开发。
| 文档 | 说明 |
|:--------------------------|:-------------------------------------------------------------|
| [规则语法详解](./syntax) | JSOUP Default/CSS、JSONPath、XPath、正则 AllInOne/OnlyOne/净化、连接符号 |
| [URL 参数详解](./url-options) | GET/POST 请求、WebView 模式、模板变量、完整 UrlOption 字段 |
| [源字段速查](./source-fields) | 搜索、发现、详情页、目录、正文各部分的所有字段说明 |
| [源示例](./examples) | CSS+正则、XPath+正则、JSONPath 三种完整源 JSON |
## 参考
核心 API 和语法参考。
| 文档 | 说明 |
|:-----------------------|:--------------------------------------|
| [源规则帮助](./rule) | 规则标志、jsLib、并发率、源类型、字体解析、图片解密 |
| [JS 变量和函数](./js) | Rhino 引擎内置变量、加解密、网络请求、Java 互操作等完整 API |
| [XPath 路径表达式](./xpath) | 13 种轴、谓语条件、通配符、内置函数 |
| [正则表达式](./regex) | 元字符、字符集、断言、常用正则示例 |
## 配置规范
源 JSON 字段的详细规范。
| 文档 | 说明 |
|:-----------------------------------|:----------------------------------------|
| [请求头配置](./request-headers) | 代理设置、自定义 Headers、URL 动态参数、重定向拦截 |
| [认证与登录](./authentication) | CookieJar、登录 UI 表单、登录 URL 脚本、登录检查 |
| [发现 URL 配置](./discovery-url) | 发现页入口 JSON 格式、分页变量、与首页模块的关系 |
| [首页模块配置](../spec/homepage-modules) | `homepageModules` 字段规范:列表、轮播、排行榜等模块类型 |
| [关联书籍配置](../spec/related-books) | `ruleBookInfo.relatedBooks` 字段规范:相关书籍推荐 |
## 扩展功能
| 文档 | 说明 |
|:--------------------------------|:-----------------------|
| [源调试](./debug) | 搜索、发现、详情页、目录页、正文页的调试方法 |
| [字典规则](./dict-rule) | 正文选中菜单的字典/翻译规则配置 |
| [在线朗读规则](./tts-rule) | 自定义在线 TTS 接口(支持语速控制) |
| [TXT 目录正则](./txt-toc) | 自定义 TXT 书籍的章节识别规则 |
| [MIME 类型参考](../spec/mime-types) | 支持的文件扩展名和 MIME 类型对照表 |
## 外部资源
- [Legado GitHub](https://github.com/HapeLee/legado-with-MD3) — 项目源码和 Issue 跟踪
- [legado-with-MD3 Wiki](https://github.com/HapeLee/legado-with-MD3/wiki) — 社区维护的文档
+349
View File
@@ -0,0 +1,349 @@
# JS 变量和函数
[[toc]]
Legado 使用 [Rhino v1.8.0](https://github.com/mozilla/rhino) 作为 JavaScript 引擎,支持在源规则中调用
Java 类和方法。本文档列出所有可用的内置变量、对象属性和扩展函数。
## 1. Rhino 引擎概览
| 构造函数 | 函数 | 调用类 | 说明 |
|:---------------|:------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------|
| `JavaImporter` | `importClass` `importPackage` | [ImporterTopLevel](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java) | 导入 Java 类到 JavaScript |
| — | `getClass` | [NativeJavaTopPackage](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java) | 默认导入 JavaScript 中的 Java 类 |
| `JavaAdapter` | — | [JavaAdapter](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/JavaAdapter.java) | 继承 Java 类 |
- [Rhino 运行时](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java)
懒加载导入的 Java 类和方法
- [ECMAScript 兼容性表格](https://mozilla.github.io/rhino/compat/engines.html)
::: warning 注意事项
- `java` 变量已被 Legado 修改,调用 `java.*` 下的包请使用 `Packages.java.*`
- 在源规则中使用 `@js``<js>`、<code v-pre>{{}}</code> 可调用 Legado 内置的类和方法
- 为安全起见,部分 Java
类调用被屏蔽,见 [RhinoClassShutter](https://github.com/HapeLee/legado-with-MD3/blob/master/modules/rhino/src/main/java/com/script/rhino/RhinoClassShutter.kt)
- 不同源规则中支持调用的 Java 类和方法可能不同
- `const` 声明的变量不支持块级作用域,循环中使用会出现值不变的问题,请改用 `var`
:::
## 2. 内置变量 (Built-in Variables)
以下变量在源规则的 JS 执行环境中自动可用。
| 变量 | 类型 | 作用域 | 说明 |
|:-----------------|:---------------|:----|:-------------------------------------------------------------------------------------------------------------------------------|
| `java` | `Object` | 全局 | 扩展工具对象,提供网络请求、加解密、文件操作等方法 |
| `baseUrl` | `String` | 全局 | 当前请求的 URL |
| `result` | `Any` | 全局 | 上一步规则的执行结果 |
| `book` | `Book` | 全局 | [书籍对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt) |
| `rssArticle` | `RssArticle` | 全局 | [RSS 文章对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/data/entities/RssArticle.kt) |
| `chapter` | `BookChapter` | 全局 | [章节对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/data/entities/BookChapter.kt) |
| `source` | `BaseSource` | 全局 | [源对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/data/entities/BaseSource.kt) |
| `cookie` | `CookieStore` | 全局 | [Cookie 操作对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/help/http/CookieStore.kt) |
| `cache` | `CacheManager` | 全局 | [缓存操作对象](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/help/CacheManager.kt) |
| `title` | `String` | 全局 | 当前章节标题 |
| `src` | `String` | 全局 | 请求返回的源码 |
| `nextChapterUrl` | `String` | 全局 | 下一章节 URL |
## 3. java 对象方法
`java` 对象是 Legado 暴露给 JS 环境的核心工具对象,聚合了多个扩展类的方法。
### 3.1 RSS 扩展 ([RssJsExtensions](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt))
::: warning 作用域限制
只能在订阅源 `shouldOverrideUrlLoading` 规则中使用。URL 跳转拦截规则不能执行耗时操作。
:::
```js
java.searchBook(bookName: String) // 调用 Legado 搜索
java.addBook(bookUrl: String) // 添加书架
```
### 3.2 URL 解析 ([AnalyzeUrl](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt))
通过 `java.` 调用,仅在 `登录检查 JS` 规则中有效。
| 方法 | 返回值 | 说明 |
|:---------------------------------------------------|:--------------|:--------------------|
| `initUrl()` | — | 重新解析 URL |
| `getHeaderMap().putAll(source.getHeaderMap(true))` | — | 重新设置登录头 |
| `getStrResponse(jsStr, sourceRegex)` | `StrResponse` | 返回文本类型的访问结果 |
| `getResponse()` | `Response` | 返回 Response 类型的访问结果 |
### 3.3 规则解析 ([AnalyzeRule](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt))
```js
// 获取文本/文本列表
java.getString(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false)
java.getStringList(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false)
// 设置解析内容
java.setContent(content: Any?, baseUrl: String? = null)
// 获取 Element/Element 列表
java.getElement(ruleStr: String)
java.getElements(ruleStr: String)
// 重新搜索书籍/重新获取目录 url(只能在刷新目录之前使用)
java.reGetBook()
java.refreshTocUrl()
// 变量存取
java.get(key)
java.put(key, value)
```
### 3.4 扩展工具 ([JsExtensions](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt))
#### 链接解析
| 方法 | 返回值 | 说明 |
|:---------------------------|:---------|:----------------------|
| `java.toURL(url)` | `JsURL` | 将字符串解析为 URL 对象 |
| `java.toURL(url, baseUrl)` | `JsURL` | 基于 baseUrl 解析相对 URL |
| `java.getWebViewUA()` | `String` | 获取 WebView User-Agent |
#### 网络请求
| 方法 | 返回值 | 说明 |
|:--------------------------------------------------------------|:----------------------|:------------------------------------------------------------------|
| `java.ajax(urlStr)` | `String` | GET 请求,返回响应体 |
| `java.ajaxAll(urlList)` | `Array<StrResponse>` | 批量请求 |
| `java.connect(urlStr)` | `StrResponse` | 返回 `body()` `code()` `message()` `headers()` `raw()` `toString()` |
| `java.post(url, body, headerMap)` | `Connection.Response` | POST 请求 |
| `java.get(url, headerMap)` | `Connection.Response` | GET 请求 |
| `java.head(url, headerMap)` | `Connection.Response` | HEAD 请求 |
| `java.webView(html, url, js)` | `String?` | 使用 WebView 访问网络 |
| `java.webViewGetOverrideUrl(html, url, js, overrideUrlRegex)` | `String?` | 使用 WebView 获取跳转 URL |
| `java.webViewGetSource(html, url, js, sourceRegex)` | `String?` | 使用 WebView 获取资源 URL |
| `java.startBrowser(url, title)` | — | 使用内置浏览器打开链接 |
| `java.startBrowserAwait(url, title, refetchAfterSuccess)` | `StrResponse` | 使用内置浏览器打开链接,等待结果 |
#### 调试与提示
| 方法 | 说明 |
|:-------------------------------------|:---------|
| `java.log(msg)` | 输出日志 |
| `java.logType(var)` | 输出变量类型 |
| `java.getVerificationCode(imageUrl)` | 弹出验证码输入框 |
| `java.longToast(msg)` | 长时间提示 |
| `java.toast(msg)` | 短时间提示 |
#### 脚本导入
| 方法 | 说明 |
|:----------------------------------|:-------------------------------------------|
| `java.importScript(url)` | 从网络加载脚本 |
| `java.importScript(relativePath)` | 从相对路径加载(支持 `android/data/{package}/cache` |
| `java.importScript(absolutePath)` | 从绝对路径加载 |
#### 文件缓存
```js
java.cacheFile(url) // 缓存网络文件
java.cacheFile(url, saveTime) // 缓存并指定保存时间
eval(String(java.cacheFile(url))) // 缓存并执行
cache.delete(java.md5Encode16(url)) // 使缓存失效
```
#### 编码与转换
| 分类 | 方法 | 说明 |
|:----------|:-------------------------------------------|:---------------------|
| URI | `java.encodeURI(str, enc?)` | URI 编码,默认 UTF-8 |
| Base64 | `java.base64Decode(str, charset?)` | Base64 解码为字符串 |
| Base64 | `java.base64DecodeToByteArray(str, flags)` | Base64 解码为 ByteArray |
| Base64 | `java.base64Encode(str, flags)` | Base64 编码 |
| ByteArray | `java.strToBytes(str, charset?)` | 字符串转 ByteArray |
| ByteArray | `java.bytesToStr(bytes, charset?)` | ByteArray 转字符串 |
| Hex | `java.hexDecodeToByteArray(hex)` | Hex 解码为 ByteArray |
| Hex | `java.hexDecodeToString(hex)` | Hex 解码为字符串 |
| Hex | `java.hexEncodeToString(utf8)` | 字符串转 Hex |
#### 标识与格式化
| 方法 | 返回值 | 说明 |
|:---------------------------------------|:----------|:--------------|
| `java.randomUUID()` | `String` | 生成 UUID |
| `java.androidId()` | `String` | 获取 Android ID |
| `java.t2s(text)` | `String` | 繁体转简体 |
| `java.s2t(text)` | `String` | 简体转繁体 |
| `java.timeFormatUTC(time, format, sh)` | `String?` | UTC 时间格式化 |
| `java.timeFormat(time)` | `String` | 时间格式化 |
| `java.htmlFormat(str)` | `String` | HTML 格式化 |
#### 文件操作
::: tip 路径限制
所有文件读写删除操作均使用相对路径,只能操作阅读缓存目录 `android/data/{package}/cache/` 内的文件。
:::
| 方法 | 返回值 | 说明 |
|:----------------------------|:---------|:------------|
| `downloadFile(url)` | `String` | 文件下载,返回文件路径 |
| `unArchiveFile(zipPath)` | `String` | 文件解压,返回解压路径 |
| `unzipFile(zipPath)` | `String` | ZIP 解压 |
| `unrarFile(zipPath)` | `String` | RAR 解压 |
| `un7zFile(zipPath)` | `String` | 7Z 解压 |
| `getTxtInFolder(unzipPath)` | `String` | 读取文件夹内所有文件 |
| `readTxtFile(path)` | `String` | 读取文本文件 |
| `deleteFile(path)` | — | 删除文件 |
#### 外部链接跳转
| 方法 | 说明 |
|:------------------------------|:-------------------------|
| `java.openUrl(url)` | 跳转外部链接(HTTP 或 scheme |
| `java.openUrl(url, mimeType)` | 指定 MIME 类型跳转,如 `video/*` |
### 3.5 加解密 ([JsEncodeUtils](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt))
提供在 JavaScript 环境中快捷调用 crypto
算法的函数,由 [hutool-crypto](https://www.hutool.cn/docs/#/crypto/概述) 实现(当前版本 5.8.22)。
::: warning 输入类型
如果输入参数不是 Utf8String,可先调用 `java.hexDecodeToByteArray``java.base64DecodeToByteArray`
转成 ByteArray。
:::
#### 对称加密
```js
// 创建 Cipherkey/iv 支持 ByteArray | Utf8String
java.createSymmetricCrypto(transformation, key, iv)
// data 支持 ByteArray | Base64String | HexString | InputStream
cipher.decrypt(data) // 解密为 ByteArray
cipher.decryptStr(data) // 解密为字符串
cipher.encrypt(data) // 加密为 ByteArray
cipher.encryptBase64(data) // 加密为 Base64
cipher.encryptHex(data) // 加密为 Hex
```
#### 非对称加密
```js
java.createAsymmetricCrypto(transformation)
.setPublicKey(key)
.setPrivateKey(key)
cipher.decrypt(data, usePublicKey: Boolean? = true)
cipher.decryptStr(data, usePublicKey: Boolean? = true)
cipher.encrypt(data, usePublicKey: Boolean? = true)
cipher.encryptBase64(data, usePublicKey: Boolean? = true)
cipher.encryptHex(data, usePublicKey: Boolean? = true)
```
#### 签名
```js
java.createSign(algorithm)
.setPublicKey(key)
.setPrivateKey(key)
sign.sign(data)
sign.signHex(data)
```
#### 摘要与 HMAC
| 方法 | 返回值 | 说明 |
|:----------------------------------------|:----------|:-------------|
| `java.digestHex(data, algorithm)` | `String?` | 摘要(Hex |
| `java.digestBase64Str(data, algorithm)` | `String?` | 摘要(Base64 |
| `java.md5Encode(str)` | `String` | MD532 位) |
| `java.md5Encode16(str)` | `String` | MD516 位) |
| `java.HMacHex(data, algorithm, key)` | `String` | HMACHex |
| `java.HMacBase64(data, algorithm, key)` | `String` | HMACBase64 |
## 4. book 对象
在 JS 中或 <code v-pre>{{}}</code> 中使用 `book.属性` 的方式获取。
| 属性 | 类型 | 说明 |
|:---------------------|:----------|:--------------------|
| `bookUrl` | `String` | 详情页 URL(本地源为完整文件路径) |
| `tocUrl` | `String` | 目录页 URL |
| `origin` | `String` | 源 URL |
| `originName` | `String` | 源名称或本地书籍文件名 |
| `name` | `String` | 书籍名称 |
| `author` | `String` | 作者名称 |
| `kind` | `String` | 分类信息 |
| `customTag` | `String` | 分类信息(用户修改) |
| `coverUrl` | `String` | 封面 URL |
| `customCoverUrl` | `String` | 封面 URL(用户修改) |
| `intro` | `String` | 简介内容 |
| `customIntro` | `String` | 简介内容(用户修改) |
| `charset` | `String` | 自定义字符集名称(仅本地书籍) |
| `type` | `Int` | 0: text, 1: audio |
| `group` | `Int` | 自定义分组索引号 |
| `latestChapterTitle` | `String` | 最新章节标题 |
| `latestChapterTime` | `Long` | 最新章节更新时间 |
| `lastCheckTime` | `Long` | 最近一次更新书籍信息的时间 |
| `lastCheckCount` | `Int` | 最近一次发现新章节数量 |
| `totalChapterNum` | `Int` | 书籍目录总数 |
| `durChapterTitle` | `String` | 当前章节名称 |
| `durChapterIndex` | `Int` | 当前章节索引 |
| `durChapterPos` | `Long` | 当前阅读进度 |
| `durChapterTime` | `Long` | 最近一次阅读时间 |
| `canUpdate` | `Boolean` | 刷新书架时是否更新 |
| `order` | `Int` | 手动排序 |
| `originOrder` | `Int` | 源排序 |
| `variable` | `String?` | 自定义书籍变量 |
## 5. chapter 对象
| 属性 | 类型 | 说明 |
|:--------------|:----------|:-----------|
| `url` | `String` | 章节地址 |
| `title` | `String` | 章节标题 |
| `baseUrl` | `String` | 用于拼接相对 URL |
| `bookUrl` | `String` | 书籍地址 |
| `index` | `Int` | 章节序号 |
| `resourceUrl` | `String` | 音频真实 URL |
| `tag` | `String` | 标签 |
| `start` | `Long` | 章节起始位置 |
| `end` | `Long` | 章节终止位置 |
| `variable` | `String?` | 变量 |
## 6. source 对象
| 方法 | 返回值 | 说明 |
|:--------------------------------------|:----------|:----------|
| `source.getKey()` | `String` | 获取源 URL |
| `source.setVariable(variable)` | — | 设置源变量 |
| `source.getVariable()` | `String?` | 获取源变量 |
| `source.getLoginHeader()` | `String?` | 获取登录头 |
| `source.getLoginHeaderMap().get(key)` | `String?` | 获取登录头某一键值 |
| `source.putLoginHeader(header)` | — | 保存登录头 |
| `source.removeLoginHeader()` | — | 清除登录头 |
| `source.getLoginInfo()` | `String?` | 获取登录信息 |
| `source.getLoginInfoMap().get(key)` | `String?` | 获取登录信息键值 |
| `source.removeLoginInfo()` | — | 清除登录信息 |
## 7. cookie 对象
| 方法 | 返回值 | 说明 |
|:------------------------------------|:----------|:------------|
| `cookie.getCookie(url)` | `String` | 获取全部 cookie |
| `cookie.getKey(url, key)` | `String?` | 获取某一键值 |
| `cookie.setCookie(url, cookie)` | — | 设置 cookie |
| `cookie.replaceCookie(url, cookie)` | — | 替换 cookie |
| `cookie.removeCookie(url)` | — | 删除 cookie |
## 8. cache 对象
saveTime 单位:秒,可省略。保存至数据库和缓存文件(50MB),内容较大时请使用 `getFile` / `putFile`
| 方法 | 返回值 | 说明 |
|:---------------------------------------|:----------|:--------|
| `cache.put(key, value, saveTime?)` | — | 保存到数据库 |
| `cache.get(key)` | `String?` | 从数据库读取 |
| `cache.delete(key)` | — | 删除数据库缓存 |
| `cache.putFile(key, value, saveTime?)` | — | 缓存文件内容 |
| `cache.getFile(key)` | `String?` | 读取文件缓存 |
| `cache.putMemory(key, value)` | — | 保存到内存 |
| `cache.getFromMemory(key)` | `Any?` | 从内存读取 |
| `cache.deleteMemory(key)` | — | 删除内存缓存 |
+123
View File
@@ -0,0 +1,123 @@
# 正则表达式
正则表达式(Regular Expression)用于在文本中匹配、查找和替换特定模式。Legado 源规则中,以 `:`
开头的规则即为正则表达式,可用于书籍列表和目录列表的提取。
[[toc]]
## 基本匹配
正则表达式是我们用于在文本中检索字母和数字的模式。例如正则表达式 `cat`,表示:字母 `c` 后面跟着一个字母
`a`,再后面跟着一个字母 `t`
正则表达式通常区分大小写,因此 `Cat` 与字符串 "cat" 不匹配。
## 元字符
元字符是正则表达式的基本组成元素。
| 元字符 | 说明 |
|:-------:|-----------------------|
| `.` | 匹配除换行符以外的任意字符 |
| `[ ]` | 字符类,匹配方括号中包含的任意字符 |
| `[^ ]` | 否定字符类,匹配方括号中不包含的任意字符 |
| `*` | 匹配前面的子表达式零次或多次 |
| `+` | 匹配前面的子表达式一次或多次 |
| `?` | 匹配前面的子表达式零次或一次 |
| `{n,m}` | 匹配前面字符至少 n 次,但不超过 m 次 |
| `(xyz)` | 字符组,按照确切的顺序匹配字符 xyz |
| `\|` | 分支结构,匹配符号之前的字符或后面的字符 |
| `\` | 转义符 |
| `^` | 匹配行的开始 |
| `$` | 匹配行的结束 |
### 英文句号
`.` 可以匹配任意单个字符,但不会匹配换行符。例如 `.ar` 匹配 "car"、"par"、"gar"。
### 字符集
字符集(字符类)使用方括号指定。例如 `[Tt]he` 匹配 "The" 或 "the"。
**否定字符集**`^` 在方括号内出现时会取消字符集。例如 `[^c]ar` 匹配除了 "car" 以外的 "par"、"gar"。
### 重复
- **星号 `*`**:匹配上一个匹配规则的零次或多次。例如 `[a-z]*` 匹配一行中任意数量的小写字母。
- **加号 `+`**:匹配上一个字符的一次或多次。例如 `c.+t` 匹配 "cat sat on the mat"。
- **问号 `?`**:表示前一个字符是可选的。例如 `[T]?he` 同时匹配 "The" 和 "he"。
### 花括号(量词)
用于指定字符或一组字符可以重复的次数。例如 `[0-9]{2,3}` 匹配至少 2 位但不超过 3 位数字。
### 字符组
写在圆括号内的子模式 `(...)`。例如 `(ab)*` 匹配零个或多个 "ab"。`(c|g|p)ar` 匹配 "car"、"gar" 或 "par"。
### 分支结构
`|` 用来定义分支结构。字符集只在字符级别上有作用,而分支结构在表达式级别上依然可以使用。
### 转义特殊字符
使用 `\` 来转义下一个字符。例如 `(f|c|m)at\.?` 匹配 "fat"、"cat"、"mat" 以及可选的 "." 字符。
### 定位符
- `^` 检查匹配字符是否是起始字符
- `$` 检查匹配字符是否是最后一个字符
## 简写字符集
| 简写 | 说明 |
|:----:|-----------------------------|
| `.` | 匹配除换行符以外的任意字符 |
| `\w` | 匹配所有字母和数字的字符:`[a-zA-Z0-9_]` |
| `\W` | 匹配非字母和数字的字符:`[^\w]` |
| `\d` | 匹配数字:`[0-9]` |
| `\D` | 匹配非数字:`[^\d]` |
| `\s` | 匹配空格符:`[\t\n\f\r\p{Z}]` |
| `\S` | 匹配非空格符:`[^\s]` |
## 断言
| 符号 | 说明 |
|:-----:|--------|
| `?=` | 正向先行断言 |
| `?!` | 负向先行断言 |
| `?<=` | 正向后行断言 |
| `?<!` | 负向后行断言 |
**正向先行断言**:匹配特定模式之后的内容。例如 `(T|t)he(?=\sfat)` 匹配 "The" 或 "the" 后面跟着 "fat"
的情况。
**负向先行断言**:获取不匹配表达式的内容。例如 `(T|t)he(?!\sfat)` 匹配不在 "fat" 前面的 "The" 或 "
the"。
**正向后行断言**:获取在特定模式之前的匹配内容。例如 `(?<=(T|t)he\s)(fat|mat)` 匹配在 "The" 或 "the"
之后的 "fat" 和 "mat"。
**负向后行断言**:获取不在特定模式之前的匹配内容。
## 标记
| 标记 | 说明 |
|:---:|--------|
| `i` | 不区分大小写 |
| `g` | 全局搜索 |
| `m` | 多行匹配 |
## 常用正则表达式
| 用途 | 正则表达式 |
|---------|------------------------------|
| 数字 | `\d+$` |
| 用户名 | `^[\w\d_.]{4,16}$` |
| 字母数字字符 | `^[a-zA-Z0-9]*$` |
| 小写字母 | `[a-z]+$` |
| 大写字母 | `[A-Z]+$` |
| HTML 标签 | `<[^>]+?>` |
| 求更求转发致谢 | `[\((【].*?[求更谢乐发推].*?[】)\)]` |
| 查找最新章节 | `您可以.*?查找最新章节` |
| PS/ps | `(?i)ps\b.*` |
+83
View File
@@ -0,0 +1,83 @@
# 请求头配置 (Request Headers)
[[toc]]
源的请求头字段用于控制 HTTP 请求行为,包括代理设置、自定义 Headers 和动态参数注入。
## 1. 基本格式
请求头以 JSON 对象形式填写,key **区分大小写**
| 正确 | 错误 |
|:-------------|:-------------|
| `User-Agent` | `user-agent` |
| `Referer` | `referer` |
## 2. 代理配置 (Proxy)
支持 HTTP、SOCKS4、SOCKS5 三种代理协议。
| 协议 | 格式 | 示例 |
|:----------|:-----------------------------|:--------------------------------------------------|
| SOCKS5 | `socks5://host:port` | `{"proxy": "socks5://127.0.0.1:1080"}` |
| SOCKS4 | `socks4://host:port` | `{"proxy": "socks4://127.0.0.1:1080"}` |
| HTTP | `http://host:port` | `{"proxy": "http://127.0.0.1:1080"}` |
| HTTP(带认证) | `http://host:port@user@pass` | `{"proxy": "http://127.0.0.1:1080@admin@secret"}` |
## 3. URL 附加 JS 参数
在 URL 后附加 JSON 对象,可在解析 URL 时执行 JavaScript 动态处理请求。
**语法:**
```
URL,{"js":"JavaScript 代码"}
```
**示例:**
```
https://www.example.com,{"js":"java.headerMap.put('xxx', 'yyy')"}
https://www.example.com,{"js":"java.url=java.url+'yyyy'"}
```
## 4. 图片链接自定义 Headers
在正文图片链接中附加自定义请求头,适用于需要 Referer 或 Cookie 才能加载图片的场景。
```js
let options = {
"headers": {
"User-Agent": "xxxx",
"Referrer": baseUrl,
"Cookie": "aaa=vbbb;"
}
};
'<img src="' + src + "," + JSON.stringify(options) + '">'
```
## 5. 重定向拦截 (Redirect Interception)
通过 `java.get` / `java.post` 方法拦截重定向,获取最终跳转后的 URL。适用于搜索结果会重定向的网站。
```js
// 方法签名
java.get(urlStr: String, headers: Map<String, String>)
java.post(urlStr: String, body: String, headers: Map<String, String>)
```
**示例**:搜索重定向场景下获取真实 URL
```js
(() => {
if (page == 1) {
let url = 'https://www.example.com/search,' + JSON.stringify({
"method": "POST",
"body": "show=title&tempid=1&keyboard=" + key
});
return source.put('surl', String(java.connect(url).raw().request().url()));
} else {
return source.get('surl') + '&page=' + (page - 1)
}
})()
```
+131
View File
@@ -0,0 +1,131 @@
# 源规则 (Source Rule) 帮助
[[toc]]
本文档介绍 Legado 源规则的核心配置项和语法。源通过声明式的 JSON 配置和可选的 JavaScript 逻辑,从网站抓取书籍内容。
::: tip 相关文档
- [JS 变量和函数](./js) — 内置变量和扩展 API
- [XPath 路径表达式](./xpath) — XPath 语法
- [正则表达式](./regex) — 正则语法
- [请求头配置](./request-headers) — 代理、Headers、重定向拦截
- [认证与登录](./authentication) — CookieJar、登录 UI、登录 URL
- [发现 URL 配置](./discovery-url) — 发现页入口 JSON 格式
:::
辅助键盘 ❓ 中可插入 URL 参数模板,打开帮助,js 教程,正则教程,选择文件。
## 2. 规则标志 (Rule Flags)
在 <code v-pre>{{......}}</code> 内使用规则时,必须包含明确的规则标志。没有规则标志的内容将作为
JavaScript 执行。
| 标志 | 语法 | 可省略条件 | 适用范围 |
|:----------|:---------|:--------------|:-----------|
| `@@` | 默认规则 | 直接写时可省略 | 所有字段 |
| `@XPath:` | XPath 规则 | 以 `//` 开头时可省略 | 所有字段 |
| `@Json:` | JSON 规则 | 以 `$.` 开头时可省略 | 所有字段 |
| `:` | 正则规则 | 不可省略 | 仅书籍列表和目录列表 |
## 3. JS 库注入 (jsLib)
注入 JavaScript 到 Rhino 引擎中,支持两种格式,可实现函数共用。
| 格式 | 说明 | 示例 |
|:------------------|:-----------------------|:-------------------------------------------|
| `JavaScript Code` | 直接填写 JavaScript 片段 | `function myUtil(s) { return s.trim() }` |
| JSON Map | URL 映射表,自动复用已下载的 js 文件 | `{"example":"https://example.com/lib.js"}` |
::: warning 线程安全
此处定义的函数可能被多个线程同时调用,函数内的全局变量内容将被共享。对其进行修改可能导致竞争问题。
- 函数内**不可**声明全局变量
- 函数外的全局变量**不可**再赋值,否则会抛出 `无法修改密封对象的属性` 异常
:::
## 4. 并发率 (Request Rate Limit)
控制对目标网站的请求频率,支持两种格式:
| 格式 | 说明 | 示例 |
|:------|:-----------|:----------------------------|
| `N` | 访问间隔(毫秒) | `1000` — 每次请求间隔 1 秒 |
| `N/M` | 时间窗口内最大请求数 | `20/60000` — 60 秒内最多 20 次请求 |
## 5. 源类型:文件 (File Source)
适用于提供文件下载的网站(如知轩藏书)。在源详情的「下载 URL」规则中获取文件链接。
**工作原理:**
1. 通过截取下载链接或文件响应头获取文件信息
2. 获取失败时自动拼接 `书名``作者` 和下载链接 `UrlOption``type` 字段
3. 压缩文件解压缓存在下次启动后自动清理,不占用额外空间
## 6. 字体解析 (Font Parsing)
在正文替换规则中使用,根据源字体的字形数据到目标字体中查找对应编码。
```js
(function(){
var b64 = String(src).match(/ttf;base64,([^\)]+)/);
if (b64) {
var f1 = java.queryTTF(b64[1]);
var f2 = java.queryTTF("https://example.com/font/SourceHanSansCN.ttf");
return java.replaceFont(result, f1, f2, true);
}
return result;
})()
```
## 7. 购买操作 (Purchase Action)
可直接填写链接或 JavaScript。
| 返回值 | 行为 |
|:--------------|:------------|
| 网络链接 | 自动打开浏览器 |
| `true`(JS 返回) | 自动刷新目录和当前章节 |
## 8. 图片解密 (Image Decryption)
适用于图片需要二次解密的情况。直接填写 JavaScript,返回解密后的 `ByteArray`
**可用变量:**
| 变量 | 说明 |
|:---------|:-------------------------------------------------------------------------------------------------------------------------------------|
| `java` | 仅支持 [JsExtensions](https://github.com/HapeLee/legado-with-MD3/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt) 中的方法 |
| `result` | 待解密图片的 `ByteArray` |
| `src` | 图片链接 |
**示例 — AES 解密:**
```js
java.createSymmetricCrypto("AES/CBC/PKCS5Padding", key, iv).decrypt(result)
```
**示例 — XOR 解密:**
```js
function decodeImage(data, key) {
var input = new Packages.java.io.ByteArrayInputStream(data)
var out = new Packages.java.io.ByteArrayOutputStream()
var byte
while ((byte = input.read()) != -1) {
out.write(byte ^ key)
}
return out.toByteArray()
}
decodeImage(result, key)
```
### 封面解密
与图片解密类似,其中 `result` 为待解密封面的 `InputStream`
```js
java.createSymmetricCrypto("AES/CBC/PKCS5Padding", key, iv).decrypt(result)
```
+193
View File
@@ -0,0 +1,193 @@
# 源字段速查 (Source Fields)
[[toc]]
本文档列出源 JSON 中各部分(搜索、发现、详情页、目录、正文)的所有可用字段。
## 1. 基本字段
| 字段 | 必须 | 说明 |
|:--------------------|:---|:------------------------------------------|
| `bookSourceUrl` | 是 | 源 URL,唯一标识,不可重复。与其他源相同会覆盖 |
| `bookSourceName` | 是 | 源名称,可重复 |
| `bookSourceGroup` | 否 | 源分组,用于整理 |
| `bookSourceType` | 否 | 源类型:`0`(文本,默认)、`1`(音频) |
| `bookUrlPattern` | 否 | 书籍 URL 正则,添加网址时用于识别源 |
| `header` | 否 | 请求头 JSON 字符串,见 [请求头配置](./request-headers) |
| `loginUrl` | 否 | 登录 URL,见 [认证与登录](./authentication) |
| `exploreUrl` | 否 | 发现地址,见 [发现 URL 配置](./discovery-url) |
| `searchUrl` | 否 | 搜索地址 |
| `weight` | 否 | 源权重,搜索排序时使用 |
| `enabled` | 否 | 是否启用 |
| `enabledExplore` | 否 | 是否启用发现 |
| `customOrder` | 否 | 自定义排序 |
| `lastUpdateTime` | 否 | 最后更新时间 |
| `bookSourceComment` | 否 | 源备注 |
## 2. 搜索字段 (`ruleSearch`)
| 字段 | 说明 |
|:--------------|:-----------------------------------|
| `url` | 搜索地址。`{{key}}` 为关键字,`{{page}}` 为页码 |
| `bookList` | 书籍列表规则 |
| `name` | 书名规则 |
| `author` | 作者规则 |
| `kind` | 分类规则 |
| `wordCount` | 字数规则 |
| `lastChapter` | 最新章节规则 |
| `intro` | 简介规则 |
| `coverUrl` | 封面规则 |
| `bookUrl` | 详情页 URL 规则 |
## 3. 发现字段 (`ruleExplore`)
| 字段 | 说明 |
|:--------------|:---------------------------------|
| `url` | 发现地址。`{{page}}` 为页码,支持 JSON 数组格式 |
| `bookList` | 书籍列表规则 |
| `name` | 书名规则 |
| `author` | 作者规则 |
| `kind` | 分类规则 |
| `wordCount` | 字数规则 |
| `lastChapter` | 最新章节规则 |
| `intro` | 简介规则 |
| `coverUrl` | 封面规则 |
| `bookUrl` | 详情页 URL 规则 |
## 4. 详情页字段 (`ruleBookInfo`)
| 字段 | 说明 |
|:---------------|:-----------------------------------------|
| `bookInfoInit` | 预处理规则(仅支持 AllInOne 正则或 JS |
| `name` | 书名规则 |
| `author` | 作者规则 |
| `kind` | 分类规则 |
| `wordCount` | 字数规则 |
| `lastChapter` | 最新章节规则 |
| `intro` | 简介规则 |
| `coverUrl` | 封面规则 |
| `tocUrl` | 目录 URL 规则(仅支持单个 URL) |
| `canReName` | 允许修改书名作者 |
| `relatedBooks` | 关联书籍配置,见 [关联书籍配置](../spec/related-books) |
### 预处理规则 (`bookInfoInit`)
只能使用 AllInOne 正则(以 `:` 开头)或 JS。JS 返回值需为 JSON 对象:
```javascript
(function(){
return {
a: "书名",
b: "作者",
c: "分类",
d: "字数",
e: "最新章节",
f: "简介",
g: "封面URL",
h: "目录URL"
};
})()
```
此时各规则字段填对应的 key`name``a``author``b`,以此类推。
### `canReName` 逻辑
| 条件 | 行为 |
|:-----------------|:--------|
| 规则不为空 且 详情页书名不为空 | 使用详情页书名 |
| 否则 | 使用搜索页书名 |
| 规则不为空 且 详情页作者不为空 | 使用详情页作者 |
| 否则 | 使用搜索页作者 |
## 5. 目录字段 (`ruleToc`)
| 字段 | 说明 |
|:--------------|:---------------------------------------------------|
| `chapterList` | 目录列表规则。首字符 `-` 可使列表反序 |
| `chapterName` | 章节名称规则 |
| `chapterUrl` | 章节 URL 规则 |
| `isVip` | VIP 标识。结果为 `null``false``0``""` 时为非 VIP |
| `updateTime` | 章节信息(可用 `java.timeFormat(timestamp)` 转换时间戳) |
| `nextTocUrl` | 目录下一页规则。支持单个 URL、URL 数组,JS 返回 `[]`/`null`/`""` 时停止 |
## 6. 正文字段 (`ruleContent`)
| 字段 | 说明 |
|:-----------------|:---------------------------------------|
| `content` | 正文规则 |
| `nextContentUrl` | 正文下一页 URL 规则。支持单个 URL、URL 数组 |
| `sourceRegex` | 资源正则,用于嗅探媒体资源 |
| `webJs` | WebView JS,用于模拟点击等操作。必须有返回值(不为空表示执行成功) |
### WebView JS (`webJs`)
用于模拟鼠标点击等操作,返回值不为空表示执行成功(否则会无限循环),返回值用于资源正则或正文。
**示例:**
```javascript
getDecode();$('#content').html();
```
### 资源正则 (`sourceRegex`)
用于嗅探 WebView 加载的媒体资源。配合章节 URL 的 `{"webView": true}` 使用。
一般写 `.*\.(mp3|mp4).*` 即可匹配常见媒体格式。
## 7. 完整 JSON 结构
```json
{
"bookSourceUrl": "https://www.example.com",
"bookSourceName": "示例源",
"bookSourceGroup": "分组",
"bookSourceType": 0,
"bookUrlPattern": "",
"header": "",
"loginUrl": "",
"searchUrl": "/search?key={{key}}&page={{page}}",
"exploreUrl": "",
"enabled": true,
"enabledExplore": false,
"weight": 0,
"ruleSearch": {
"bookList": "",
"name": "",
"author": "",
"kind": "",
"wordCount": "",
"lastChapter": "",
"intro": "",
"coverUrl": "",
"bookUrl": ""
},
"ruleExplore": {
"bookList": "",
"name": "",
"author": "",
"bookUrl": ""
},
"ruleBookInfo": {
"name": "",
"author": "",
"kind": "",
"intro": "",
"coverUrl": "",
"tocUrl": ""
},
"ruleToc": {
"chapterList": "",
"chapterName": "",
"chapterUrl": "",
"nextTocUrl": ""
},
"ruleContent": {
"content": "",
"nextContentUrl": "",
"sourceRegex": "",
"webJs": ""
}
}
```
+263
View File
@@ -0,0 +1,263 @@
# 规则语法详解 (Rule Syntax)
[[toc]]
Legado 支持多种规则语法,可在源的不同字段中混合使用。本文档详细介绍每种语法的写法和使用场景。
## 1. JSOUP 默认规则 (Default)
`@` 为分隔符的 CSS 选择器语法,是 Legado 最常用的规则类型。
### 基本结构
每段规则可分为 3 部分,用 `.` 连接:
```
类型.名称.位置
```
| 部分 | 说明 | 示例 |
|:---|:-----------------|:-------------------------------------|
| 类型 | 选择器类型 | `class``id``tag``text``children` |
| 名称 | 匹配值 | `odd``content``a` |
| 位置 | 索引(从 0 开始,负数为倒数) | `0``-1` |
**示例:**
```
class.odd.0@tag.a.0@text // 取 class="odd" 的第一个元素下第一个 <a> 标签的文本
tag.div.-1@text // 取最后一个 <div> 的文本
```
### 最后一段为获取内容
| 值 | 说明 |
|:------------|:------------|
| `text` | 文本内容 |
| `textNodes` | 文本节点列表 |
| `ownText` | 自身文本(不含子元素) |
| `href` | 链接地址 |
| `src` | 图片/资源地址 |
| `html` | HTML 内容 |
| `all` | 全部内容 |
### 排除符号 `!`
`!` 排除不需要的元素,多个排除用 `:` 分隔:
```
tag.div!0:2@text // 排除第 1 个和第 3 个 div
```
### 列表倒置 `-`
在获取列表的最前面加 `-` 可使列表倒置,适用于网站目录列表本身是倒序的情况:
```
-tag.dd@tag.a@text // 倒序获取所有 dd 下的 a 标签文本
```
### 正则替换 `##`
在规则末尾添加 `##正则表达式##替换内容`
```
tag.p@text##去除的文字##替换为的文字
tag.p@text##要去除的文字 // 替换内容为空时可省略第二个 ##
```
### 数组写法 `[index]`
支持类似数组的索引选择:
| 写法 | 说明 |
|:-----------|:--------------|
| `[0]` | 第一个元素 |
| `[-1]` | 最后一个元素 |
| `[0:3]` | 前 3 个元素 |
| `[2:5]` | 第 3 到第 5 个元素 |
| `[0:10:2]` | 每隔一个取一个 |
| `[!0:2]` | 排除第 1 个和第 3 个 |
| `[-1:0]` | 反转列表 |
### 连接符号
| 符号 | 说明 | 示例 |
|:-------|:---------------|:------------------------------|
| `\|\|` | 优先取第一个有值的结果 | `tag.a@text\|\|tag.span@text` |
| `&&` | 合并所有取到的值 | `tag.a@text&&tag.span@text` |
| `%%` | 交替取值(依次从各列表中取) | `tag.a@href%%tag.a@text` |
::: tip 使用限制
连接符号只能在同种规则间使用,不包括 js 和正则。
:::
## 2. JSOUP CSS 选择器 (CSS Selector)
`@css:` 开头的标准 CSS 选择器语法。
```
@css:选择器@获取内容
```
**示例:**
```
@css:.book-list li@text // 获取 class="book-list" 下所有 li 的文本
@css:#content p@html // 获取 id="content" 下所有 p 的 HTML
@css:[property=og:image]@content // 获取 meta 标签的 content 属性
```
- 在线测试:[Try jsoup online](https://try.jsoup.org/)
## 3. JSONPath 路径表达式
`@json:``$.` 开头的 JSON 数据提取语法。
```
@json:表达式
$.表达式
```
**示例:**
```
$.data.books[*] // 获取所有书籍
$.data.books[0].title // 获取第一本书的标题
$..books[*] // 递归查找所有 books 数组
$.info.Datas // 获取 info 下的 Datas
```
- 在线测试:[Jayway JsonPath Evaluator](http://jsonpath.herokuapp.com/)
## 4. XPath 路径表达式
`@XPath:``//` 开头的 XML/HTML 路径表达式。
```
@XPath:表达式
//表达式
```
**示例:**
```
//div[@class="book-list"]/ul/li // 获取指定 class 下的列表项
//*[@property="og:novel:author"]/@content // 获取 meta 标签属性
//a[text()="阅读"]/@href // 获取特定文本链接的 href
```
详细语法参见 [XPath 路径表达式](./xpath)。
## 5. JavaScript 脚本
`<js></js>``@js:` 中使用 JavaScript。
| 形式 | 使用位置 | 说明 |
|:------------|:----------------|:-----------------------|
| `@js:` | 只能放在其他规则的**最后** | 将前面规则的结果作为 `result` 变量 |
| `<js></js>` | 任意位置 | 可作为其他规则的分隔符 |
**示例:**
```
@css:.book@text@js:result.replace("旧", "新") // 先取文本再用 JS 处理
tag.li<js></js>//a // JS 作为分隔符
@js:java.base64Encode(key) // 纯 JS 表达式
```
### 在搜索/发现列表中使用
`+` 开头可使用 AllInOne 规则,在搜索列表、发现列表和目录中使用。
## 6. 正则表达式
Legado 中有三种正则用法:
### 全部匹配 (AllInOne)
- 只能在搜索列表、发现列表、详情页预加载和目录列表中使用
- 必须以 `:` 开头
```
:<正则表达式>
```
**示例(目录页):**
```
-:<li><a[^\"]+\"([^\"]*)\">([^<]*) // 提取链接和标题,- 前缀表示倒序
```
### 单一匹配 (OnlyOne)
形式:`##正则表达式##替换内容###`
- 只能在搜索列表、发现列表、详情页预加载、目录列表**之外**使用
- 只获取**第一个**匹配结果并替换
**示例(详情页):**
```
##:book_name"[^"]+"([^"]+)"##$1### // 提取书名
```
### 净化(循环替换)
形式:`##正则表达式##替换内容`
- 跟在其他规则后面,对结果进行循环匹配替换
- 独立使用相当于 `all##正则表达式##替换内容`
**示例(正文页):**
```
@css:.content@html##<script>.*?</script>## // 移除所有 script 标签
```
## 7. `{{}}` 模板变量
### 在搜索 URL 和发现 URL 中
`{{}}` 内只能使用 JavaScript
```
/search?key={{key}}&page={{page}}
/search?key={{java.base64Encode(key)}}&page={{(page-1)*20}}
```
### 在其他规则中
`{{}}` 内可使用任意规则,默认为 JS。使用其他规则需要标志头:
| 规则类型 | 标志头 |
|:---------|:-----------------|
| JS(默认) | 无需标志 |
| Default | `@@` |
| XPath | `@xpath:``//` |
| JSONPath | `@json:``$.` |
| CSS | `@css:` |
### `{}` 旧语法
保留的阅读 2.0 语法,只能使用 JSONPath,建议避免使用。
## 8. 变量存取
### `@put` 与 `@get`
只能在 JS 以外的规则中使用:
```
@put:{bid:"//*[@bid-data]/@bid-data"} // 存储变量
@get:bid // 读取变量
```
### `java.put` 与 `java.get`
只能在 JS 中使用(JS 中无法使用 `@get`):
```js
java.put('key', 'value') // 存储
java.get('key') // 读取
```
+31
View File
@@ -0,0 +1,31 @@
# 在线朗读规则
在线朗读规则用于接入第三方 TTS(文本转语音)服务。规则格式为 URL 规则,语法同源 URL 规则。
[[toc]]
## JS 参数
在规则中可以使用以下 JS 变量:
| 参数 | 说明 |
|--------------|--------------|
| `speakText` | 待朗读的文本内容 |
| `speakSpeed` | 朗读速度,范围 5-50 |
## 示例
在线 TTS 接口示例:
```
http://tts.example.com/text2audio,{
"method": "POST",
"body": "text={{java.encodeURI(speakText)}}&speed={{speakSpeed}}&lang=zh"
}
```
::: tip 说明
- 请求返回的音频会自动播放
- `speakSpeed` 值越大语速越快,需根据具体 TTS 服务的参数范围进行映射
:::
+30
View File
@@ -0,0 +1,30 @@
# TXT 目录正则
对于 TXT 格式的本地书籍,Legado 使用正则表达式自动识别章节标题并生成目录。当默认规则无法满足需求时,可以自定义目录正则。
[[toc]]
## 菜单区
| 按钮 | 说明 |
|------------|-------------------------|
| **新增目录规则** | 创建自定义目录正则,满足个性化需求 |
| **导入默认规则** | 恢复或更新 Legado 内置的默认目录规则 |
| **网络导入** | 从网络导入他人分享的目录规则 |
| **拆分超长章节** | 启用后,单章超过约 3 万字时会自动拆分为多章 |
::: tip 注意
导入默认规则不会覆盖用户自定义的规则,但会重置用户对内置规则的修改。
:::
## 操作区
界面上的按钮分为三组:
- **按钮 ①**(单选):选中表示当前书籍使用该目录规则。如果 Legado 自动识别的目录不理想,可以手动选择其他规则。该按钮
**仅对当前书籍生效**
- **按钮组 ②**
- 左边开关:启用后该规则会在自动识别目录时尝试匹配,**对所有 TXT 书籍生效**
- 中间按钮:编辑当前规则
- 右边按钮:删除当前规则(内置规则删除后可通过「导入默认规则」恢复)
- **按钮 ③**(确认):在当前界面进行操作后,需要点击确认按钮使选择生效
+193
View File
@@ -0,0 +1,193 @@
# URL 参数详解 (URL Options)
[[toc]]
Legado 源中的 URL 支持通过 JSON 对象附加请求参数,控制请求方法、编码、Headers、WebView 等行为。
## 1. 基本语法
URL 和参数之间用逗号 `,` 连接:
```
URL,{JSON 参数}
```
**简单 GET 请求:**
```
https://www.example.com/api/list
```
**带参数的请求:**
```
https://www.example.com/api/list,{
"charset": "gbk",
"headers": {"User-Agent": "Mozilla/5.0 ..."}
}
```
## 2. UrlOption 完整字段
| 字段 | 类型 | 必须 | 说明 |
|:----------|:----------|:---|:----------------------------------|
| `method` | `String` | 否 | 请求方法,`GET`(默认)或 `POST` |
| `charset` | `String` | 否 | 响应编码,默认 `utf-8` |
| `headers` | `Object` | 否 | 自定义请求头 |
| `body` | `String` | 否 | POST 请求体 |
| `webView` | `Boolean` | 否 | 是否使用 WebView 加载 |
| `js` | `String` | 否 | 解析 URL 时执行的 JS |
| `type` | `String` | 否 | 文件类型(用于文件类源) |
| `retry` | `Int` | 否 | 重试次数,默认 0 |
| `proxy` | `String` | 否 | 代理地址,见 [请求头配置](./request-headers) |
## 3. GET 请求
### 简单形式
```
https://www.example.com/api/list
```
### 带 Headers
```
https://www.example.com/api/list,{
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "zh-CN,zh;q=0.9"
}
}
```
### 指定编码
```
https://www.example.com/list,{
"charset": "gbk"
}
```
### 使用 WebView
```
https://www.example.com/book/123,{
"webView": true
}
```
### JS 动态构建
```javascript
var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
var headers = {"User-Agent": ua};
var option = {
"charset": "gbk",
"headers": headers,
"webView": true
};
"https://www.example.com," + JSON.stringify(option)
```
## 4. POST 请求
### 简单形式
```
https://www.example.com/search,{
"method": "POST",
"body": "keyword=系统&page=1"
}
```
### 带 Headers 和编码
```
https://www.example.com/search,{
"charset": "gbk",
"method": "POST",
"body": "searchkey={{key}}&page={{page}}",
"headers": {
"User-Agent": "Mozilla/5.0 ..."
}
}
```
### JS 动态构建
```javascript
var body = "key=" + key + "&page=" + page;
var option = {
"method": "POST",
"body": String(body),
"headers": {"User-Agent": "Mozilla/5.0 ..."}
};
"https://www.example.com/search," + JSON.stringify(option)
```
::: warning body 类型
`body` 必须保证是 JavaScript 的 String 类型。变量是计算得到的尽量都用 `String()` 强转。
:::
## 5. WebView 模式
设置 `"webView": true` 后,Legado 会使用内置 WebView 加载页面,适用于需要 JavaScript 渲染的网站。
### WebView 加载
```
https://www.example.com/book/123,{
"webView": true
}
```
### WebView + 正文嗅探
章节链接加 `{"webView": true}`,配合正文的 `sourceRegex` 嗅探媒体资源:
```json
{
"ruleToc": {
"chapterUrl": "href##$##{\"webView\":true}"
},
"ruleContent": {
"content": "<js>result</js>",
"sourceRegex": ".*\\.(mp3|mp4).*"
}
}
```
**嗅探步骤:**
1. 章节链接后面加 `,{"webView":true}`
2. 在有嗅探功能的浏览器中输入章节链接(不带 webView 参数)
3. 媒体开始播放后使用浏览器的嗅探功能查看资源链接
4. 在资源正则里填写资源链接的正则,如 `.*\.(mp3|mp4).*`
5. 正文填写 `<js>result</js>`
## 6. 模板变量
URL 中可使用以下模板变量:
| 变量 | 说明 | 适用位置 |
|:--------------------------------|:-----------|:--------------|
| `{{key}}` | 搜索关键字 | 搜索 URL |
| `{{page}}` | 页码(从 1 开始) | 搜索 URL、发现 URL |
| `{{page - 1 == 0 ? "" : page}}` | 第一页无页码 | 搜索 URL、发现 URL |
| `<,{{page}}>` | 第一页无页码(简写) | 搜索 URL、发现 URL |
### 页码计算示例
```
/search?key={{key}}&start={{(page-1)*20}}&limit=20
/search?key={{key}}&page={{page - 1 == 0 ? "" : page}}
```
## 7. 相对 URL
URL 支持相对路径,会自动基于源 URL 拼接:
```
/search?key={{key}} // 相对于 bookSourceUrl
/api/list // 相对于 bookSourceUrl
```
+165
View File
@@ -0,0 +1,165 @@
# XPath 路径表达式
[[toc]]
XPath 是一种用于在 XML/HTML 文档中定位节点的语言。Legado 源支持使用 XPath 规则提取网页内容,以 `//`
开头的规则会自动识别为 XPath 表达式。
_注:本文所有代码均通过 Chrome(版本 123.0.6312.86) 验证_
## 1. 轴 (Axes)
XPath 规范定义了 13 种轴,用于定位元素树上相对于当前元素的节点。
| 轴 | 说明 | 缩写 |
|:---------------------|:---------------------------|:-----|
| `attribute` | 元素的属性 | `@` |
| `self` | 元素本身 | `.` |
| `parent` | 当前元素的父元素 | `..` |
| `child` | 当前元素的子元素 | — |
| `ancestor` | 当前元素的所有直属祖先 | — |
| `ancestor-or-self` | 当前元素及其所有直属祖先 | — |
| `descendant` | 当前元素的所有递归子元素 | — |
| `descendant-or-self` | 当前元素及其所有递归子元素 | — |
| `following` | 当前元素之后出现的所有元素(无视层级,不含直属后代) | — |
| `following-sibling` | 当前元素之后出现的所有同级元素 | — |
| `preceding` | 当前元素之前出现的所有元素(无视层级,不含直属祖先) | — |
| `preceding-sibling` | 当前元素之前出现的所有同级元素 | — |
| `namespace` | 不支持 | — |
**语法**`轴名::表达式`
```js
> $x('//body/ancestor-or-self::*')
< [body, html]
```
## 2. 路径格式
XPath 通过"路径表达式"Path Expression)选取元素,形式上与文件系统路径类似。
| 概念 | 说明 |
|:-----|:----------------------------------|
| `/` | 路径内部分隔符 |
| 绝对路径 | 以 `/` 起首,后跟根元素,如 `/step/step/...` |
| 相对路径 | 除绝对路径外的其他写法,如 `step/step` |
| `.` | 当前元素 |
| `..` | 当前元素的父元素 |
### 选取语法
| 符号 | 说明 |
|:-----------|:------------|
| `/` | 选取根元素 |
| `//` | 选取任意位置的某个元素 |
| `nodename` | 选取指定名称的元素 |
| `@` | 选取某个属性 |
## 3. 示例
以下示例基于这段 HTML
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>标题</title>
<meta property="author" content="作者" />
</head>
<body>
<div>
<title lang="eng">Harry Potter</title>
<p>29.39</p>
<p>usd</p>
</div>
<div>
<title lang="cn">Cpp高级编程</title>
<p>39.95</p>
<p>rmb</p>
</div>
<div id="list">
<dl>
<dd><a href="/1"></a></dd>
<dd><a href="/2"></a></dd>
<dd><a href="/3"></a></dd>
</dl>
</div>
</body>
</html>
```
| 表达式 | 结果 | 说明 |
|:-----------------------|:---------------|:--------------|
| `$x('/')` | `[document]` | 选取根元素 |
| `$x('/html')` | `[html]` | 绝对路径选取 |
| `$x('html/head/meta')` | `[meta, meta]` | 相对路径选取 |
| `$x('//p')` | `[p, p, p, p]` | 选取所有 p 元素 |
| `$x('html/body//a')` | `[a, a, a]` | 选取 body 下所有 a |
| `$x('//@lang')` | `[lang, lang]` | 选取属性 |
| `$x('//meta/..')` | `[head]` | 选取父元素 |
## 4. 谓语条件 (Predicate)
谓语条件是对路径表达式的附加筛选条件,写在方括号 `[]` 中。
| 表达式 | 结果 | 说明 |
|:-------------------------------|:-------------------------------|:--------|
| `html/head/meta[1]` | `<meta charset="utf-8">` | 选取第一个 |
| `html/head/meta[last()]` | `<meta property="author" ...>` | 选取最后一个 |
| `html/head/meta[last()-1]` | `<meta charset="utf-8">` | 选取倒数第二个 |
| `html/head/meta[position()>1]` | `<meta property="author" ...>` | 位置大于 1 |
| `//title[@lang]` | 两个 title | 具有特定属性 |
| `//title[@lang="eng"]` | English title | 属性值匹配 |
| `/html/body/div[dl]` | `<div id="list">` | 包含特定子元素 |
| `/html/body/div[p>35.00]` | Cpp 高级编程 div | 子元素值条件 |
## 5. 通配符
| 符号 | 说明 |
|:-----|:--------|
| `*` | 匹配任何元素 |
| `@*` | 匹配任何属性名 |
```js
$x('//*') // 选取所有元素
$x('/*/*') // 选取所有第二层元素
$x('//title[@*]') // 选取所有带属性的 title
```
## 6. 多路径选择
`|` 合并多个表达式的选取结果:
```js
$x('//title | //a') // 选取所有 title 和 a 元素
```
## 7. 函数
XPath 函数的参数可以是静态字符串或表达式,函数可嵌套调用。XPath 索引从 **1** 开始。
| 函数 | 说明 | 示例 |
|:---------------------|:----------|:------------------------------------------------------|
| `boolean()` | 转换为布尔值 | `boolean(//title)``true` |
| `number()` | 转换为数字 | `number(//p[1])``29.39` |
| `round()` | 四舍五入 | `round(//p[1])``29` |
| `ceiling()` | 向上取整 | `ceiling(//p[1])``30` |
| `floor()` | 向下取整 | `floor(//p[1])``29` |
| `concat()` | 字符串拼接 | `concat("cost:", //p[1], //p[2])``'cost:29.39usd'` |
| `contains()` | 判断是否包含 | `contains(//p[1], "29.39")``true` |
| `count()` | 统计元素个数 | `count(//p)``4` |
| `id()` | 根据 id 选取 | `id("list")``[dl#list]` |
| `last()` | 同级元素集合数量 | `//p[last()]` |
| `name()` | 返回元素名 | `name(//*[@id])``'dl'` |
| `normalize-space()` | 去除前后空白 | `normalize-space(" test ")``'test'` |
| `not()` | 返回布尔反值 | `//title[not(@lang)]` |
| `position()` | 返回元素位置 | `//meta[position()=2]` |
| `starts-with()` | 检查字符串开头 | `//title[starts-with(., "Cpp")]` |
| `string()` | 转换为字符串 | `string(//p)``'29.39'` |
| `string-length()` | 返回字符串长度 | `string-length(string(//p))``5` |
| `substring()` | 截取字符串 | `substring(string(//p), 1, 3)``'29.'` |
| `substring-after()` | 某字符之后的字符串 | `substring-after(string(//p), ".")``'39'` |
| `substring-before()` | 某字符之前的字符串 | `substring-before(string(//p), ".")``'29'` |
| `sum()` | 对数字求和 | `sum(//p[1])``69.34` |
| `translate()` | 依次替换字符 | `translate("aabbcc", "ac", "V8")``'VVbb88'` |