json2html.ts 7.14 KB
Newer Older
Zéfling's avatar
Zéfling committed
1

2
3
4
export interface Json2htmlAttr {
    [key: string]: string | number | null;
}
Zéfling's avatar
Zéfling committed
5

6
7
export type Json2htmlBody = (Json2htmlRef | string)[] | Json2htmlRef | string;

Zéfling's avatar
Zéfling committed
8
export interface Json2htmlRef {
Zéfling's avatar
Zéfling committed
9
    /** tag name */
10
    tag: string;
Zéfling's avatar
Zéfling committed
11
    /** attributes */
12
    attrs?: Json2htmlAttr;
Zéfling's avatar
Zéfling committed
13
    /** content of tag */
14
    body?: Json2htmlBody;
Zéfling's avatar
Zéfling committed
15
16
17
18
    /** inline : override formatting option for this element and these children */
    inline?: boolean;
    /** autoclose (XML only) */
    autoclose?: boolean;
Zéfling's avatar
Zéfling committed
19
20
21
}

export interface Json2htmlOptions {
Zéfling's avatar
Zéfling committed
22
23
24
25
26
    /**
     * format of the rendered structure:
     * * `inline`: all on one line without space
     * * `multiline`: structure sur plusieur lignes avec indentation possible
     */
Zéfling's avatar
Zéfling committed
27
    formatting?: 'inline' | 'multiline';
Zéfling's avatar
Zéfling committed
28
    /** type d'indentation `space` or `tab` */
29
    spaceType?: 'space' | 'tab';
Zéfling's avatar
Zéfling committed
30
    /** size of each level of the indentation  */
31
    spaceLength?: number;
Zéfling's avatar
Zéfling committed
32
    /** size of the indentation before each line */
33
    spaceBase?: number;
Zéfling's avatar
Zéfling committed
34
    /** maximum number of characters for a line */
35
    maxLenght?: number;
Zéfling's avatar
Zéfling committed
36
37
38
39
40
41
42
    /**
     * attribute alignment:
     * * `inline`: no alignment
     * * `space`: alignment with higher level
     * * `alignTag`: alignment with the tag
     * * `alignFirstAttr`: alignment with the first attribute
     */
43
    attrPosition?: 'inline' | 'space' | 'alignTag' | 'alignFirstAttr';
Zéfling's avatar
Zéfling committed
44
45
46
    /** Format of the targeted structure */
    type?: 'html' | 'xml';
    /** active of not en indentation. If false, these options are ignored : `spaceType`, `spaceLength` */
47
    indent?: boolean;
Zéfling's avatar
Zéfling committed
48
    /** list of HTML tags without content */
49
    noContentTags?: string[];
Zéfling's avatar
Zéfling committed
50
51
52
53
54
55
56
57
58
}

export class Json2html {

