十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入掌握 @typespec/xml 装饰器:从属性映射到 XML 命名空间的完整实战指南

深入掌握 @typespec/xml 装饰器:从属性映射到 XML 命名空间的完整实战指南 深入掌握 typespec/xml 装饰器从属性映射到 XML 命名空间的完整实战指南【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec导读typespec/xml是 TypeSpec 官方提供的 XML 编码库它通过一组精炼的装饰器Decorator控制 TypeSpec 模型在序列化为 XML 时的表现形态属性是编码为 XML 特性attribute还是子节点node、元素与属性的最终名称、命名空间namespace与前缀prefix如何声明以及列表/文本内容是否需要包装节点。本文以官方参考文档 decorators.md 为核心骨架结合typespec/xml包的源码实现与测试用例逐一拆解 5 个装饰器的签名、约束、底层原理与实战用法读完即可在自己的 TypeSpec 定义中精确控制 XML 输出结构。一、装饰器总览typespec/xml通过main.tsp见 main.tsp聚合导入types.tsp与decorators.tsp对外暴露全部装饰器。其 TypeScript 侧的声明与实现分别位于 decorators.tspTypeSpec 声明与 decorators.ts运行时实现。官方参考文档共收录 5 个装饰器汇总如下装饰器目标类型Target参数作用TypeSpec.Xml.nameunknown任意类型name: valueof string指定 XML 元素/特性的名称等价于encodedName(application/xml, value)TypeSpec.Xml.attributeModelProperty无将目标属性编码为 XML 特性而非子节点TypeSpec.Xml.unwrappedModelProperty无不为目标属性创建包装节点用于扁平化数组或内联原始文本TypeSpec.Xml.nsunknownns: string \| EnumMemberprefix?: valueof string为元素指定 XML 命名空间与前缀TypeSpec.Xml.nsDeclarationsEnum无标记某个枚举为 XML 命名空间声明表配合ns使用在 TypeSpec 源文件中装饰器既可以写全限定名TypeSpec.Xml.attribute也可以在使用using TypeSpec.Xml;导入后简写为attribute。下文示例沿用参考文档与官方测试中的简写形式。二、name精确控制 XML 元素与特性名称2.1 签名与语义TypeSpec.Xml.name(name: valueof string)name的语义与encodedName(application/xml, value)完全等价即声明该类型在 XML 编码下的最终名称。它的目标Target是unknown意味着可以作用在模型、模型属性、标量等任意类型上。官方参考文档中的完整示例name(XmlBook) model Book { name(XmlId) id: string; encodedName(application/xml, XmlName) name: string; content: string; }序列化结果XmlBook XmlIdstring/XmlId XmlNamestring/XmlName contentstring/content /XmlBook注意示例中id使用name(XmlId)、name使用encodedName(application/xml, XmlName)二者产出完全相同的 XML 元素名直观印证了两者的等价性未标注的content则保留原始属性名。2.2 源码实现一行转调name的实现非常简洁——它本质上是标准encodedName装饰器在application/xml媒体类型上的语法糖。见 decorators.tsexport const $name: NameDecorator (context, target, name) { context.call($encodedName, target, application/xml, name); };2.3 测试验证多目标支持官方测试 decorators.test.ts 使用it.each参数化用例分别验证name作用于模型、模型属性与标量三种目标时resolveEncodedName(program, type, application/xml)均能解析出指定的XmlName确认了其Target: unknown的通用性it.each([ [model, test Xml.name(XmlName) model Blob {}], [model prop, model Blob {Xml.name(XmlName) test title:string}], [scalar, Xml.name(XmlName) test scalar Blob extends string;], ])(%s, async (_, code) { const result await runner.compile(t.code${code}); const curr (result.Blob || result.title) as Model; expect(resolveEncodedName(runner.program, curr, application/xml)).toEqual(XmlName); });三、attribute把属性变成 XML 特性3.1 默认行为 vs 特性编码XML 中存在两种承载数据的方式元素子节点node与特性attribute。默认情况下模型属性会被编码为子节点。参考文档给出的对照示例默认编码model Blob { id: string; }Blob idabcdef/id /Blob使用attribute之后model Blob { attribute id: string; }Blob idabcdef /Blob同一份数据前者是Blob下的id子节点后者则是Blob标签上的idabcdef特性XML 文档结构完全不同。3.2 目标与约束TargetModelProperty只能作用于模型属性Parameters无参数互斥约束参考文档明确指出它不能与unwrapped同时使用详见下文第 4 节。3.3 源码实现状态标记attribute的实现是把目标属性登记进编译器的状态集合state set。见 decorators.tsexport const $attribute: AttributeDecorator (context, target) { context.program.stateSet(XmlStateKeys.attribute).add(target); }; /** Check if the given property should be serialized as an attribute instead of a node. */ export function isAttribute(program: Program, target: ModelProperty): boolean { return program.stateSet(XmlStateKeys.attribute).has(target); }配套导出的isAttribute()查询函数会被序列化器各语言 emitter在输出阶段调用判断某个属性应当渲染为特性。状态键attribute在库定义 lib.ts 中登记描述为 Mark a model property to be serialized as xml attribute。3.4 测试验证decorators.test.ts 验证被Xml.attribute标注的属性isAttribute(...)返回true未标注的返回false。四、unwrapped去掉包装节点4.1 作用与互斥约束unwrapped指定目标属性不创建包装节点常用于两种场景扁平化数组节点让数组元素直接成为外层模型节点的子节点内联原始文本让字符串内容直接落在模型节点内而不是嵌套一层子节点。参考文档明确它不能与attribute同时使用一个属性要么是特性要么去掉包装两者语义冲突。4.2 数组属性默认 vs 解包默认情况下数组属性会产生一层ItemsTags之类的包装节点包装节点名称由数组属性名决定model Pet { tags: Tag[]; }XmlPet ItemsTags XmlTag namestring/name /XmlTag /ItemsTags /XmlPet加上unwrapped后包装节点被移除Tag元素直接挂在XmlPet下model Pet { unwrapped tags: Tag[]; }XmlPet XmlTag namestring/name /XmlTag /XmlPet4.3 字符串属性默认 vs 内联文本对于字符串属性默认会生成一层内容节点model BlobName { content: string; }BlobName content abcdef /content /BlobName使用unwrapped后字符串内容直接成为模型节点的文本内容model BlobName { unwrapped content: string; }BlobName abcdef /BlobName4.4 源码实现与测试与attribute对称unwrapped同样基于状态集合实现见 decorators.tsexport const $unwrapped: UnwrappedDecorator (context, target) { context.program.stateSet(XmlStateKeys.unwrapped).add(target); }; /** Check if the given property should be unwrapped in the XML containing node. */ export function isUnwrapped(program: Program, target: ModelProperty): boolean { return program.stateSet(XmlStateKeys.unwrapped).has(target); }测试 decorators.test.ts 验证了isUnwrapped()对标注/未标注属性的判别。库定义中状态键描述为 Mark a model property to be serialized without a node wrapping the content见 lib.ts。五、ns与nsDeclarationsXML 命名空间体系XML 命名空间是跨文档共享元素定义的基础设施typespec/xml提供了两套声明方式。5.1ns的两种用法签名如下参考文档原样TypeSpec.Xml.ns(ns: string | EnumMember, prefix?: valueof string)参数类型说明nsstring \| EnumMember命名空间 URI或一个被nsDeclaration装饰的枚举的成员prefixvalueof string命名空间前缀当ns以字符串传入时为必填用法一字符串 URI 前缀ns(https://example.com/ns1, ns1) model Foo { ns(https://example.com/ns1, ns1) bar: string; ns(https://example.com/ns2, ns2) bar: string; }用法二引用nsDeclarations枚举成员Xml.nsDeclarations enum Namespaces { ns1: https://example.com/ns1, ns2: https://example.com/ns2, } Xml.ns(Namespaces.ns1) model Foo { Xml.ns(Namespaces.ns1) bar: string; Xml.ns(Namespaces.ns2) bar: string; }采用枚举方式时前缀自动取枚举成员名如ns1、ns2无需也不能再手动传前缀。5.2nsDeclarations命名空间声明表TypeSpec.Xml.nsDeclarationsTargetEnumParameters无作用将枚举标记为 XML 命名空间声明表。枚举成员的值必须是命名空间 URI 字符串成员名即前缀。5.3 源码实现与 5 类诊断规则ns的参数处理集中在 decorators.ts 的getData()中其分支逻辑与错误校验清晰对应着参考文档的约束字符串 URI 必须携带前缀——缺失时抛出ns-missing-prefix错误When using a string namespace you must provide a prefix as the 2nd argument.枚举成员必须来自nsDeclarations枚举——否则抛出ns-enum-not-declarationEnum member used as namespace must be part of an enum marked with nsDeclaration.枚举成员值必须是字符串 URI——值为空或数字时抛出invalid-ns-declaration-memberEnum membernamemust have a value that is the XML namespace url.枚举方式禁止再传前缀——抛出prefix-not-allowedns decorator cannot have the prefix parameter set when using an enum member.命名空间必须是合法 URI——通过new URL(namespace)校验见 validateNamespaceIsUri非法时抛出ns-not-uriNamespacenamespaceis not a valid URI.。上述诊断信息全部登记在库定义 lib.ts 中错误码格式为typespec/xml/code。解析成功后命名空间以{ namespace, prefix }结构存入状态映射state map数据模型即 types.ts 中的XmlNamespace接口export interface XmlNamespace { /** Namespace name */ readonly namespace: string; /** Namespace prefix */ readonly prefix: string; }外部查询通过getNs(program, target)获取见 decorators.ts。5.4 测试验证命名空间不向子级传递decorators.test.ts 对ns进行了全面覆盖值得注意的用例包括字符串方式与枚举方式都能正确解析出{ namespace, prefix }命名空间不会自动向子级传递在Blob模型上标注ns其属性id查询getNs返回undefined说明命名空间需要逐级显式声明上述 5 类错误场景均有对应诊断测试例如缺少第二参、给枚举方式传前缀、命名空间不是合法 URL 等。六、综合实战示例将 5 个装饰器组合使用可以得到一份完整的 XML 映射模型综合参考文档示例整理using TypeSpec.Xml; nsDeclarations enum Namespaces { storage: https://example.com/storage, } name(XmlBook) ns(Namespaces.storage) model Book { name(XmlId) attribute id: string; name(XmlName) name: string; unwrapped tags: string[]; unwrapped content: string; }对应输出 XML 的形态如下示意XmlBook xmlns:storagehttps://example.com/storage storage:XmlIdstring storage:XmlNamestring/storage:XmlName stringtag1/string stringtag2/string string /XmlBook要点回顾name负责元素/特性命名attribute让id成为特性nsnsDeclarations声明命名空间与前缀unwrapped分别将数组元素直接平铺、将文本内容内联进模型节点。七、验证与运行方式仓库为typespec/xml提供了完整的单元测试核心位于 decorators.test.ts涵盖name、attribute、unwrapped、ns、nsDeclarations的全部行为与诊断分支。测试基于 test-host.ts 构建的Tester实例并使用typespec/compiler/testing提供的t测试辅助与expectDiagnostics断言工具。感兴趣的读者可以在仓库根目录执行对应包的测试命令参见 package.json进行本地验证。此外typespec/xml还定义了 XML 编码枚举TypeSpec.Xml.EncodingxmlDateTime、xmlDate、xmlTime、xmlDuration、xmlBase64Binary见 types.tsp配合编译器内置的encode装饰器使用相关编码实现位于 encoding.ts可作为理解该库完整能力的下一步阅读材料。结语typespec/xml的装饰器设计遵循少而精的原则name统一命名、attribute控制特性形态、unwrapped控制节点层级、ns/nsDeclarations管理命名空间。理解它们各自的 Target、参数与底层状态存储机制state set / state map以及 5 类编译期诊断规则就能在编写 TypeSpec 时精准预测 XML 输出为各语言 emitter 的正确序列化打下坚实基础。完整的签名、参数表与示例可随时查阅官方参考文档 decorators.md以及源码 decorators.ts 与测试 decorators.test.ts。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表