|
|
|
@ -2671,4 +2671,491 @@ function defineReactive$$1 (
|
|
|
|
|
? provide.call(vm)
|
|
|
|
|
: provide;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 运行时辅助函数,用于将 v-bind="object" 合并到 VNode 的数据中
|
|
|
|
|
function bindObjectProps (
|
|
|
|
|
// VNode 的数据对象
|
|
|
|
|
data,
|
|
|
|
|
// 标签名
|
|
|
|
|
tag,
|
|
|
|
|
// v-bind 绑定的对象值
|
|
|
|
|
value,
|
|
|
|
|
// 是否作为 DOM 属性绑定
|
|
|
|
|
asProp,
|
|
|
|
|
// 是否为同步绑定
|
|
|
|
|
isSync
|
|
|
|
|
) {
|
|
|
|
|
// 如果绑定的值存在
|
|
|
|
|
if (value) {
|
|
|
|
|
// 如果绑定的值不是对象类型
|
|
|
|
|
if (!isObject(value)) {
|
|
|
|
|
// 发出警告,提示 v-bind 无参数时需要对象或数组值
|
|
|
|
|
warn(
|
|
|
|
|
'v-bind without argument expects an Object or Array value',
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 如果绑定的值是数组类型
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
// 将数组转换为对象
|
|
|
|
|
value = toObject(value);
|
|
|
|
|
}
|
|
|
|
|
// 用于存储属性的目标对象
|
|
|
|
|
var hash;
|
|
|
|
|
// 循环处理绑定对象的每个键
|
|
|
|
|
var loop = function ( key ) {
|
|
|
|
|
// 如果键是 'class'、'style' 或者是保留属性
|
|
|
|
|
if (
|
|
|
|
|
key === 'class' ||
|
|
|
|
|
key === 'style' ||
|
|
|
|
|
isReservedAttribute(key)
|
|
|
|
|
) {
|
|
|
|
|
// 目标对象为 data
|
|
|
|
|
hash = data;
|
|
|
|
|
} else {
|
|
|
|
|
// 获取 data 中的 type 属性
|
|
|
|
|
var type = data.attrs && data.attrs.type;
|
|
|
|
|
// 根据条件判断目标对象是 domProps 还是 attrs
|
|
|
|
|
hash = asProp || config.mustUseProp(tag, type, key)
|
|
|
|
|
? data.domProps || (data.domProps = {})
|
|
|
|
|
: data.attrs || (data.attrs = {});
|
|
|
|
|
}
|
|
|
|
|
// 将键转换为驼峰形式
|
|
|
|
|
var camelizedKey = camelize(key);
|
|
|
|
|
// 将键转换为连字符形式
|
|
|
|
|
var hyphenatedKey = hyphenate(key);
|
|
|
|
|
// 如果目标对象中不存在驼峰形式和连字符形式的键
|
|
|
|
|
if (!(camelizedKey in hash) && !(hyphenatedKey in hash)) {
|
|
|
|
|
// 将绑定对象中的值赋给目标对象
|
|
|
|
|
hash[key] = value[key];
|
|
|
|
|
|
|
|
|
|
// 如果是同步绑定
|
|
|
|
|
if (isSync) {
|
|
|
|
|
// 获取或创建 data 中的 on 对象
|
|
|
|
|
var on = data.on || (data.on = {});
|
|
|
|
|
// 为 update:key 事件添加处理函数
|
|
|
|
|
on[("update:" + key)] = function ($event) {
|
|
|
|
|
value[key] = $event;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 遍历绑定对象的每个键
|
|
|
|
|
for (var key in value) loop( key );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 返回处理后的 data 对象
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行时辅助函数,用于渲染静态树
|
|
|
|
|
function renderStatic (
|
|
|
|
|
// 静态树的索引
|
|
|
|
|
index,
|
|
|
|
|
// 是否在 v-for 指令内部
|
|
|
|
|
isInFor
|
|
|
|
|
) {
|
|
|
|
|
// 获取或创建静态树缓存数组
|
|
|
|
|
var cached = this._staticTrees || (this._staticTrees = []);
|
|
|
|
|
// 获取缓存中的静态树
|
|
|
|
|
var tree = cached[index];
|
|
|
|
|
// 如果已经渲染过静态树且不在 v-for 内部
|
|
|
|
|
if (tree && !isInFor) {
|
|
|
|
|
// 直接返回缓存的静态树
|
|
|
|
|
return tree
|
|
|
|
|
}
|
|
|
|
|
// 否则,重新渲染新的静态树
|
|
|
|
|
tree = cached[index] = this.$options.staticRenderFns[index].call(
|
|
|
|
|
// 渲染代理对象
|
|
|
|
|
this._renderProxy,
|
|
|
|
|
null,
|
|
|
|
|
// 用于函数式组件模板生成的渲染函数
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
// 标记静态树
|
|
|
|
|
markStatic(tree, ("__static__" + index), false);
|
|
|
|
|
// 返回渲染好的静态树
|
|
|
|
|
return tree
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 运行时辅助函数,用于 v-once 指令
|
|
|
|
|
// 实际上意味着用唯一的键将节点标记为静态
|
|
|
|
|
function markOnce (
|
|
|
|
|
// 要标记的 VNode 树
|
|
|
|
|
tree,
|
|
|
|
|
// 索引
|
|
|
|
|
index,
|
|
|
|
|
// 键
|
|
|
|
|
key
|
|
|
|
|
) {
|
|
|
|
|
// 标记静态节点
|
|
|
|
|
markStatic(tree, ("__once__" + index + (key ? ("_" + key) : "")), true);
|
|
|
|
|
// 返回标记后的树
|
|
|
|
|
return tree
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 标记静态节点或节点数组
|
|
|
|
|
function markStatic (
|
|
|
|
|
// 要标记的节点或节点数组
|
|
|
|
|
tree,
|
|
|
|
|
// 键
|
|
|
|
|
key,
|
|
|
|
|
// 是否为 v-once 节点
|
|
|
|
|
isOnce
|
|
|
|
|
) {
|
|
|
|
|
// 如果是节点数组
|
|
|
|
|
if (Array.isArray(tree)) {
|
|
|
|
|
// 遍历数组中的每个节点
|
|
|
|
|
for (var i = 0; i < tree.length; i++) {
|
|
|
|
|
// 如果节点存在且不是字符串类型
|
|
|
|
|
if (tree[i] && typeof tree[i] !== 'string') {
|
|
|
|
|
// 标记单个静态节点
|
|
|
|
|
markStaticNode(tree[i], (key + "_" + i), isOnce);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 标记单个静态节点
|
|
|
|
|
markStaticNode(tree, key, isOnce);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 标记单个静态节点
|
|
|
|
|
function markStaticNode (
|
|
|
|
|
// 要标记的节点
|
|
|
|
|
node,
|
|
|
|
|
// 键
|
|
|
|
|
key,
|
|
|
|
|
// 是否为 v-once 节点
|
|
|
|
|
isOnce
|
|
|
|
|
) {
|
|
|
|
|
// 标记节点为静态节点
|
|
|
|
|
node.isStatic = true;
|
|
|
|
|
// 设置节点的键
|
|
|
|
|
node.key = key;
|
|
|
|
|
// 标记节点是否为 v-once 节点
|
|
|
|
|
node.isOnce = isOnce;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将对象形式的监听器绑定到 VNode 的数据上
|
|
|
|
|
function bindObjectListeners (
|
|
|
|
|
// VNode 的数据对象
|
|
|
|
|
data,
|
|
|
|
|
// 监听器对象
|
|
|
|
|
value
|
|
|
|
|
) {
|
|
|
|
|
// 如果监听器对象存在
|
|
|
|
|
if (value) {
|
|
|
|
|
// 如果监听器对象不是普通对象
|
|
|
|
|
if (!isPlainObject(value)) {
|
|
|
|
|
// 发出警告,提示 v-on 无参数时需要对象值
|
|
|
|
|
warn(
|
|
|
|
|
'v-on without argument expects an Object value',
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 获取或创建 data 中的 on 对象
|
|
|
|
|
var on = data.on = data.on ? extend({}, data.on) : {};
|
|
|
|
|
// 遍历监听器对象的每个键
|
|
|
|
|
for (var key in value) {
|
|
|
|
|
// 获取已存在的监听器
|
|
|
|
|
var existing = on[key];
|
|
|
|
|
// 获取当前监听器
|
|
|
|
|
var ours = value[key];
|
|
|
|
|
// 如果已存在监听器,则将当前监听器添加到数组中,否则直接赋值
|
|
|
|
|
on[key] = existing ? [].concat(existing, ours) : ours;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 返回处理后的 data 对象
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 解析作用域插槽
|
|
|
|
|
function resolveScopedSlots (
|
|
|
|
|
// 作用域插槽函数数组
|
|
|
|
|
fns,
|
|
|
|
|
// 解析结果对象
|
|
|
|
|
res,
|
|
|
|
|
// 是否有动态键
|
|
|
|
|
hasDynamicKeys,
|
|
|
|
|
// 内容哈希键
|
|
|
|
|
contentHashKey
|
|
|
|
|
) {
|
|
|
|
|
// 如果结果对象不存在,则创建一个新的对象
|
|
|
|
|
res = res || { $stable: !hasDynamicKeys };
|
|
|
|
|
// 遍历作用域插槽函数数组
|
|
|
|
|
for (var i = 0; i < fns.length; i++) {
|
|
|
|
|
// 获取当前插槽
|
|
|
|
|
var slot = fns[i];
|
|
|
|
|
// 如果插槽是数组类型
|
|
|
|
|
if (Array.isArray(slot)) {
|
|
|
|
|
// 递归解析作用域插槽
|
|
|
|
|
resolveScopedSlots(slot, res, hasDynamicKeys);
|
|
|
|
|
} else if (slot) {
|
|
|
|
|
// 如果插槽有 proxy 标记
|
|
|
|
|
if (slot.proxy) {
|
|
|
|
|
// 设置插槽函数的 proxy 标记
|
|
|
|
|
slot.fn.proxy = true;
|
|
|
|
|
}
|
|
|
|
|
// 将插槽函数添加到结果对象中
|
|
|
|
|
res[slot.key] = slot.fn;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 如果有内容哈希键
|
|
|
|
|
if (contentHashKey) {
|
|
|
|
|
// 将内容哈希键添加到结果对象中
|
|
|
|
|
(res).$key = contentHashKey;
|
|
|
|
|
}
|
|
|
|
|
// 返回解析结果对象
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 绑定动态键值对到基础对象上
|
|
|
|
|
function bindDynamicKeys (
|
|
|
|
|
// 基础对象
|
|
|
|
|
baseObj,
|
|
|
|
|
// 动态键值对数组
|
|
|
|
|
values
|
|
|
|
|
) {
|
|
|
|
|
// 遍历动态键值对数组
|
|
|
|
|
for (var i = 0; i < values.length; i += 2) {
|
|
|
|
|
// 获取键
|
|
|
|
|
var key = values[i];
|
|
|
|
|
// 如果键是字符串且不为空
|
|
|
|
|
if (typeof key === 'string' && key) {
|
|
|
|
|
// 将键值对添加到基础对象上
|
|
|
|
|
baseObj[values[i]] = values[i + 1];
|
|
|
|
|
} else if (key !== '' && key !== null) {
|
|
|
|
|
// null 是用于显式移除绑定的特殊值
|
|
|
|
|
// 发出警告,提示动态指令参数值无效
|
|
|
|
|
warn(
|
|
|
|
|
("Invalid value for dynamic directive argument (expected string or null): " + key),
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 返回处理后的基础对象
|
|
|
|
|
return baseObj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助函数,用于动态地将修饰符运行时标记添加到事件名称前
|
|
|
|
|
// 确保仅在值已经是字符串时添加,否则会被转换为字符串并导致类型检查失败
|
|
|
|
|
function prependModifier (
|
|
|
|
|
// 要处理的值
|
|
|
|
|
value,
|
|
|
|
|
// 修饰符符号
|
|
|
|
|
symbol
|
|
|
|
|
) {
|
|
|
|
|
// 如果值是字符串类型,则在前面添加修饰符符号,否则直接返回值
|
|
|
|
|
return typeof value === 'string' ? symbol + value : value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 安装渲染辅助函数到目标对象上
|
|
|
|
|
function installRenderHelpers (
|
|
|
|
|
// 目标对象
|
|
|
|
|
target
|
|
|
|
|
) {
|
|
|
|
|
// 安装 markOnce 函数
|
|
|
|
|
target._o = markOnce;
|
|
|
|
|
// 安装 toNumber 函数
|
|
|
|
|
target._n = toNumber;
|
|
|
|
|
// 安装 toString 函数
|
|
|
|
|
target._s = toString;
|
|
|
|
|
// 安装 renderList 函数
|
|
|
|
|
target._l = renderList;
|
|
|
|
|
// 安装 renderSlot 函数
|
|
|
|
|
target._t = renderSlot;
|
|
|
|
|
// 安装 looseEqual 函数
|
|
|
|
|
target._q = looseEqual;
|
|
|
|
|
// 安装 looseIndexOf 函数
|
|
|
|
|
target._i = looseIndexOf;
|
|
|
|
|
// 安装 renderStatic 函数
|
|
|
|
|
target._m = renderStatic;
|
|
|
|
|
// 安装 resolveFilter 函数
|
|
|
|
|
target._f = resolveFilter;
|
|
|
|
|
// 安装 checkKeyCodes 函数
|
|
|
|
|
target._k = checkKeyCodes;
|
|
|
|
|
// 安装 bindObjectProps 函数
|
|
|
|
|
target._b = bindObjectProps;
|
|
|
|
|
// 安装 createTextVNode 函数
|
|
|
|
|
target._v = createTextVNode;
|
|
|
|
|
// 安装 createEmptyVNode 函数
|
|
|
|
|
target._e = createEmptyVNode;
|
|
|
|
|
// 安装 resolveScopedSlots 函数
|
|
|
|
|
target._u = resolveScopedSlots;
|
|
|
|
|
// 安装 bindObjectListeners 函数
|
|
|
|
|
target._g = bindObjectListeners;
|
|
|
|
|
// 安装 bindDynamicKeys 函数
|
|
|
|
|
target._d = bindDynamicKeys;
|
|
|
|
|
// 安装 prependModifier 函数
|
|
|
|
|
target._p = prependModifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 函数式渲染上下文类
|
|
|
|
|
function FunctionalRenderContext (
|
|
|
|
|
// VNode 的数据对象
|
|
|
|
|
data,
|
|
|
|
|
// 组件的 props
|
|
|
|
|
props,
|
|
|
|
|
// 子节点
|
|
|
|
|
children,
|
|
|
|
|
// 父组件实例
|
|
|
|
|
parent,
|
|
|
|
|
// 组件构造函数
|
|
|
|
|
Ctor
|
|
|
|
|
) {
|
|
|
|
|
var this$1 = this;
|
|
|
|
|
|
|
|
|
|
// 获取组件的选项
|
|
|
|
|
var options = Ctor.options;
|
|
|
|
|
// 确保函数式组件中的 createElement 函数有唯一的上下文
|
|
|
|
|
// 这对于正确的命名插槽检查是必要的
|
|
|
|
|
var contextVm;
|
|
|
|
|
// 如果父组件有 _uid 属性
|
|
|
|
|
if (hasOwn(parent, '_uid')) {
|
|
|
|
|
// 创建一个继承自父组件的对象
|
|
|
|
|
contextVm = Object.create(parent);
|
|
|
|
|
// 记录原始父组件
|
|
|
|
|
contextVm._original = parent;
|
|
|
|
|
} else {
|
|
|
|
|
// 传入的上下文 vm 也是一个函数式上下文
|
|
|
|
|
// 在这种情况下,我们要确保能够获取到真实的上下文实例
|
|
|
|
|
contextVm = parent;
|
|
|
|
|
// 获取原始父组件
|
|
|
|
|
parent = parent._original;
|
|
|
|
|
}
|
|
|
|
|
// 判断组件是否是编译后的
|
|
|
|
|
var isCompiled = isTrue(options._compiled);
|
|
|
|
|
// 判断是否需要规范化
|
|
|
|
|
var needNormalization = !isCompiled;
|
|
|
|
|
|
|
|
|
|
// 保存数据对象
|
|
|
|
|
this.data = data;
|
|
|
|
|
// 保存 props
|
|
|
|
|
this.props = props;
|
|
|
|
|
// 保存子节点
|
|
|
|
|
this.children = children;
|
|
|
|
|
// 保存父组件实例
|
|
|
|
|
this.parent = parent;
|
|
|
|
|
// 保存监听器
|
|
|
|
|
this.listeners = data.on || emptyObject;
|
|
|
|
|
// 解析注入
|
|
|
|
|
this.injections = resolveInject(options.inject, parent);
|
|
|
|
|
// 定义 slots 方法
|
|
|
|
|
this.slots = function () {
|
|
|
|
|
// 如果 $slots 不存在
|
|
|
|
|
if (!this$1.$slots) {
|
|
|
|
|
// 规范化作用域插槽
|
|
|
|
|
normalizeScopedSlots(
|
|
|
|
|
data.scopedSlots,
|
|
|
|
|
this$1.$slots = resolveSlots(children, parent)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// 返回 $slots
|
|
|
|
|
return this$1.$slots
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 定义 scopedSlots 属性
|
|
|
|
|
Object.defineProperty(this, 'scopedSlots', ({
|
|
|
|
|
// 可枚举
|
|
|
|
|
enumerable: true,
|
|
|
|
|
// 获取作用域插槽
|
|
|
|
|
get: function get () {
|
|
|
|
|
return normalizeScopedSlots(data.scopedSlots, this.slots())
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 支持编译后的函数式模板
|
|
|
|
|
if (isCompiled) {
|
|
|
|
|
// 暴露 $options 用于 renderStatic()
|
|
|
|
|
this.$options = options;
|
|
|
|
|
// 预解析插槽用于 renderSlot()
|
|
|
|
|
this.$slots = this.slots();
|
|
|
|
|
// 规范化作用域插槽
|
|
|
|
|
this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有作用域 ID
|
|
|
|
|
if (options._scopeId) {
|
|
|
|
|
// 创建带有作用域 ID 的 VNode
|
|
|
|
|
this._c = function (a, b, c, d) {
|
|
|
|
|
var vnode = createElement(contextVm, a, b, c, d, needNormalization);
|
|
|
|
|
if (vnode && !Array.isArray(vnode)) {
|
|
|
|
|
vnode.fnScopeId = options._scopeId;
|
|
|
|
|
vnode.fnContext = parent;
|
|
|
|
|
}
|
|
|
|
|
return vnode
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// 创建普通的 VNode
|
|
|
|
|
this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 安装渲染辅助函数到函数式渲染上下文的原型上
|
|
|
|
|
installRenderHelpers(FunctionalRenderContext.prototype);
|
|
|
|
|
|
|
|
|
|
// 创建函数式组件
|
|
|
|
|
function createFunctionalComponent (
|
|
|
|
|
// 组件构造函数
|
|
|
|
|
Ctor,
|
|
|
|
|
// props 数据
|
|
|
|
|
propsData,
|
|
|
|
|
// VNode 的数据对象
|
|
|
|
|
data,
|
|
|
|
|
// 上下文 vm
|
|
|
|
|
contextVm,
|
|
|
|
|
// 子节点
|
|
|
|
|
children
|
|
|
|
|
) {
|
|
|
|
|
// 获取组件的选项
|
|
|
|
|
var options = Ctor.options;
|
|
|
|
|
// 初始化 props 对象
|
|
|
|
|
var props = {};
|
|
|
|
|
// 获取组件的 props 选项
|
|
|
|
|
var propOptions = options.props;
|
|
|
|
|
// 如果 props 选项存在
|
|
|
|
|
if (isDef(propOptions)) {
|
|
|
|
|
// 遍历 props 选项的每个键
|
|
|
|
|
for (var key in propOptions) {
|
|
|
|
|
// 验证并设置 props 的值
|
|
|
|
|
props[key] = validateProp(key, propOptions, propsData || emptyObject);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果 data 中有 attrs 属性,合并到 props 中
|
|
|
|
|
if (isDef(data.attrs)) { mergeProps(props, data.attrs); }
|
|
|
|
|
// 如果 data 中有 props 属性,合并到 props 中
|
|
|
|
|
if (isDef(data.props)) { mergeProps(props, data.props); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建函数式渲染上下文
|
|
|
|
|
var renderContext = new FunctionalRenderContext(
|
|
|
|
|
data,
|
|
|
|
|
props,
|
|
|
|
|
children,
|
|
|
|
|
contextVm,
|
|
|
|
|
Ctor
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 调用组件的渲染函数
|
|
|
|
|
var vnode = options.render.call(null, renderContext._c, renderContext);
|
|
|
|
|
|
|
|
|
|
// 如果渲染结果是单个 VNode
|
|
|
|
|
if (vnode instanceof VNode) {
|
|
|
|
|
// 克隆并标记函数式组件的结果
|
|
|
|
|
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
|
|
|
|
|
} else if (Array.isArray(vnode)) {
|
|
|
|
|
// 规范化子节点
|
|
|
|
|
var vnodes = normalizeChildren(vnode) || [];
|
|
|
|
|
// 初始化结果数组
|
|
|
|
|
var res = new Array(vnodes.length);
|
|
|
|
|
// 遍历每个子节点
|
|
|
|
|
for (var i = 0; i < vnodes.length; i++) {
|
|
|
|
|
// 克隆并标记函数式组件的结果
|
|
|
|
|
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext);
|
|
|
|
|
}
|
|
|
|
|
// 返回结果数组
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
}
|