File : SourceHtmlBuilder.js

1
/*
2
Copyright - 2021 - wwwouaiebe - Contact: https://www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v1.0.0:
21
        - created
22
    - v1.1.0:
23
        - Issue ♯1 : Improve colorization of sources files...
24
        - Issue ♯3 : String.substr ( ) is deprecated... Replace...
25
Doc reviewed 20211111
26
*/
27
/* ------------------------------------------------------------------------------------------------------------------------- */
28
29
import FileWriter from './FileWriter.js';
30
import NavHtmlBuilder from './NavHtmlBuilder.js';
31
import theLinkBuilder from './LinkBuilder.js';
32
import theConfig from './Config.js';
33
34
/* ------------------------------------------------------------------------------------------------------------------------- */
35
/**
36
Build the sources HTML pages
37
*/
38
/* ------------------------------------------------------------------------------------------------------------------------- */
39
40
class SourceHtmlBuilder {
41
42
    /**
43
    A sources files counter
44
    @type {Number}
45
    */
46
47
    #sourcesCounter;
48
49
    /**
50
    A list with JS keyword that will be colored in blue in the sources files
51
    @type {Array.<String>}
52
    */
53
54
    static get #jsKeywords ( ) {
55
        return [
56
            'async',
57
            'await',
58
            'break',
59
            'class',
60
            'const',
61
            'constructor',
62
            'export',
63
            'for',
64
            'get',
65
            'import',
66
            'let',
67
            'new',
68
            'return',
69
            'set',
70
            'static',
71
            'switch',
72
            'this',
73
            'throw',
74
            'try',
75
            'catch',
76
            'while'
77
        ];
78
    }
79
80
    /**
81
    The path between the html file and theConfig.destDir ( something like '../../../', depending of the folders tree )
82
    @type {String}
83
    */
84
85
    #rootPath;
86
87
    /**
88
    The content of the source file
89
    @type {String}
90
    */
91
92
    #fileContent;
93
94
    /**
95
    Colorize the js keywords in the source file content
96
    */
97
98
    #colorizeJSKeywords ( ) {
99
        SourceHtmlBuilder.#jsKeywords.forEach (
100
            keyword => {
101
                const regexp = new RegExp ( '(?<=[\n| |,|;|.])' + keyword + '(?=[ |,|;|.])', 'g' );
102
                this.#fileContent = this.#fileContent.replaceAll ( regexp, '<span class="jsKeyword">' + keyword + '</span>' );
103
            }
104
        );
105
    }
106
107
    /**
108
    add the classes links in the source file content
109
    */
110
111
    #setClassesLinks ( ) {
112
        theLinkBuilder.classesLinks.forEach (
113
            classLink => {
114
                const regexp = new RegExp ( '(?<=[\n| |,|;|.|{])' + classLink [ 0 ] + '(?=[ |,|;|.|}|\r|\n])', 'g' );
115
                this.#fileContent = this.#fileContent.replaceAll (
116
                    regexp,
117
                    `<a class="classLink" href="${this.#rootPath + classLink [ 1 ]}">` + classLink [ 0 ] + '</a>'
118
                );
119
            }
120
        );
121
    }
122
123
    /**
124
    add the variables links in the source file content
125
    */
126
127
    #setVariablesLinks ( ) {
128
        theLinkBuilder.variablesLinks.forEach (
129
            variableLink => {
130
                const regexp = new RegExp ( '(?<=[\n| |,|;|.])' + variableLink [ 0 ] + '(?=[ |,|;|.|\r|\n])', 'g' );
131
                this.#fileContent = this.#fileContent.replaceAll (
132
                    regexp,
133
                    `<a class="variableLink" href="${this.#rootPath + variableLink [ 1 ]}">` + variableLink [ 0 ] + '</a>'
134
                );
135
            }
136
        );
137
    }
138
139
    /**
140
    The constructor
141
    */
142
143
    constructor ( ) {
144
        Object.freeze ( this );
145
        this.#sourcesCounter = 0;
146
    }
147
148
    /**
149
    A sources files counter
150
    @type {Number}
151
    */
