元数据 · 书写与读取
+元数据书写与读取模型
+作者想怎么写就怎么写,读取只认一个入口 metadata.xml。展平与导出只服务于网络传输和把模组信息放在一起,不是读取的前提。
三条结论
+-
+
-
+
只认一个入口
+
+MetadataReader.Read接收入口metadata.xml,其余文件由Include+ 递归发现。入口可以是本地路径、HTTP(S) 地址或自定义来源,没有第二个读取入口。
+ -
+
源树写法自由
+可以按 Mod、应用任意拆分,支持嵌套
+Include、Base继承、资源短 ID 和完整内联清单。读取器在内存里合成一棵规范树。
+ -
+
展平不是前提
+
+build与export把树合成单文件发布物,解析${TIMESTAMP}等变量,便于下载与聚合;源树本身同样能被 + Reader + 直接读取。
+
读取路径
+同一个 Reader 消费两种形态:作者维护的源树,以及构建导出的单文件发布物。展平只是把多份文件变成一份,不改变读取入口,也不改变读取语义。
++ Include 关系 + 资源 Source 引用 + 读取 / 构建路径 +
+-
+
- 源树 → Reader:
MetadataReader.Read("./Metadata/metadata.xml"),入口直接指向源根,不需要先构建。 +
+ - Output → Reader:
MetadataReader.Read("./Output/metadata.xml"),或由 +MetadataClient拉取发布物;展平后的单文件同样由一个入口读取。 +
+ - 两条路径产出同一个规范化模型,最终都由
MetadataClient与桌面端消费。
+
三种写法,同一个入口
+下面的写法都能被同一个入口读取。示例 ID 使用短名(Corona、icon、manifest-3258):资源的 scope 前缀由
+ Reader 与构建统一补齐,实体 ID 保持全仓库唯一的原名。子文件根节点同样写 <Metadata>;SchemaVersion 只要求写在入口根节点上。
+
入口只负责列出 Include;每个 Mod 或应用独占目录,内部文件继续按需拆分。
<!-- Metadata/metadata.xml -->
+ <Metadata SchemaVersion="1.0">
+ <Includes>
+ <Include Source="mods/mods.xml" />
+ <Include Source="apps/apps.xml" />
+ </Includes>
+ </Metadata>
+
+ <!-- Metadata/mods/mods.xml -->
+ <Metadata>
+ <Includes>
+ <Include Source="corona/corona.xml" />
+ </Includes>
+ </Metadata>
+
+ <!-- Metadata/mods/corona/corona.xml -->
+ <Metadata>
+ <Image ID="icon" Source="images/icon.png" />
+ <Manifest ID="manifest-3258" Source="manifests/3258.xml" />
+
+ <Mod ID="Corona">
+ <CurrentVersion>3.258</CurrentVersion>
+ <Icon>icon</Icon>
+ <DisplayName Language="zh">日冕</DisplayName>
+ <DisplayName Language="en">Corona</DisplayName>
+ <Packages>
+ <Package Version="3.258">
+ <Manifest>manifest-3258</Manifest>
+ </Package>
+ </Packages>
+ </Mod>
+ </Metadata>
+ 全部内容放在入口文件里。资源 Source 相对当前文档解释,因此这里从源根算起。
<!-- Metadata/metadata.xml:没有 Include,所有节点平铺 -->
+ <Metadata SchemaVersion="1.0">
+ <Image ID="icon" Source="corona/images/icon.png" />
+ <Manifest ID="manifest-3258" Source="corona/manifests/3258.xml" />
+
+ <Mod ID="Corona">
+ <CurrentVersion>3.258</CurrentVersion>
+ <Icon>icon</Icon>
+ <DisplayName Language="zh">日冕</DisplayName>
+ <DisplayName Language="en">Corona</DisplayName>
+ <Packages>
+ <Package Version="3.258">
+ <Manifest>manifest-3258</Manifest>
+ </Package>
+ </Packages>
+ </Mod>
+ </Metadata>
+ 完整清单直接写在 Manifest 里(无 Source),外部清单只写
+ Source。两种形式可以共存,Package 里都只写短名。
+
<!-- Metadata/metadata.xml -->
+ <Metadata SchemaVersion="1.0">
+ <Includes>
+ <Include Source="mods/corona/corona.xml" />
+ </Includes>
+ </Metadata>
+
+ <!-- Metadata/mods/corona/corona.xml -->
+ <Metadata>
+ <Image ID="icon" Source="images/icon.png" />
+
+ <!-- 完整内联清单:没有 Source,随 Include 一起保留 -->
+ <Manifest ID="manifest-3258" HashAlgorithm="None">
+ <File Mount="base">
+ <FileName>Corona.big</FileName>
+ <RelativePath>Corona</RelativePath>
+ <KindOf>big</KindOf>
+ </File>
+ </Manifest>
+
+ <!-- 外部清单:有 Source,只是引用 -->
+ <Manifest ID="manifest-3300" Source="manifests/3300.xml" />
+
+ <Mod ID="Corona">
+ <CurrentVersion>3.258</CurrentVersion>
+ <Icon>icon</Icon>
+ <DisplayName Language="zh">日冕</DisplayName>
+ <DisplayName Language="en">Corona</DisplayName>
+ <Packages>
+ <Package Version="3.258">
+ <Manifest>manifest-3258</Manifest>
+ </Package>
+ <Package Version="3.300">
+ <Manifest>manifest-3300</Manifest>
+ </Package>
+ </Packages>
+ </Mod>
+ </Metadata>
+ 版本契约
+SchemaVersion 是作者声明的数据契约,构建不再替你注入,也不写任何修订标记。
| 字段 | +写在哪 | +谁写入 | +缺失或版本不符 | +
|---|---|---|---|
SchemaVersion="1.0" |
+ 源根入口 metadata.xml 的根节点 |
+ 作者手写;构建不再注入 | +构建直接失败;MetadataClient 拒读该根清单 |
+
MetadataSchema.IsCompatible 只接受当前版本
+ 1.0。版本提高意味着旧客户端可能无法工作;改样式、加可选字段、改默认值都不提高版本。缺 SchemaVersion 时构建的报错文本是「源根 metadata.xml
+ 的根节点缺少 SchemaVersion 属性」。
+
读取细节
+-
+
- 递归捕获 Include。入口加载后按
Include@Source继续递归;路径相对当前文件。入口与Include都可以是 + HTTP(S) 地址,但 HTTP 文档不能引用本地文件。
+ - 菱形 Include 只展开一次。同一份文档只捕获一次,内容只贡献一次;循环引用直接报错,不做静默跳过。 +
- 路径限制在源根内。入口、
Include与资源Source都必须落在 +SourceRoot(缺省为入口所在目录)内,越界抛InvalidOperationException。 +
+ - 缺
SchemaVersion或入口不存在就直接报错。构建在源根硬失败;客户端对缺SchemaVersion+ 的根清单报「根清单缺少 SchemaVersion」并拒读;入口文件不存在抛FileNotFoundException。没有默认版本注入,也没有兜底路径。
+ - 读取只在内存中合并。Reader 合并
Include、继承与资源引用,不写盘、不替换变量、不转换图片,也不改写已声明的 +SchemaVersion。 +
+ - 引用校验是显式选项。
MetadataSourceOptions.ValidateReferences开启后才检查断引用;默认关闭。
+
为什么这样设计
+作者侧不规定文件数量、嵌套层数、短名还是完整 ID。拆成多少文件、要不要内联清单,都是作者的编辑选择,不构成读取契约的一部分。
+客户端与构建共用同一个
+ MetadataReader:构建读源树,客户端读的是同一个入口——源树或展平产物都行,展平只是同一张图的另一种布局。这样就不存在「源树一套语义、发布物另一套语义」的分叉,也不会出现必须先构建才能读取的隐含规则。
+