diff --git a/domain_resource/src/main/ets/resource/VersionXml.ets b/domain_resource/src/main/ets/resource/VersionXml.ets index 5d9c916..32e2839 100644 --- a/domain_resource/src/main/ets/resource/VersionXml.ets +++ b/domain_resource/src/main/ets/resource/VersionXml.ets @@ -1,15 +1,15 @@ /** - * version.xml 解析(契约 §5.3,框架 §8.3)。用 @kit.ArkTS xml.XmlPullParser。 + * version.xml 解析(契约 §5.3,框架 §8.3)。 * * 结构: * 用途:取 与远程 game_version 比较决定是否更新;取子 - * 作 app_data 的 app_gamename。`value` 为 version 标签独有;`name` 多标签共有,故按 - * 所属标签(当前为子 game)区分抓取。 + * 作 app_data 的 app_gamename;取子 作大厅 gameid 为空时的回退匹配 key。 + * + * 实现说明:早期用 @kit.ArkTS xml.XmlPullParser,实测其 attributeValueCallbackFunction 对 + * 本文件 `` 的 `value` 属性**不稳定触发**(同文件 `` 却能取到), + * 导致 version 恒为 0、子游戏每次都误判需更新而重复下载。改用确定性正则直接从原文抽属性, + * 语义等价于 Android `pullxml1`,规避解析器回调时序/属性触发的不确定性。 */ -import { xml, util } from '@kit.ArkTS'; -import { Logger } from 'common'; - -/** version.xml 解析结果。 */ export interface VersionXmlInfo { /** 整数(缺失/失败 → 0)。 */ version: number; @@ -20,46 +20,36 @@ export interface VersionXmlInfo { } export class VersionXml { - private static readonly log: Logger = Logger.tag('VersionXml'); + // :取版本号。允许属性间任意顺序/空白。 + private static readonly VERSION_RE: RegExp = new RegExp(']*\\bvalue\\s*=\\s*"([^"]*)"', 'i'); + // 子 ...>:根 无属性(其后紧跟 '>'),故 \s+ 自然跳过,只命中带属性的子 game。 + private static readonly GAME_TAG_RE: RegExp = new RegExp(']*?>', 'i'); + private static readonly ID_RE: RegExp = new RegExp('\\bid\\s*=\\s*"([^"]*)"', 'i'); + private static readonly NAME_RE: RegExp = new RegExp('\\bname\\s*=\\s*"([^"]*)"', 'i'); - /** 解析 version + gameName。属性回调紧随其开始标签,故用 currentTag 把 name 归属到 game 标签。 */ + /** 解析 version + gameName + gameId(任一缺失取默认值,绝不抛错)。 */ static parse(xmlText: string): VersionXmlInfo { const info: VersionXmlInfo = { version: 0, gameName: '', gameId: '' }; if (xmlText.length === 0) { return info; } - let value: string = ''; - let currentTag: string = ''; - try { - const buf: Uint8Array = new util.TextEncoder().encodeInto(xmlText); - const parser: xml.XmlPullParser = new xml.XmlPullParser(buf.buffer as object as ArrayBuffer, 'UTF-8'); - const options: xml.ParseOptions = { - ignoreNameSpace: true, - tagValueCallbackFunction: (name: string, _val: string): boolean => { - currentTag = name; - return true; - }, - attributeValueCallbackFunction: (name: string, val: string): boolean => { - if (name === 'value') { - value = val; - } else if (name === 'name' && currentTag === 'game') { - // 根 无 name 属性;只有子 触发,正是所需游戏名 - info.gameName = val; - } else if (name === 'id' && currentTag === 'game') { - // 子 的 id:大厅 gameid 回退匹配 key(根 无 id 属性) - info.gameId = val; - } - return true; - }, - }; - parser.parseXml(options); - } catch (e) { - const err = e as Error; - VersionXml.log.w(`parse version.xml failed: ${err.message}`); - return info; + const verMatch: RegExpMatchArray | null = xmlText.match(VersionXml.VERSION_RE); + if (verMatch !== null) { + const n: number = parseInt(verMatch[1], 10); + info.version = isNaN(n) ? 0 : n; + } + const gameMatch: RegExpMatchArray | null = xmlText.match(VersionXml.GAME_TAG_RE); + if (gameMatch !== null) { + const tag: string = gameMatch[0]; + const idMatch: RegExpMatchArray | null = tag.match(VersionXml.ID_RE); + if (idMatch !== null) { + info.gameId = idMatch[1]; + } + const nameMatch: RegExpMatchArray | null = tag.match(VersionXml.NAME_RE); + if (nameMatch !== null) { + info.gameName = nameMatch[1]; + } } - const n: number = parseInt(value, 10); - info.version = isNaN(n) ? 0 : n; return info; }