stanza-extend
Version:
Modern XMPP in the browser, with a JSON API
434 lines (421 loc) • 13.8 kB
JavaScript
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
const Types_1 = require('./Types')
class Translator {
constructor() {
this.parents = new Set()
this.placeholder = false
this.typeField = ''
this.versionField = ''
this.defaultType = ''
this.defaultVersion = ''
this.languageField = 'lang'
this.typeValues = new Map()
this.typeOrders = new Map()
this.importers = new Map()
this.exporters = new Map()
this.children = new Map()
this.childrenIndex = new Map()
this.implicitChildren = new Set()
this.contexts = new Map()
}
addChild(name, translator, multiple = false, selector, implicit) {
const child = {
multiple: multiple || false,
name,
selector,
translator,
}
const existingChild = this.children.get(name)
if (!existingChild) {
child.translator.parents.add(this)
this.children.set(name, child)
for (const [xid] of translator.importers) {
if (!this.implicitChildren.has(xid)) {
this.childrenIndex.set(xid, name)
}
}
if (implicit) {
this.implicitChildren.add(implicit)
}
return
}
const existing = existingChild.translator
existingChild.multiple = multiple
if (selector && existingChild.selector && selector !== existingChild.selector) {
existingChild.selector = undefined
}
for (const [xid, importer] of translator.importers) {
const [type, version] = (existing.typeValues.get(xid) || '').split('__v__')
existing.updateDefinition({
contexts: translator.contexts,
element: importer.element,
exporterOrdering: new Map(),
exporters: new Map(),
importerOrdering: importer.fieldOrders,
importers: importer.fields,
namespace: importer.namespace,
optionalNamespaces: new Map(),
type,
version,
})
if (!this.implicitChildren.has(xid)) {
this.childrenIndex.set(xid, name)
}
}
for (const [exportType, exporter] of translator.exporters) {
const [type, version] = exportType.split('__v__')
existing.updateDefinition({
contexts: translator.contexts,
element: exporter.element,
exporterOrdering: exporter.fieldOrders,
exporters: exporter.fields,
importerOrdering: new Map(),
importers: new Map(),
namespace: exporter.namespace,
optionalNamespaces: exporter.optionalNamespaces,
type,
version,
})
}
}
addContext(path, selector, field, xid, value, implied) {
if (selector) {
path = `${path}[${selector}]`
}
let context = this.contexts.get(path)
if (!context) {
context = {
typeField: '',
versionField: '',
typeValues: new Map(),
}
}
if (implied) {
context.impliedType = value
}
context.typeField = field || ''
context.typeValues.set(xid, value)
this.contexts.set(path, context)
}
getChild(name) {
const child = this.children.get(name)
if (!child) {
return
}
return child.translator
}
getImportKey(xml) {
return this.childrenIndex.get(`{${xml.getNamespace()}}${xml.getName()}`)
}
updateDefinition(opts) {
const xid = `{${opts.namespace}}${opts.element}`
const type = opts.type || this.defaultType
const version = opts.version || this.defaultVersion
const versionType = version ? `${type}__v__${version}` : type
const importer = this.importers.get(xid) ||
{
element: opts.element,
fieldOrders: new Map(),
fields: new Map(),
namespace: opts.namespace,
}
for (const [fieldName, fieldImporter] of opts.importers) {
importer.fields.set(fieldName, fieldImporter)
}
for (const [fieldName, order] of opts.importerOrdering) {
importer.fieldOrders.set(fieldName, order)
}
this.importers.set(xid, importer)
const exporter = this.exporters.get(versionType) ||
{
element: opts.element,
fieldOrders: new Map(),
fields: new Map(),
namespace: opts.namespace,
optionalNamespaces: opts.optionalNamespaces,
}
for (const [fieldName, fieldExporter] of opts.exporters) {
exporter.fields.set(fieldName, fieldExporter)
}
for (const [name, order] of opts.exporterOrdering) {
exporter.fieldOrders.set(name, order)
}
for (const [prefix, namespace] of opts.optionalNamespaces) {
exporter.optionalNamespaces.set(prefix, namespace)
}
this.exporters.set(versionType, exporter)
for (const [path, newContext] of opts.contexts) {
const context = this.contexts.get(path) || {
impliedType: undefined,
typeField: newContext.typeField,
versionField: newContext.versionField,
typeValues: new Map(),
}
if (!context.typeField) {
context.typeField = newContext.typeField
}
if (!context.versionField) {
context.versionField = newContext.versionField
}
if (!context.impliedType) {
context.impliedType = newContext.impliedType
}
for (const [xid2, type] of newContext.typeValues) {
context.typeValues.set(xid2, type)
}
this.contexts.set(path, context)
}
if (opts.type) {
this.typeValues.set(xid, versionType)
if (opts.typeOrder && opts.type) {
this.typeOrders.set(opts.type, opts.typeOrder)
}
} else if (this.typeField && !opts.type) {
for (const [, imp] of this.importers) {
for (const [fieldName, fieldImporter] of opts.importers) {
imp.fields.set(fieldName, fieldImporter)
}
for (const [fieldName, order] of opts.importerOrdering) {
imp.fieldOrders.set(fieldName, order)
}
}
for (const [, exp] of this.exporters) {
for (const [fieldName, fieldExporter] of opts.exporters) {
exp.fields.set(fieldName, fieldExporter)
}
for (const [fieldName, order] of opts.exporterOrdering) {
exp.fieldOrders.set(fieldName, order)
}
}
}
}
replaceWith(replacement) {
for (const [a, b] of this.children) {
replacement.children.set(a, b)
}
for (const [a, b] of this.childrenIndex) {
replacement.childrenIndex.set(a, b)
}
for (const [a, b] of this.contexts) {
replacement.contexts.set(a, b)
}
for (const a of this.implicitChildren) {
replacement.implicitChildren.add(a)
}
for (const parent of this.parents) {
for (const child of parent.children.values()) {
if (child.translator === this) {
child.translator = replacement
}
}
}
this.parents = new Set()
}
import(xml, parentContext) {
const xid = `{${xml.getNamespace()}}${xml.getName()}`
const output = {}
const importer = this.importers.get(xid)
if (!importer) {
return
}
const versionTypeValue = this.typeValues.get(xid) || ''
const [typeValue, versionValue] = versionTypeValue.split('__v__')
const path = parentContext.path || ''
let implied
if (parentContext.pathSelector) {
implied = this.contexts.get(`${path}[${parentContext.pathSelector}]`)
}
if (!implied) {
implied = this.contexts.get(path)
}
if (implied) {
if (!implied.impliedType) {
const impliedTypeValue = implied.typeValues.get(xid) || ''
if (impliedTypeValue) {
output[implied.typeField] = impliedTypeValue
}
}
} else if (this.typeField && typeValue && typeValue !== this.defaultType) {
output[this.typeField] = typeValue
}
if (this.versionField && versionValue && versionValue !== this.defaultVersion) {
output[this.versionField] = versionValue
}
const context = {
...parentContext,
data: output,
importer,
lang: (xml.getAttribute('xml:lang') || parentContext.lang || '').toLowerCase(),
pathSelector: typeValue,
translator: this,
}
const importFields = [...importer.fieldOrders.entries()].sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0)
const preChildren = importFields.filter(field => field[1] >= 0)
const postChildren = importFields.filter(field => field[1] < 0)
for (const [fieldName] of preChildren) {
const importField = importer.fields.get(fieldName)
context.path = `${parentContext.path}.${fieldName}`
const value = importField(xml, context)
if (value !== null && value !== undefined) {
output[fieldName] = value
}
}
for (const child of xml.children) {
if (typeof child === 'string') {
continue
}
const childName = `{${child.getNamespace()}}${child.getName()}`
const fieldName = this.childrenIndex.get(childName)
if (!fieldName) {
continue
}
context.path = `${parentContext.path}.${fieldName}`
const { translator, multiple, selector } = this.children.get(fieldName)
if (!selector || selector === typeValue) {
const childOutput = translator.import(child, context)
if (childOutput !== undefined) {
if (multiple) {
if (!output[fieldName]) {
output[fieldName] = []
}
output[fieldName].push(childOutput)
} else if (!output[fieldName]) {
output[fieldName] = childOutput
} else {
output[fieldName] = translator.resolveCollision(output[fieldName], childOutput)
}
}
}
}
for (const [fieldName] of postChildren) {
const importField = importer.fields.get(fieldName)
context.path = `${parentContext.path}.${fieldName}`
const value = importField(xml, context)
if (value !== null && value !== undefined) {
output[fieldName] = value
}
}
return output
}
export(data, parentContext) {
if (!data) {
return
}
let exportType = this.defaultType
let exportVersion = this.defaultVersion
const path = parentContext.path || ''
let implied
if (parentContext.pathSelector) {
implied = this.contexts.get(`${path}[${parentContext.pathSelector}]`)
}
if (!implied) {
implied = this.contexts.get(path)
}
if (implied) {
exportType = implied.impliedType || data[implied.typeField] || this.defaultType
} else if (this.typeField) {
exportType = data[this.typeField] || this.defaultType
}
if (this.versionField) {
exportVersion = data[this.versionField] || this.defaultVersion
}
//yjing modify
switch (path){
case 'iq.createGroupStanza':
exportVersion = 'createGroupStanza'
break
case 'iq.groupAffiliationIQ':
exportVersion = 'groupAffiliationIQ'
break
}
//yjing modify end
let exportVersionType = exportVersion ? `${exportType}__v__${exportVersion}` : exportType
//yjing modify
if(data['customFieldName']){
exportVersionType = data['customFieldName']
}
//yjing modify end
const exporter = this.exporters.get(exportVersionType)
if (!exporter) {
return
}
//yjing modify start
let namespace = exporter.namespace
if(data['heartIQ']){
namespace = ''
}
const output = Types_1.createElement(namespace, exporter.element, parentContext.namespace, parentContext.element)
//yjing modify end
if (parentContext.element) {
output.parent = parentContext.element
}
for (const [prefix, namespace] of exporter.optionalNamespaces) {
output.addOptionalNamespace(prefix, namespace)
}
const context = {
...parentContext,
data,
element: output,
exporter,
lang: (data[this.languageField] || parentContext.lang || '').toLowerCase(),
namespace: output.getDefaultNamespace(),
pathSelector: exportType,
translator: this,
}
const langExporter = exporter.fields.get(this.languageField)
if (langExporter) {
langExporter(output, data[this.languageField], parentContext)
}
const keys = Object.keys(data)
keys.sort((key1, key2) => {
const a = exporter.fieldOrders.get(key1) || 100000
const b = exporter.fieldOrders.get(key2) || 100000
return a - b
})
for (const key of keys) {
if (key === this.languageField) {
// We've already processed this field
continue
}
const value = data[key]
const fieldExporter = exporter.fields.get(key)
if (fieldExporter) {
fieldExporter(output, value, context)
continue
}
const childTranslator = this.children.get(key)
if (!childTranslator) {
continue
}
context.path = `${parentContext.path ? parentContext.path + '.' : ''}${key}`
const { translator, multiple, selector } = childTranslator
if (!selector || selector === exportType) {
let items
if (multiple) {
items = value
} else {
items = [value]
}
for (const item of items) {
const childOutput = translator.export(item, context)
if (childOutput) {
output.appendChild(childOutput)
}
}
}
}
//yjing modify begin
if(output){
output.removeChild("ignoreProperties")
}
//yjing modify end
return output
}
resolveCollision(existingData, newData) {
const existingOrder = this.typeOrders.get(existingData[this.typeField] || this.defaultType) || 0
const newOrder = this.typeOrders.get(newData[this.typeField] || this.defaultType) || 0
return existingOrder <= newOrder ? existingData : newData
}
}
exports.default = Translator