1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const propsMap: string[] = ['disabled', 'hidden', 'checked', 'selected', 'required', 'open', 'readonly']; 17 18declare interface HTMLTemplateElement { 19 render(data: unknown): unknown; 20} 21//@ts-ignore 22(HTMLTemplateElement as unknown).prototype.render = function (data: unknown): HTMLElement { 23 if (!this.$fragment) { 24 const rule = this.getAttribute('rule') || 'v-'; 25 this.$fragment = this.cloneNode(true); 26 this.fragment = document.createElement('TEMPLATE'); 27 28 // v-for Loop rendering 29 // <div v-for="list"></div> => ${ list.map(function(item,index){ return '<div></div>' }).join('') } 30 const repeatEls = this.$fragment.content.querySelectorAll(`[\\${rule}for]`); 31 repeatEls.forEach((el: unknown) => { 32 //@ts-ignore 33 const strFor = el.getAttribute(`${rule}for`); 34 const { isArray, items, params } = parseFor(strFor); //@ts-ignore 35 el.before( 36 '${Object.entries(' + 37 items + 38 ').map(function([' + 39 `${isArray ? '$index$' : params[1] || 'name'},${params[0] || (isArray ? 'item' : 'value')}],${ 40 params[2] || 'index' 41 }` + 42 '){ return `' 43 ); //@ts-ignore 44 el.removeAttribute(`${rule}for`); //@ts-ignore 45 el.after('`}).join("")}'); 46 }); 47 48 // v-if Conditional rendering 49 // <div v-if="if"></div> => ${ if ? '<div></div>' : '' } 50 const ifEls = this.$fragment.content.querySelectorAll(`[\\${rule}if]`); 51 ifEls.forEach((el: unknown) => { 52 //@ts-ignore 53 const ifs = el.getAttribute(`${rule}if`); //@ts-ignore 54 el.before('${' + ifs + '?`'); //@ts-ignore 55 el.removeAttribute(`${rule}if`); //@ts-ignore 56 el.after('`:`<!--if:' + el.tagName + '-->`}'); 57 }); 58 59 // fragment <fragment>aa</fragment> => aa 60 const fragments = this.$fragment.content.querySelectorAll('fragment,block'); 61 fragments.forEach((el: unknown) => { 62 //@ts-ignore 63 el.after(el.innerHTML); //@ts-ignore 64 el.parentNode.removeChild(el); 65 }); 66 } 67 this.fragment.innerHTML = this.$fragment.innerHTML.interpolate(data); 68 69 // props 70 const propsEls = this.fragment.content.querySelectorAll(`[${propsMap.join('],[')}]`); 71 propsEls.forEach((el: unknown) => { 72 propsMap.forEach((props: unknown) => { 73 // If these attribute values are false, they are removed directly 74 //@ts-ignore 75 if (el.getAttribute(props) === 'false') { 76 //@ts-ignore 77 el.removeAttribute(props); 78 } 79 }); 80 }); 81 return this.fragment; 82}; 83 84function parseFor(strFor: String): { isArray: boolean; items: string | String; params: string[] } { 85 // Whether it is an object 86 const isObject = strFor.includes(' of '); 87 const reg = /\s(?:in|of)\s/g; 88 const [keys, obj] = strFor.match(reg) ? strFor.split(reg) : ['item', strFor]; 89 const items = Number(obj) > 0 ? `[${'null,'.repeat(Number(obj) - 1)}null]` : obj; 90 const params = keys.split(/[\(|\)|,\s?]/g).filter(Boolean); 91 return { isArray: !isObject, items, params }; 92} 93 94// String to template string 95//@ts-ignore 96(String as unknown).prototype.interpolate = function (params: unknown): Function { 97 //@ts-ignore 98 const names = Object.keys(params); 99 // @ts-ignore 100 const vals = Object.values(params); 101 const str = this.replace(/\{\{([^\}]+)\}\}/g, (all: unknown, s: unknown) => `\${${s}}`); 102 // @ts-ignore 103 return new Function(...names, `return \`${escape2Html(str)}\`;`)(...vals); 104}; 105 106// HTML Character inversion meaning < => < 107function escape2Html(str: string): string { 108 let arrEntities: unknown = { lt: '<', gt: '>', nbsp: ' ', amp: '&', quot: '"' }; 109 return str.replace(/&(lt|gt|nbsp|amp|quot);/gi, function (all, t) { 110 //@ts-ignore 111 return arrEntities[t]; 112 }); 113} 114 115export function replacePlaceholders(str: string, ...args: string[]): string { 116 return str.replace(/\{(\d+)\}/g, (match, placeholderIndex) => { 117 const argIndex = parseInt(placeholderIndex, 10); 118 const replacement = args[argIndex - 1]; 119 return replacement || match; 120 }); 121} 122