    readonly options: Json2htmlOptions = {
        spaceType: 'space',
        spaceLength: 4,
        spaceBase: 0,
        maxLenght: 0,
Zéfling's avatar
Zéfling committed
59
        attrPosition: 'alignFirstAttr',
Zéfling's avatar
Zéfling committed
60
        type: 'html',
Zéfling's avatar
Zéfling committed
61
        formatting: 'multiline',
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
        indent: true,
        noContentTags: [
            'area',
            'base',
            'br',
            'col',
            'command',
            'embed',
            'hr',
            'img',
            'input',
            'keygen',
            'link',
            'meta',
            'param',
            'source',
            'track',
            'wbr'
        ]
    };
Zéfling's avatar
Zéfling committed
82

Zéfling's avatar
Zéfling committed
83
84
85
86
    /**
     * @param json one ou list of node data
     * @param option formating options
     */
Zéfling's avatar
Zéfling committed
87
    constructor(
88
        public json: Json2htmlRef | Json2htmlRef[],
Zéfling's avatar
Zéfling committed
89
90
91
92
93
        option: Json2htmlOptions = {}
    ) {
        Object.assign(this.options, option);
    }

Zéfling's avatar
Zéfling committed
94
95
96
    /**
     * rendering in string
     */
Zéfling's avatar
Zéfling committed
97
    toString() {
98
99
100
101
102
103
104
105
106
        let html = '';
        if (!Array.isArray(this.json)) {
            html = `${this._getSpacing(0)}${this._generate(0, this.json)}`;
        } else {
            this.json.forEach((element, index) => {
                html += `${index > 0 ? '\n' : ''}${this._getSpacing(0)}${this._generate(0, element)}`;
            });
        }
        return html;
Zéfling's avatar
Zéfling committed
107
108
    }

Zéfling's avatar
Zéfling committed
109
110
111
112
113
114
115
    /**
     * generation in string for a node (tag with attributes and content)
     * @param lvl node level
     * @param json node data
     * @returns render of node
     */
    private _generate(lvl: number, json: Json2htmlRef): string {
116
117
118
        const hasContent = !this.options.noContentTags.includes(json.tag.toLowerCase());
        let string = `<${json.tag}${this._generateAttrs(lvl, json)}>`;
        if (hasContent) {
119
            let tagcontent = this._generateBody(lvl, json);
120
            if (tagcontent && this._hasMultiline()) {
Zéfling's avatar
Zéfling committed
121
122
                tagcontent = `${tagcontent}\n${this._getSpacing(lvl)}`;
            }
123
            string += `${tagcontent}</${json.tag}>`;
Zéfling's avatar
Zéfling committed
124
        }
125
        return string;
Zéfling's avatar
Zéfling committed
126
127
    }

Zéfling's avatar
Zéfling committed
128
129
130
131
132
133
    /**
     * attributes list generation
     * @param lvl level node
     * @param json node data
     * @returns attributes tag
     */
Zéfling's avatar
Zéfling committed
134
135
    private _generateAttrs(lvl: number, json: Json2htmlRef) {
        let string = '';
136
        const attrs = json.attrs;
Zéfling's avatar
Zéfling committed
137
138
139
140
        if (attrs && Object.keys(attrs).length) {
            for (const id in attrs) {
                if (attrs[id] !== undefined) {
                    let attr = '';
141
142
                    switch (this.options.attrPosition) {
                        case 'inline':
Zéfling's avatar
Zéfling committed
143
                            attr += ' ';
144
145
                            break;
                        case 'space':
Zéfling's avatar
Zéfling committed
146
                            attr += string && this.options.indent && this._hasMultiline()
147
148
149
150
                                ? `\n${this._getSpacing(lvl + 1)}`
                                : string += ' ';
                            break;
                        case 'alignTag':
Zéfling's avatar
Zéfling committed
151
                            attr += string && this.options.indent && this._hasMultiline()
152
153
154
155
                                ? `\n${this._getSpacing(lvl, 1)}`
                                : ' ';
                            break;
                        case 'alignFirstAttr':
Zéfling's avatar
Zéfling committed
156
157
                            attr += string && this.options.indent && this._hasMultiline()
                                ? `\n${this._getSpacing(lvl, json.tag.length + 2)}`
158
159
160
                                : ' ';
                            break;
                    }
Zéfling's avatar
Zéfling committed
161
162
                    attr += `${id}${attrs[id] !== null || attrs[id] ? `="${attrs[id]}"` : ''}`;
                    string += attr;
Zéfling's avatar
Zéfling committed
163
164
165
166
167
168
                }
            }
        }
        return string;
    }

Zéfling's avatar
Zéfling committed
169
170
171
172
173
174
    /**
     * tag body generation
     * @param lvl level node
     * @param json node data
     * @returns render of body
     */
175
    private _generateBody(lvl: number, json: Json2htmlRef) {
Zéfling's avatar
Zéfling committed
176
177
178
        let string = '';
        if (json.body) {
            if (typeof json.body === 'string') {
Zéfling's avatar
Zéfling committed
179
180
181
                if (this._hasMultiline()) {
                    string = `\n${this._getSpacing(lvl + 1)}`;
                }
Zéfling's avatar
Zéfling committed
182
                string += json.body;
183
184
            } else if (!Array.isArray(json.body)) {
                string += this._generateBodyElement(lvl, json);
Zéfling's avatar
Zéfling committed
185
            } else {
186
187
                json.body.forEach(element => {
                    string += this._generateBodyElement(lvl, element);
188
                });
Zéfling's avatar
Zéfling committed
189
190
191
192
193
            }
        }
        return string;
    }

Zéfling's avatar
Zéfling committed
194
195
196
197
198
199
    /**
     * tag body generation for one node
     * @param lvl level node
     * @param element node data or string
     * @returns render of body
     */
200
201
202
203
204
205
206
207
208
209
210
    private _generateBodyElement(lvl: number, element: Json2htmlRef | string): string {
        let string = '';
        if (this._hasMultiline()) {
            string += `\n${this._getSpacing(lvl + 1)}`;
        }
        string += typeof element === 'string'
            ? element
            : this._generate(lvl + 1, element);
        return string;
    }

Zéfling's avatar
Zéfling committed
211
212
213
    /**
     * if multiline rendering
     */
Zéfling's avatar
Zéfling committed
214
215
216
217
    private _hasMultiline(): boolean {
        return this.options.formatting === 'multiline';
    }

Zéfling's avatar
Zéfling committed
218
219
220
221
222
223
224
    /**
     * calculating the number of spaces for indentation
     * @param lvl level
     * @param addition space of space (only space, no tabulation)
     * @returns space
     */
    private _getSpacing(lvl: number, addition: number = 0): string {
Zéfling's avatar
Zéfling committed
225
226
        return this.options.indent
            ? (this.options.spaceType === 'space' ? ' ' : '\t')
Zéfling's avatar
Zéfling committed
227
                .repeat((lvl + this.options.spaceBase) * this.options.spaceLength) + ' '.repeat(addition)
Zéfling's avatar
Zéfling committed
228
229
230
            : '';
    }

Zéfling's avatar
Zéfling committed
231
}