fix(粘贴): URL 提取按括号配对裁剪,修复高德链接被截断导致地址丢失
## 现象(用户实测)
粘贴高德分享链接后,识别出的地点"只有一半,只有一部分"。
## 根因
高德分享链接里的 POI 名称含**未编码的半角括号**:
...%E9%9D%92%E5%B2%9B%E8%8F%9C(黄岛珠江路店)%2C武夷山路118号...
↑ 就是这个 ( 和 )
而 URL 提取正则是 `/https?:\/\/[^\s"'<>()【】]+/i`(当时还排除了半角 `)`),
于是一到第一个 `)` 就停:
提取长度 221 / 原文 456 ← 只拿到不到一半
__p 切成 4 段(应为 5 段)
名称 = "船歌·鱼水饺青岛菜(黄岛珠江路店" ← 括号不闭合
地址 = 空 ← 第 5 段整个丢失
## 为什么当初排除括号
本意是防止把 URL 后跟着的中文括注一起吞掉,如
`看这个 https://x.com/a(路线图在这里)`。
但规则太粗暴,误伤了 URL 内部的合法括号 —— RFC 3986 允许 `(` `)` 出现在 query。
## 修法
1. 正则放宽:只排除空白 / 引号 / 尖括号 / **全角**括号(全角括号不会被编码进 URL,
出现在明文里必然是用户自己加的中文括注)
2. 新增 `trimUrlTail()`:从左扫描数括号,`depth < 0` 说明遇到多余的右括号,
那里才是真正的 URL 边界
3. 再裁掉末尾可能粘上的中文标点(。,、;!?)
## 验证(用用户提供的真实 URL 跑等价逻辑)
修复后:
提取长度 780 / 原文 780 ← 完整
__p 段数 5
[0] B02140WITR
[1] 35.95086086125046
[2] 120.19152268767353
[3] 船歌·鱼水饺青岛菜(黄岛珠江路店) ← 括号完整
[4] 武夷山路118号中南·海湾新城(珠江路金石国际广场旁) ← 地址回来了
边界场景:
"https://x?a=1(路线图)" → https://x?a=1 ✅ 中文括注被裁掉
"https://x?a=1) 后面有字" → https://x?a=1 ✅ 多余右括号被裁掉
同时确认另外两处 `split(',')` 都用了 `slice(2).join(',')` 拼回,
地址内部含逗号时不会被切错。
## 编译
`BUILD SUCCESSFUL`
## 未验证
真机端到端(写剪贴板 → 点粘贴 → 看结果)需人工操作:
hdc 没有可用的剪贴板写入工具(`paste` 是 Linux 文本合并命令,非剪贴板服务)。
解析逻辑本身已用真实 URL 在等价实现上验证通过。
This commit is contained in:
@@ -63,13 +63,54 @@ export class PlaceParser {
|
|||||||
|
|
||||||
// ==================== 链接解析 ====================
|
// ==================== 链接解析 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁剪 URL 尾部:只裁"多出来"的右括号与中文标点,保留 URL 内部的合法括号。
|
||||||
|
*
|
||||||
|
* 为什么需要:高德分享链接里的 POI 名称常含未编码的半角括号,
|
||||||
|
* 如 `...青岛菜(黄岛珠江路店)%2C武夷山路...`
|
||||||
|
* 若正则直接排除 ')',就会在第一个 ')' 处截断 → 后面的地址段全丢。
|
||||||
|
* 实测该链接被截到 221/456 字符,__p 从 5 段变成 4 段。
|
||||||
|
*
|
||||||
|
* 做法:从左往右数括号,depth 变负说明这个 ')' 没有对应 '(',
|
||||||
|
* 它不属于 URL → 从这里截断。
|
||||||
|
*/
|
||||||
|
private static trimUrlTail(raw: string): string {
|
||||||
|
let depth: number = 0;
|
||||||
|
let cut: number = raw.length;
|
||||||
|
for (let i = 0; i < raw.length; i++) {
|
||||||
|
const ch: string = raw.charAt(i);
|
||||||
|
if (ch === '(') {
|
||||||
|
depth++;
|
||||||
|
} else if (ch === ')') {
|
||||||
|
depth--;
|
||||||
|
if (depth < 0) {
|
||||||
|
// 多出来的右括号 → URL 到此为止
|
||||||
|
cut = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let out: string = raw.substring(0, cut);
|
||||||
|
// 再裁掉可能粘在末尾的中文标点(用户手动加的全角句号/逗号等)
|
||||||
|
while (out.length > 0) {
|
||||||
|
const last: string = out.charAt(out.length - 1);
|
||||||
|
if (last === '。' || last === ',' || last === '、' || last === ';' ||
|
||||||
|
last === '!' || last === '?') {
|
||||||
|
out = out.substring(0, out.length - 1);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
private static async parseUrl(text: string): Promise<ParsedPlace | null> {
|
private static async parseUrl(text: string): Promise<ParsedPlace | null> {
|
||||||
// 从可能夹带其他文字的段落里抽出第一个 http(s) 链接
|
// 从可能夹带其他文字的段落里抽出第一个 http(s) 链接
|
||||||
const urlMatch: RegExpExecArray | null = /https?:\/\/[^\s"'<>))]+/i.exec(text);
|
const urlMatch: RegExpExecArray | null = /https?:\/\/[^\s"'<>()【】]+/i.exec(text);
|
||||||
if (urlMatch === null) {
|
if (urlMatch === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const url: string = urlMatch[0];
|
const url: string = PlaceParser.trimUrlTail(urlMatch[0]);
|
||||||
// ① poi/detail: __p=<id>,<lat>,<lng>,<name>,<address...>
|
// ① poi/detail: __p=<id>,<lat>,<lng>,<name>,<address...>
|
||||||
const p: ParsedPlace | null = PlaceParser.parseAmapP(url);
|
const p: ParsedPlace | null = PlaceParser.parseAmapP(url);
|
||||||
if (p !== null) {
|
if (p !== null) {
|
||||||
|
|||||||
@@ -1754,7 +1754,11 @@ struct EventEditPage {
|
|||||||
// 外观与右边的「地图」按钮保持一致:同字号、同色、同底色、同圆角、同高度。
|
// 外观与右边的「地图」按钮保持一致:同字号、同色、同底色、同圆角、同高度。
|
||||||
PasteButton({
|
PasteButton({
|
||||||
text: PasteDescription.PASTE,
|
text: PasteDescription.PASTE,
|
||||||
buttonType: ButtonType.Capsule
|
// ⚠️ 必须用 Normal,不能用 Capsule:
|
||||||
|
// 按 SDK 文档,Capsule / Circle 下 borderRadius **不生效**,
|
||||||
|
// 圆角由按钮类型自动决定(= 两端全圆),与「地图」的圆角矩形不一致。
|
||||||
|
// 只有 Normal / ROUNDED_RECTANGLE 才认 borderRadius。
|
||||||
|
buttonType: ButtonType.Normal
|
||||||
})
|
})
|
||||||
.fontSize(13)
|
.fontSize(13)
|
||||||
.fontColor($r('app.color.brand_text'))
|
.fontColor($r('app.color.brand_text'))
|
||||||
|
|||||||
Reference in New Issue
Block a user