Skip to content

Commit e4e7f1d

Browse files
committed
feat: Add Pangu spacing processor for CJK/Latin text
1 parent f54d5ff commit e4e7f1d

12 files changed

Lines changed: 832 additions & 0 deletions

File tree

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343

4444
- 无需主题适配即可使用的功能:
4545
- [代码高亮处理器](#代码高亮处理器)(仅全量版可用)
46+
- [中英文混排格式化处理器](#中英文混排格式化处理器)
4647
- 需要主题适配的 Finder API:
4748
- [文章字数统计 API(单篇/全站)](#文章字数统计-api)
4849
- [HTML 内容字数统计 API](#html-内容字数统计-api)
4950
- [代码高亮 API](#代码高亮-api)(仅全量版可用)
51+
- [中英文混排格式化 API](#中英文混排格式化-api)
5052

5153
未来将实现的功能:[TODO](#todo)
5254

@@ -73,12 +75,17 @@
7375
- [HTML 内容字数统计 API](#html-内容字数统计-api)
7476
- [渲染 API](#渲染-api)
7577
- [代码高亮 API](#代码高亮-api)
78+
- [中英文混排格式化 API](#中英文混排格式化-api)
7679
- [处理器文档](#处理器文档)
7780
- [代码高亮处理器](#代码高亮处理器)
7881
- [特点](#特点)
7982
- [配置选项](#配置选项)
8083
- [支持的主题](#支持的主题)
8184
- [补充说明](#补充说明)
85+
- [中英文混排格式化处理器](#中英文混排格式化处理器)
86+
- [功能说明](#功能说明)
87+
- [使用说明](#使用说明)
88+
- [补充说明](#补充说明-1)
8289
- [版本说明](#版本说明)
8390
- [轻量版的优势](#轻量版的优势)
8491
- [轻量版本缺少的功能](#轻量版本缺少的功能)
@@ -431,6 +438,110 @@ extraApiRenderFinder.renderCodeHtml(htmlContent)
431438
</div>
432439
```
433440

441+
#### 中英文混排格式化 API
442+
443+
**Finder 名称:** `extraApiPanguFinder`
444+
445+
**描述**
446+
447+
提供中英文混排自动空格功能,自动在中日韩(CJK)字符与英文字母、数字、符号之间插入空格,提升内容可读性。此功能在轻量版和全量版中均可用。
448+
449+
**API 方法**
450+
451+
```javascript
452+
// 对 HTML 内容中的指定标签应用 Pangu 空格处理
453+
extraApiPanguFinder.spacingElementByTagName(htmlContent, tagName)
454+
455+
// 对 HTML 内容中具有指定 ID 的元素应用 Pangu 空格处理
456+
extraApiPanguFinder.spacingElementById(htmlContent, id)
457+
458+
// 对 HTML 内容中具有指定 class 的元素应用 Pangu 空格处理
459+
extraApiPanguFinder.spacingElementByClassName(htmlContent, className)
460+
461+
// 对纯文本应用 Pangu 空格处理
462+
extraApiPanguFinder.spacingText(text)
463+
```
464+
465+
**参数**
466+
467+
- `spacingElementByTagName(htmlContent, tagName)`
468+
- `htmlContent`
469+
- 类型:`string`
470+
- 解释:包含 HTML 标签的内容
471+
- `tagName`
472+
- 类型:`string`
473+
- 解释:要处理的 HTML 标签名称(如 "p"、"div"、"span" 等)
474+
475+
- `spacingElementById(htmlContent, id)`
476+
- `htmlContent`
477+
- 类型:`string`
478+
- 解释:包含 HTML 标签的内容
479+
- `id`
480+
- 类型:`string`
481+
- 解释:要处理的元素 ID(如 "main"、"content" 等)
482+
483+
- `spacingElementByClassName(htmlContent, className)`
484+
- `htmlContent`
485+
- 类型:`string`
486+
- 解释:包含 HTML 标签的内容
487+
- `className`
488+
- 类型:`string`
489+
- 解释:要处理的 class 名称(如 "comment"、"article" 等)
490+
491+
- `spacingText(text)`
492+
- `text`
493+
- 类型:`string`
494+
- 解释:要处理的纯文本内容
495+
496+
**返回值**
497+
498+
- 类型:`Mono<String>`
499+
- 解释:处理后的内容,失败时返回原始内容
500+
501+
**处理规则**
502+
503+
- 在中日韩字符和英文字母之间添加空格
504+
- 在中日韩字符和数字之间添加空格
505+
- 在中日韩字符和常见符号之间添加空格
506+
- 自动跳过 `<code>``<pre>``<script>``<style>``<textarea>` 等标签,保留其原始格式
507+
508+
**使用示例**
509+
510+
```html
511+
<!--/* 对文章内容中的段落标签应用 Pangu 处理,下面这段代码可直接用于 /templates/post.html */-->
512+
<div th:utext="${extraApiPanguFinder.spacingElementByTagName(post.content?.content, 'p')}"></div>
513+
514+
<!--/* 对整个 HTML 内容的所有 div 标签应用 Pangu 处理 */-->
515+
<div th:utext="${extraApiPanguFinder.spacingElementByTagName(content, 'div')}"></div>
516+
517+
<!--/* 对指定 ID 的元素应用 Pangu 处理 */-->
518+
<div th:utext="${extraApiPanguFinder.spacingElementById(content, 'main')}"></div>
519+
520+
<!--/* 对指定 class 的元素应用 Pangu 处理 */-->
521+
<div th:utext="${extraApiPanguFinder.spacingElementByClassName(content, 'comment')}"></div>
522+
523+
<!--/* 对纯文本应用 Pangu 处理 */-->
524+
<span th:text="${extraApiPanguFinder.spacingText('请问Jackie的鼻子有几个?123个!')}"></span>
525+
<!--/* 输出:请问 Jackie 的鼻子有几个?123 个! */-->
526+
527+
<!--/* 在变量中使用 */-->
528+
<div th:with="processedContent=${extraApiPanguFinder.spacingElementByTagName(moment.spec.content?.html, 'p')}">
529+
<div th:utext="${processedContent}"></div>
530+
</div>
531+
```
532+
533+
**性能说明**
534+
535+
- 纯 Java 实现,无需 JavaScript 运行时
536+
- 处理速度快,适合在模板中直接使用
537+
- 不涉及缓存,每次调用都会重新处理
538+
539+
**错误处理**
540+
541+
- 输入为空或 null 时返回原始内容
542+
- HTML 解析失败时返回原始内容
543+
- 不会抛出异常,保证页面渲染稳定性
544+
434545
## 处理器文档
435546

436547
### 代码高亮处理器
@@ -516,6 +627,48 @@ extraApiRenderFinder.renderCodeHtml(htmlContent)
516627
- 补充说明:
517628
- 双主题模式会生成两个并列的 div 元素
518629

630+
### 中英文混排格式化处理器
631+
632+
插件提供了自动化的中英文混排格式化处理器,无需在模板中手动调用,即可对文章和页面内容自动应用 Pangu 空格处理。
633+
634+
此功能在轻量版和全量版中均可用。
635+
636+
#### 功能说明
637+
638+
- **自动处理范围**:
639+
- 处理器会自动处理文章(post)和页面(page)内容中的段落标签(`<p>`)
640+
- 在中日韩字符与英文、数字、符号之间自动插入空格
641+
642+
- **处理规则**:
643+
- 自动在 CJK 字符和英文字母之间添加空格
644+
- 自动在 CJK 字符和数字之间添加空格
645+
- 自动在 CJK 字符和常见符号之间添加空格
646+
- 智能跳过 `<code>`、`<pre>`、`<script>``<style>``<textarea>` 等标签
647+
648+
- **性能特点**:
649+
- 纯 Java 实现,无需 JavaScript 运行时
650+
- 处理速度快,对页面加载影响极小
651+
- 不涉及缓存,每次渲染时实时处理
652+
653+
- **错误处理**:
654+
- 处理失败时保持原始内容不变
655+
- 不会因格式化问题影响页面正常渲染
656+
657+
#### 使用说明
658+
659+
处理器默认启用,无需额外配置。如果您希望禁用自动处理,可以通过以下方式:
660+
661+
1. 在 Halo 管理后台进入插件设置页面
662+
2. 找到"中英文混排格式化"相关选项(如果提供)
663+
3. 或者仅使用 [Finder API](#中英文混排格式化-api) 在需要的地方手动调用
664+
665+
#### 补充说明
666+
667+
- 此功能使用 [Pangu.java](https://github.com/vinta/pangu.java) 库实现
668+
- 仅处理可见文本内容,不影响 HTML 结构
669+
- 递归处理嵌套元素,确保完整覆盖
670+
- 与代码高亮处理器兼容,互不影响
671+
519672
## 版本说明
520673
521674
插件提供两个版本:

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ dependencies {
8181
// JavaScript 引擎 - 为什么选择 Javet:支持 Node.js 模块,性能好,维护活跃
8282
implementation 'com.caoccao.javet:javet:4.1.7'
8383

84+
// Pangu - 自动在中日韩字符和英文、数字、符号之间添加空格,提升可读性
85+
implementation 'ws.vinta:pangu:1.1.0'
86+
8487
// Spring 依赖 - compileOnly:Halo 已内置,避免版本冲突和包体积增加
8588
compileOnly 'org.springframework:spring-context'
8689
compileOnly 'org.springframework:spring-core'
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package top.howiehz.halo.plugin.extra.api.finder.basic;
2+
3+
import reactor.core.publisher.Mono;
4+
5+
/**
6+
* Finder for Pangu spacing operations.
7+
* Pangu 空格操作的 Finder。
8+
*
9+
* <p>This finder provides template-accessible methods for applying Pangu spacing
10+
* to text content, improving readability by inserting whitespace between CJK
11+
* and Latin characters.</p>
12+
* <p>此 Finder 提供模板可访问的方法,用于对文本内容应用 Pangu 空格,
13+
* 通过在中日韩字符和拉丁字符之间插入空格来提高可读性。</p>
14+
*
15+
* @author HowieXie
16+
* @since 1.0.0
17+
*/
18+
public interface ExtraApiPanguFinder {
19+
20+
/**
21+
* Apply Pangu spacing to HTML content for specified tag elements.
22+
* 对 HTML 内容中的指定标签元素应用 Pangu 空格。
23+
*
24+
* @param htmlContent the HTML content to process / 要处理的 HTML 内容
25+
* @param tagName the tag name to process (e.g., "p", "div") / 要处理的标签名称(例如 "p"、"div")
26+
* @return Mono emitting processed HTML content / 发出处理后的 HTML 内容的 Mono
27+
*/
28+
Mono<String> spacingElementByTagName(String htmlContent, String tagName);
29+
30+
/**
31+
* Apply Pangu spacing to HTML element with specified ID.
32+
* 对 HTML 内容中具有指定 ID 的元素应用 Pangu 空格。
33+
*
34+
* @param htmlContent the HTML content to process / 要处理的 HTML 内容
35+
* @param id the element ID to process / 要处理的元素 ID
36+
* @return Mono emitting processed HTML content / 发出处理后的 HTML 内容的 Mono
37+
*/
38+
Mono<String> spacingElementById(String htmlContent, String id);
39+
40+
/**
41+
* Apply Pangu spacing to HTML elements with specified class name.
42+
* 对 HTML 内容中具有指定 class 的元素应用 Pangu 空格。
43+
*
44+
* @param htmlContent the HTML content to process / 要处理的 HTML 内容
45+
* @param className the class name to process / 要处理的 class 名称
46+
* @return Mono emitting processed HTML content / 发出处理后的 HTML 内容的 Mono
47+
*/
48+
Mono<String> spacingElementByClassName(String htmlContent, String className);
49+
50+
/**
51+
* Apply Pangu spacing to plain text.
52+
* 对纯文本应用 Pangu 空格。
53+
*
54+
* @param text the plain text to process / 要处理的纯文本
55+
* @return Mono emitting processed text / 发出处理后的文本的 Mono
56+
*/
57+
Mono<String> spacingText(String text);
58+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package top.howiehz.halo.plugin.extra.api.finder.basic.impl;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.stereotype.Component;
5+
import reactor.core.publisher.Mono;
6+
import run.halo.app.theme.finders.Finder;
7+
import top.howiehz.halo.plugin.extra.api.finder.basic.ExtraApiPanguFinder;
8+
import top.howiehz.halo.plugin.extra.api.service.basic.post.render.pangu.PanguSpacingService;
9+
10+
/**
11+
* Implementation of ExtraApiPanguFinder.
12+
* ExtraApiPanguFinder 的实现。
13+
*
14+
* <p>This finder implementation provides template-accessible methods for applying
15+
* Pangu spacing to text content in Halo themes.</p>
16+
* <p>此 Finder 实现为 Halo 主题提供模板可访问的方法,用于对文本内容应用 Pangu 空格。</p>
17+
*
18+
* @author HowieXie
19+
* @since 1.0.0
20+
*/
21+
@Component
22+
@RequiredArgsConstructor
23+
@Finder("extraApiPanguFinder")
24+
public class ExtraApiPanguFinderImpl implements ExtraApiPanguFinder {
25+
26+
private final PanguSpacingService panguSpacingService;
27+
28+
@Override
29+
public Mono<String> spacingElementByTagName(String htmlContent, String tagName) {
30+
return Mono.fromCallable(() ->
31+
panguSpacingService.spacingElementByTagName(htmlContent, tagName));
32+
}
33+
34+
@Override
35+
public Mono<String> spacingElementById(String htmlContent, String id) {
36+
return Mono.fromCallable(() ->
37+
panguSpacingService.spacingElementById(htmlContent, id));
38+
}
39+
40+
@Override
41+
public Mono<String> spacingElementByClassName(String htmlContent, String className) {
42+
return Mono.fromCallable(() ->
43+
panguSpacingService.spacingElementByClassName(htmlContent, className));
44+
}
45+
46+
@Override
47+
public Mono<String> spacingText(String text) {
48+
return Mono.fromCallable(() -> panguSpacingService.spacingText(text));
49+
}
50+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package top.howiehz.halo.plugin.extra.api.service.basic.config;
2+
3+
import lombok.Data;
4+
5+
/**
6+
* Configuration class for Pangu text spacing.
7+
* Pangu 中英文混排格式化配置文件类。
8+
*
9+
* @author HowieXie
10+
* @since 1.0.0
11+
*/
12+
@Data
13+
public class PanguConfig {
14+
15+
/**
16+
* Whether to enable Pangu rendering.
17+
* 是否启用 Pangu 自动渲染。
18+
*/
19+
private boolean enabledPanguRender = true;
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package top.howiehz.halo.plugin.extra.api.service.basic.config;
2+
3+
import java.util.function.Supplier;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.stereotype.Component;
6+
import reactor.core.publisher.Mono;
7+
import run.halo.app.plugin.ReactiveSettingFetcher;
8+
9+
/**
10+
* Supplier for Pangu configuration that fetches settings reactively.
11+
* Pangu 配置的供应器,以响应式方式获取设置。
12+
*
13+
* @author HowieXie
14+
* @since 1.0.0
15+
*/
16+
@Component
17+
@RequiredArgsConstructor
18+
public class PanguConfigSupplier implements Supplier<Mono<PanguConfig>> {
19+
private final ReactiveSettingFetcher fetcher;
20+
21+
/**
22+
* Get the Pangu configuration from plugin settings.
23+
* 从插件设置中获取 Pangu 配置。
24+
*
25+
* @return Mono emitting the Pangu configuration / 发出 Pangu 配置的 Mono
26+
*/
27+
@Override
28+
public Mono<PanguConfig> get() {
29+
return fetcher.fetch("pangu", PanguConfig.class)
30+
.defaultIfEmpty(new PanguConfig());
31+
}
32+
}

0 commit comments

Comments
 (0)