import SettingsManager from "../douban/setting/SettingsManager";
import {ArraySetting, DEFAULT_SETTINGS_ARRAY_NAME} from "../douban/setting/model/ArraySetting";
import StringUtil from "./StringUtil";
import YamlUtil from "./YamlUtil";
import {log} from "./Logutil";
import {i18nHelper} from "../lang/helper";
import {DataValueType, SupportType} from "../constant/Constsant";
import {DataField} from "./model/DataField";
import {FieldVariable} from "./model/FieldVariable";
import {CustomProperty} from "../douban/setting/model/CustomProperty";
import {FileUtil} from "./FileUtil";
import {ArrayLengthLimit} from "../douban/setting/model/ArrayLengthLimit";
type TargetType = 'text' | 'path' | 'yml_text';
export class VariableUtil {
/**
*
* @param obj
* @param content
* @param subjectType
* @param settingManager
* @param targetType
*/
static replaceSubject(obj: any, content: string, subjectType: SupportType, settingManager:SettingsManager, targetType: TargetType): string {
if (!content || !obj) {
return content;
}
const allVariables = this.getAllVariables(content, settingManager);
if (!allVariables || allVariables.length == 0) {
return content;
}
if (obj instanceof Map) {
this.applyArrayLengthLimits(subjectType, obj, settingManager);
this.handleCustomVariable(subjectType, obj, settingManager, 'text')
content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
}else {
const map = this.objToMap(obj);
this.applyArrayLengthLimits(subjectType, map, settingManager);
this.handleCustomVariable(subjectType, map, settingManager, 'text')
content = this.replaceMap(map, allVariables, content, settingManager, targetType);
}
return content;
}
/**
*
* @param obj
* @param content
* @param settingManager
* @param targetType
*/
static replace(obj: any, content: string, settingManager:SettingsManager, targetType : TargetType): string {
if (!content || !obj) {
return content;
}
const allVariables = this.getAllVariables(content, settingManager);
if (!allVariables || allVariables.length == 0) {
return content;
}
if (obj instanceof Map) {
content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
}else {
const map = this.objToMap(obj);
content = this.replaceMap(map, allVariables, content, settingManager, targetType); }
return content;
}
/**
*
type
* Number
* String
* Array
*
* name
* aliases
* @param variable
* @param value
* @param content
* @param settingManager
* @param targetType
*/
static replaceVariable(variable: FieldVariable, value: any, content: string, settingManager:SettingsManager, targetType: TargetType): string {
if (!content) {
return content;
}
//根据value的类型,替换对应的变量
if (value instanceof Array) {
content = this.replaceArray(variable, value, content, settingManager, targetType);
} else if(value instanceof DataField) {
content = this.replaceDataField(variable, value, content, settingManager, targetType);
} else {
content = this.replaceString(variable, value, value, content, settingManager, targetType);
}
return content;
}
static replaceArray(variable: FieldVariable, value: any[], content: string, settingManager:SettingsManager, targetType: TargetType): string {
if (!content) {
return content;
}
const variableStr = variable.variable;
const outTypeName = variable.outTypeName;
if (!value) {
return content.replaceAll(variableStr, "");
}
const arraySettings = this.getArraySetting(outTypeName, settingManager);
if (!arraySettings) {
log.warn(i18nHelper.getMessage(`130107`, variable.variable, outTypeName));
return content;
}
const strValues:string[] = value.map((v) => {
if (typeof v === 'string') {
return v;
} else {
return v?v.toString() : null;
}
})
.filter(v => v)
.map(v => this.handleText(v, targetType))
;
const arrayValue = StringUtil.handleArray(strValues, arraySettings);
content = content.replaceAll(variableStr, arrayValue);
return content;
}
static keyToVariable(key: string): string {
return `{{${key}}}`;
}
static replaceString(variable: FieldVariable, valueField: DataField, value: any, content: string, settingManager:SettingsManager, targetType: TargetType): string {
if (!content) {
return content;
}
let strValue = value? value.toString() : "";
return content.replaceAll(variable.variable, this.handleText(strValue, targetType, valueField));
}
/**
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
* @private
* @param content
* @param settingManager
*/
private static getAllVariables(content: string, settingManager:SettingsManager): FieldVariable[] {
const reg =/\{\{[a-zA-Z-0-9_.]+([(a-zA-Z-0-9)]+)?}}/g
const result = content.match(reg);
if (!result) {
return [];
}
return result.map((v) => {
const reg2 = new RegExp(`[a-zA-Z-0-9_.]+`, 'g');
const result2 = v.match(reg2);
if (!result2) {
return null;
}
if (result2.length == 1) {
return new FieldVariable(result2[0], v, null);
}
return new FieldVariable(result2[0], v, result2[1]);
}).filter(v => v);
}
/**
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
* @param outTypeName
* @param settingManager
* @private
*/
private static getArraySetting(outTypeName: string, settingManager:SettingsManager): ArraySetting {
if (!outTypeName) {
return settingManager.getArraySetting(DEFAULT_SETTINGS_ARRAY_NAME);
} else {
return settingManager.getArraySetting(outTypeName);
}
}
private static replaceMap(obj: Map, allVariables:FieldVariable[], content: string, settingManager: SettingsManager, targetType: TargetType) {
allVariables.forEach(variable => {
const value = obj.get(variable.key);
content = this.replaceVariable(variable, value, content, settingManager, targetType);
});
return content;
}
static getType(value: any):DataValueType {
if (typeof value === 'number') {
return DataValueType.number;
} else if (typeof value === 'string') {
if (value.startsWith('http://') || value.startsWith('https://')) {
return DataValueType.url;
}else if (/^(\/|\.\/|\.\.\/|~\/|.*\.[a-zA-Z0-9]+$)/.test(value)) {
return DataValueType.path;
}
return DataValueType.string;
} else if (value instanceof Date) {
return DataValueType.date;
} else if (value instanceof Array) {
return DataValueType.array;
} else {
return DataValueType.string;
}
}
private static replaceDataField(variable: FieldVariable, value: DataField, content: string, settingManager: SettingsManager, targetType: TargetType) {
if (!content) {
return content;
}
const variableStr = variable.variable;
if (!value) {
return content.replaceAll(variableStr, "");
}
switch (value.type) {
case DataValueType.string:
content = this.replaceString(variable, value, value.value, content, settingManager, targetType);
break;
case DataValueType.number:
content = content.replaceAll(variableStr, this.handleText(value.value.toString(), targetType, value));
break;
case DataValueType.date:
content = content.replaceAll(variableStr, this.handleText(value.value, targetType, value));
break;
case DataValueType.array:
content = this.replaceArray(variable, value.value, content, settingManager, targetType);
break;
default:
content = content.replaceAll(variableStr, this.handleText(value.value, targetType, value));
break;
}
return content;
}
/**
* 处理自定义参数
* @private
* @param subjectType
* @param variableMap
* @param settingMananger
* @param targetType
*/
static handleCustomVariable(subjectType: SupportType, variableMap:Map, settingMananger: SettingsManager, targetType:TargetType): void {
// @ts-ignore
const customProperties: CustomProperty[] = settingMananger.getSetting('customProperties');
if (!customProperties) {
return ;
}
const customPropertiesMap= new Map();
customProperties.filter(customProperty => customProperty.name &&
customProperty.field
&& (customProperty.field.toLowerCase() == SupportType.all ||
customProperty.field.toLowerCase() == subjectType)).forEach(customProperty => {
customPropertiesMap.set(customProperty.name, customProperty.value);
});
customPropertiesMap.forEach((value, key) => {
variableMap.set(key,
new DataField(
key, DataValueType.string, value,
VariableUtil.replace(variableMap, value, settingMananger, targetType)));
})
}
private static objToMap(obj: any):Map {
const map = new Map();
Object.keys(obj).forEach(key => {
map.set(key, obj[key]);
});
return map;
}
/**
* 根据用户配置的 ArrayLengthLimit 截断 variableMap 中匹配的数组字段。
*
* 仅对 DataField.value 为数组的字段生效。当 limit > 0 且数组长度大于 limit 时,
* 会取前 limit 个元素生成新的 DataField 替换原值; origin 保持不变以便后续逻辑。
*
* 当 type=all 时, 该限制作用于所有内容类型, 否则仅作用于 type 与当前 subjectType 相等的条目。
*
* @param subjectType 当前内容类型
* @param variableMap 变量映射
* @param settingManager
*/
static applyArrayLengthLimits(subjectType: SupportType,
variableMap: Map,
settingManager: SettingsManager): void {
if (!variableMap || variableMap.size == 0) {
return;
}
// @ts-ignore
const limits: ArrayLengthLimit[] = settingManager.getSetting('arrayLengthLimits');
if (!limits || limits.length == 0) {
return;
}
const effectiveLimits: Map = new Map();
limits
.filter(l => l && l.field && l.limit && l.limit > 0)
.filter(l => {
if (!l.type) {
return false;
}
const t = String(l.type).toLowerCase();
return t === SupportType.all || t === subjectType;
})
.forEach(l => {
const existing = effectiveLimits.get(l.field);
// 当同一字段同时存在 type=all 和 type=具体 的限制时,
// 优先使用更具体的(更小的)限制值, 行为更符合用户预期。
if (existing == null || l.limit < existing) {
effectiveLimits.set(l.field, l.limit);
}
});
if (effectiveLimits.size == 0) {
return;
}
effectiveLimits.forEach((limit, fieldName) => {
const dataField = variableMap.get(fieldName);
if (!dataField) {
return;
}
const value = dataField.value;
if (Array.isArray(value) && value.length > limit) {
const truncated = value.slice(0, limit);
variableMap.set(fieldName, new DataField(
dataField.name,
dataField.type,
dataField.origin,
truncated
));
}
});
}
private static handleText(v: string, targetType: TargetType, dataField: DataField = null): string {
if (targetType === 'yml_text') {
return YamlUtil.handleText(v, dataField);
}
if (targetType === 'text') {
return v;
}
if (targetType === 'path') {
return FileUtil.replaceSpecialCharactersForFileName(v);
}
}
}