152
153
    get sourcesCounter ( ) { return this.#sourcesCounter; }
154
155
    /**
156
    Build the  source html file.
157
    @param {String} fileContent The file content
158
    @param {String} fileName The file name, including the path since theConfig.destDir
159
    @param {Array.<TagData>} tagsData An array with the tags to insert in the souce file
160
    */
161
162
    build ( fileContent, fileName, tagsData ) {
163
164
        this.#sourcesCounter ++;
165
166
        // Computing rootPath, htmlFilePath and dirs
167
        // dirs is an array with all the folders between theConfig.destDir and the htmlFile
168
        // rootPath is the path between the htmlFile and theConfig.destDir
169
        // htmlFilePath is the path between theConfig.destDir and the htmlFile
170
        const dirs = fileName.split ( '/' );
171
        const htmlFileName = dirs.pop ( ).split ( '.' ) [ 0 ] + 'js.html';
172
        this.#rootPath = '';
173
        dirs.forEach ( ( ) => this.#rootPath += '../' );
174
175
        const navHtmlBuilder = new NavHtmlBuilder ( );
176
177
        // head
178
        let html =
179
            '<!DOCTYPE html><html><head><meta charset="UTF-8">' +
180
            `<link type="text/css" rel="stylesheet" href="${this.#rootPath}ESSimpleDoc.css"></head><body>`;
181
182
        // nav
183
        html += navHtmlBuilder.build ( this.#rootPath );
184
185
        // title
186
        html += `<h1>File : ${fileName}</h1>`;
187
188
        if ( tagsData ) {
189
190
            // tagsDdata is sorted by line number and column number.
191
            // TagsData with column === null are the last
192
            tagsData.sort (
193
                ( first, second ) => {
194
                    if ( first.line === second.line ) {
195
                        if ( null === second.column ) {
196
                            return -1;
197
                        }
198
                        else if ( null === first.column ) {
199
                            return 1;
200
                        }
201
                        return second.column - first.column;
202
                    }
203
204
                    return first.line - second.line;
205
206
                }
207
            );
208
209
            // File content is splitted into lines
210
            let lines = fileContent.split ( /\r\n|\r|\n/ );
211
212
            // and then tags are inserted
213
            let previousTag = { line : -1, column : -1, tag : '' };
214
            tagsData.forEach (
215
                tag => {
216
                    if ( previousTag.line !== tag.line || previousTag.column !== tag.column ) {
217
                        let line = lines [ tag.line - 1 ];
218
                        if ( null === tag.column ) {
219
                            line += tag.tag;
220
                        }
221
                        else {
222
                            line = line.substring ( 0, tag.column ) + tag.tag + line.substring ( tag.column );
223
                        }
224
                        lines [ tag.line - 1 ] = line;
225
                    }
226
227
                    previousTag = tag;
228
                }
229
            );
230
231
            // Line are reassembled into a unique string to avoid a lot of replaceAll
232
            this.#fileContent = '';
233
            lines.forEach (
234
                line => this.#fileContent += line + '\n'
235
            );
236
        }
237
        else {
238
            this.#fileContent = fileContent;
239
        }
240
241
        this.#fileContent = this.#fileContent
242
243
            // Removing tabs
244
            .replaceAll ( '\t', '    ' )
245
246
            // Removing html entities
247
            .replaceAll ( /\u003c/g, '<' )
248
            .replaceAll ( /\u003e/g, '>' )
249
            .replaceAll ( /\u0022/g, '"' )
250
            .replaceAll ( /\u0027/g, ''' )
251
252
            // replacing < > and quot in tags for literals and comments
253
            .replaceAll ( /, '<' )
254
            .replaceAll ( />/g, '>' )
255
            .replaceAll ( /"/g, '"' );
256
257
        // Adding color span and links
258
        if ( ! theConfig.noSourcesColor ) {
259
            this.#colorizeJSKeywords ( );
260
            this.#setClassesLinks ( );
261
            this.#setVariablesLinks ( );
262
        }
263
264
        // creating the file content in a html table
265
        let lineCounter = 0;
266
        html += '<table class="srcCode">';
267
268
        // splitting file into lines
269
        this.#fileContent.split ( /\r\n|\r|\n/ ).forEach (
270
            line => {
271
272
                // and adding a row in the html table
273
                lineCounter ++;
274
                const strLineCounter = String ( lineCounter ).padStart ( 5, '_' );
275
                html +=
276
                    `<tr><td><a id="L${strLineCounter}">${lineCounter}</a></td>` +
277
                    `<td><pre>${line}</pre></td></tr>`;
278
            }
279
        );
280
281
        html += '</table>';
282
283
        // footer
284
        html += navHtmlBuilder.footer;
285
        html +=
286
            '<script>document.getElementById ( new URL ( window.location' +
287
            ' ).hash.substring( 1 ) )?.parentNode.classList.add ( \'hash\' )</script>';
288
        html += '</body></html>';
289
290
        // write file
291
        new FileWriter ( ).write ( dirs, htmlFileName, html );
292
293
    }
294
}
295
296
export default SourceHtmlBuilder;
297
298
/* --- End of file --------------------------------------------------------------------------------------------------------- */
299