├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── main.yml
├── .gitignore
├── CONTRIBUTING.md
├── Chapter1
├── 01_01
│ └── cookies.html
├── 01_02
│ ├── cookies.html
│ └── cookies.js
├── 01_03
│ ├── queryall.html
│ └── queryall.js
├── 01_04
│ ├── queryall.html
│ └── queryall.js
├── 01_05
│ ├── queryall.html
│ └── queryall.js
└── 01_06
│ ├── queryall.html
│ └── queryall.js
├── Chapter2
├── 02_01
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_03
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_04
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_05
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_06
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_07
│ ├── prettyparse.html
│ └── prettyparse.js
├── 02_08
│ ├── prettyparse.html
│ └── prettyparse.js
└── 02_09
│ ├── prettyparse.html
│ └── prettyparse.js
├── Chapter3
├── 03_03
│ ├── prettyparse.html
│ └── prettyparse.js
├── 03_04
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
├── 03_05
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
├── 03_06
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
├── 03_07
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
├── 03_08
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
└── 03_09
│ ├── prettyparse.css
│ ├── prettyparse.html
│ └── prettyparse.js
├── LICENSE
├── NOTICE
└── README.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Codeowners for these exercise files:
2 | # * (asterisk) deotes "all files and folders"
3 | # Example: * @producer @instructor
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ## Issue Overview
9 |
10 |
11 | ## Describe your environment
12 |
13 |
14 | ## Steps to Reproduce
15 |
16 | 1.
17 | 2.
18 | 3.
19 | 4.
20 |
21 | ## Expected Behavior
22 |
23 |
24 | ## Current Behavior
25 |
26 |
27 | ## Possible Solution
28 |
29 |
30 | ## Screenshots / Video
31 |
32 |
33 | ## Related Issues
34 |
35 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Copy To Branches
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 | copy-to-branches:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v2
9 | with:
10 | fetch-depth: 0
11 | - name: Copy To Branches Action
12 | uses: planetoftheweb/copy-to-branches@v1
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | .tmp
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | Contribution Agreement
3 | ======================
4 |
5 | This repository does not accept pull requests (PRs). All pull requests will be closed.
6 |
7 | However, if any contributions (through pull requests, issues, feedback or otherwise) are provided, as a contributor, you represent that the code you submit is your original work or that of your employer (in which case you represent you have the right to bind your employer). By submitting code (or otherwise providing feedback), you (and, if applicable, your employer) are licensing the submitted code (and/or feedback) to LinkedIn and the open source community subject to the BSD 2-Clause license.
8 |
--------------------------------------------------------------------------------
/Chapter1/01_01/cookies.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Granny's Chocolate Chip Cookies
6 |
9 |
10 |
11 |
16 |
17 |
18 | Granny's Chocolate Chip Cookies
19 | The trick to great cookies is getting the butter to the right
20 | consistency. Soft, but not melted. Cream the butter and two sugars,
21 | mix in the egg, vanilla, baking soda, water, and salt.
22 |
23 | Last of all, mix in the flour slowly, so there are no lumps or unmixed
24 | flour in your dough. Mix in the chocolate chips, then use two spoons
25 | to drop dough on an ungreased cookie sheet.
26 |
27 | Bake at 190°c, and use your eyes and nose to know when they're
28 | done. Final step: enjoy!
29 |
30 |
31 |
32 |
33 |
34 | Ingredient
35 | Amountg
36 |
37 |
38 |
39 |
40 | Butter
41 | 100
42 |
43 |
44 | Sugar (granulated white)
45 | 140
46 |
47 |
48 | Brown sugar (dark or light, packed)
49 | 40
50 |
51 |
52 | Egg (fresh)
53 | 44
54 |
55 |
56 | Vanilla Extract
57 | 5
58 |
59 |
60 | Baking soda
61 | 4
62 |
63 |
64 | Water
65 | 6
66 |
67 |
68 | Salt (table)
69 | 3
70 |
71 |
72 | All-Purpose Flour
73 | 151
74 |
75 |
76 | Chocolate Chips
77 | 150
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Chapter1/01_02/cookies.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Granny's Chocolate Chip Cookies
6 |
9 |
10 |
11 |
24 |
25 |
26 | Granny's Chocolate Chip Cookies
27 | The trick to great cookies is getting the butter to the right
28 | consistency. Soft, but not melted. Cream the butter and two sugars,
29 | mix in the egg, vanilla, baking soda, water, and salt.
30 |
31 | Last of all, mix in the flour slowly, so there are no lumps or unmixed
32 | flour in your dough. Mix in the chocolate chips, then use two spoons
33 | to drop dough on an ungreased cookie sheet.
34 |
35 | Bake at 190°c, and use your eyes and nose to know when they're
36 | done. Final step: enjoy!
37 |
38 |
39 |
40 |
41 |
42 | Ingredient
43 | Amountg
44 |
45 |
46 |
47 |
48 | Butter
49 | 100
50 |
51 |
52 | Sugar (granulated white)
53 | 140
54 |
55 |
56 | Brown sugar (dark or light, packed)
57 | 40
58 |
59 |
60 | Egg (fresh)
61 | 44
62 |
63 |
64 | Vanilla Extract
65 | 5
66 |
67 |
68 | Baking soda
69 | 4
70 |
71 |
72 | Water
73 | 6
74 |
75 |
76 | Salt (table)
77 | 3
78 |
79 |
80 | All-Purpose Flour
81 | 151
82 |
83 |
84 | Chocolate Chips
85 | 150
86 |
87 |
88 | Walnuts
89 | 150
90 |
91 |
92 | Total Weight:
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Chapter1/01_02/cookies.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | const ingredients = document.getElementById('ingredients');
3 |
4 | let total = 0;
5 |
6 | Array.prototype.forEach.call(ingredients.children, (tr) => {
7 | const td = tr.children[1];
8 |
9 | if (tr.id == 'totals') {
10 | td.innerText = total;
11 | } else {
12 | const weight = parseFloat(td.innerText);
13 |
14 | total += weight;
15 | }
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/Chapter1/01_03/queryall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | QueryAll
8 |
9 |
10 |
11 |
25 |
26 |
27 |
28 | QueryAll - querySelectorAll() Demo
29 |
30 |
31 |
32 |
35 | Matches
36 |
38 |
39 |
43 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Chapter1/01_03/queryall.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | document.forms.queryall.addEventListener('submit', (e) => {
11 | e.preventDefault();
12 |
13 | refreshQuery(e.target.selector.value);
14 |
15 | return false;
16 | });
17 |
18 | refreshOutput(textarea.value);
19 | });
20 |
21 | function refreshOutput(html) {
22 | const output = document.querySelector('#output');
23 |
24 | output.innerHTML = html;
25 | }
26 |
27 | function refreshQuery(selector) {
28 | const output = document.querySelector('#output');
29 | const matches = document.querySelector('#matches');
30 |
31 | matches.innerHTML = '';
32 |
33 | output.querySelectorAll(selector).forEach((el) => {
34 | const li = document.createElement('li');
35 | li.innerText = el.outerHTML;
36 |
37 | matches.appendChild(li);
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/Chapter1/01_04/queryall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | QueryAll
8 |
9 |
10 |
11 |
25 |
26 |
27 |
28 | QueryAll - querySelectorAll() Demo
29 |
30 |
31 |
32 |
37 | Matches
38 |
40 |
41 |
45 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Chapter1/01_04/queryall.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | document.forms.queryall.addEventListener('submit', (e) => {
11 | e.preventDefault();
12 |
13 | refreshQuery(e.target.selector.value);
14 |
15 | return false;
16 | });
17 |
18 | document.forms.queryall.addEventListener('click', (e) => {
19 | if (e.target.tagName != 'BUTTON') {
20 | return;
21 | }
22 |
23 | switch (e.target.innerText) {
24 | case 'delete': {
25 | deleteMatches();
26 | } break;
27 |
28 | case 'textify': {
29 | textifyMatches();
30 | } break;
31 | }
32 | });
33 |
34 | refreshOutput(textarea.value);
35 | });
36 |
37 | function refreshOutput(html) {
38 | const output = document.querySelector('#output');
39 |
40 | output.innerHTML = html;
41 | }
42 |
43 | function refreshQuery(selector) {
44 | const output = document.querySelector('#output');
45 | const matches = document.querySelector('#matches');
46 |
47 | matches.innerHTML = '';
48 |
49 | output.querySelectorAll(selector).forEach((el) => {
50 | const li = document.createElement('li');
51 | li.innerText = el.outerHTML;
52 |
53 | matches.appendChild(li);
54 | });
55 | }
56 |
57 | function deleteMatches() {
58 | const output = document.querySelector('#output');
59 |
60 | const selector = document.forms.queryall.selector.value;
61 |
62 | output.querySelectorAll(selector).forEach((el) => {
63 | el.parentElement.removeChild(el);
64 | });
65 |
66 | refreshQuery(selector);
67 | }
68 |
69 | function textifyMatches() {
70 | const output = document.querySelector('#output');
71 |
72 | const selector = document.forms.queryall.selector.value;
73 |
74 | output.querySelectorAll(selector).forEach((el) => {
75 | const text = document.createTextNode(el.innerText);
76 | el.parentElement.replaceChild(text, el);
77 | });
78 |
79 | refreshQuery(selector);
80 | }
81 |
--------------------------------------------------------------------------------
/Chapter1/01_05/queryall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | QueryAll
8 |
9 |
10 |
11 |
25 |
26 |
27 |
28 | QueryAll - querySelectorAll() Demo
29 |
30 |
31 |
32 |
37 | Matches
38 |
40 |
41 |
45 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Chapter1/01_05/queryall.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | document.forms.queryall.addEventListener('submit', (e) => {
11 | e.preventDefault();
12 |
13 | refreshQuery(e.target.selector.value);
14 |
15 | return false;
16 | });
17 |
18 | document.forms.queryall.addEventListener('click', (e) => {
19 | if (e.target.tagName != 'BUTTON') {
20 | return;
21 | }
22 |
23 | switch (e.target.innerText) {
24 | case 'delete': {
25 | deleteMatches();
26 | } break;
27 |
28 | case 'textify': {
29 | textifyMatches();
30 | } break;
31 | }
32 | });
33 |
34 | refreshOutput(textarea.value);
35 | });
36 |
37 | function refreshOutput(html) {
38 | const output = document.querySelector('#output');
39 |
40 | output.innerHTML = html;
41 | }
42 |
43 | function refreshQuery(selector) {
44 | const output = document.querySelector('#output');
45 | const matches = document.querySelector('#matches');
46 |
47 | matches.innerHTML = '';
48 |
49 | output.querySelectorAll(selector).forEach((el) => {
50 | const li = document.createElement('li');
51 | li.innerText = el.outerHTML;
52 |
53 | matches.appendChild(li);
54 | });
55 | }
56 |
57 | function deleteMatches() {
58 | const output = document.querySelector('#output');
59 |
60 | const selector = document.forms.queryall.selector.value;
61 |
62 | output.querySelectorAll(selector).forEach((el) => {
63 | el.parentElement.removeChild(el);
64 | });
65 |
66 | refreshQuery(selector);
67 | }
68 |
69 | function textifyMatches() {
70 | const output = document.querySelector('#output');
71 |
72 | const selector = document.forms.queryall.selector.value;
73 |
74 | output.querySelectorAll(selector).forEach((el) => {
75 | const text = document.createTextNode(el.innerText);
76 | el.parentElement.replaceChild(text, el);
77 | });
78 |
79 | refreshQuery(selector);
80 | }
81 |
--------------------------------------------------------------------------------
/Chapter1/01_06/queryall.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | QueryAll
8 |
9 |
10 |
11 |
30 |
31 |
32 |
33 | QueryAll - querySelectorAll() Demo
34 |
35 |
36 |
37 |
42 |
43 | Matches
44 |
46 |
47 |
48 |
52 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Chapter1/01_06/queryall.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | document.forms.queryall.addEventListener('submit', (e) => {
11 | e.preventDefault();
12 |
13 | refreshQuery(e.target.selector.value);
14 |
15 | return false;
16 | });
17 |
18 | document.forms.queryall.addEventListener('click', (e) => {
19 | if (e.target.tagName != 'BUTTON') {
20 | return;
21 | }
22 |
23 | switch (e.target.innerText) {
24 | case 'delete': {
25 | deleteMatches();
26 | } break;
27 |
28 | case 'textify': {
29 | textifyMatches();
30 | } break;
31 | }
32 | });
33 |
34 | refreshOutput(textarea.value);
35 | });
36 |
37 | function refreshOutput(html) {
38 | const output = document.querySelector('#output');
39 |
40 | output.innerHTML = html;
41 | }
42 |
43 | function refreshQuery(selector) {
44 | const output = document.querySelector('#output');
45 | const matches = document.querySelector('#matches');
46 |
47 | document.querySelectorAll('.match').forEach((el) => {
48 | el.classList.remove('match');
49 | });
50 |
51 | matches.innerHTML = '';
52 |
53 | output.querySelectorAll(selector).forEach((el) => {
54 | const li = document.createElement('li');
55 | li.innerText = el.outerHTML;
56 |
57 | matches.appendChild(li);
58 |
59 | el.classList.add('match');
60 | });
61 | }
62 |
63 | function deleteMatches() {
64 | const output = document.querySelector('#output');
65 |
66 | const selector = document.forms.queryall.selector.value;
67 |
68 | output.querySelectorAll(selector).forEach((el) => {
69 | el.parentElement.removeChild(el);
70 | });
71 |
72 | refreshQuery(selector);
73 | }
74 |
75 | function textifyMatches() {
76 | const output = document.querySelector('#output');
77 |
78 | const selector = document.forms.queryall.selector.value;
79 |
80 | output.querySelectorAll(selector).forEach((el) => {
81 | const text = document.createTextNode(el.innerText);
82 | el.parentElement.replaceChild(text, el);
83 | });
84 |
85 | refreshQuery(selector);
86 | }
87 |
--------------------------------------------------------------------------------
/Chapter2/02_01/prettyparse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HTML Pretty Printer
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 | HTML Pretty Printer
25 |
26 |
27 |
56 |
57 | Pretty Output
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Chapter2/02_01/prettyparse.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | refreshOutput(textarea.value);
11 | });
12 |
13 | function refreshOutput(html) {
14 | const output = document.querySelector('pp-output');
15 |
16 | output.innerText = '';
17 | output.appendChild(prettyParse(html));
18 | }
19 |
20 | function prettyParse(html) {
21 | const fragment = document.createDocumentFragment();
22 | const div = document.createElement('div');
23 |
24 | div.innerHTML = html;
25 |
26 | div.childNodes.forEach((node) => {
27 | fragment.appendChild(node);
28 | });
29 |
30 | return fragment;
31 | }
32 |
--------------------------------------------------------------------------------
/Chapter2/02_03/prettyparse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HTML Pretty Printer
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 | HTML Pretty Printer
25 |
26 |
27 |
56 |
57 | Pretty Output
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Chapter2/02_03/prettyparse.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | refreshOutput(textarea.value);
11 | });
12 |
13 | function refreshOutput(html) {
14 | const output = document.querySelector('pp-output');
15 |
16 | output.innerText = '';
17 | testLexer(html);
18 | }
19 |
20 | class Lexer {
21 | constructor(source) {
22 | this.source = source;
23 | this.file_pointer = 0;
24 | }
25 |
26 | read() {
27 | if (this.file_pointer < 0 || this.file_pointer >= this.source.length) {
28 | return undefined;
29 | }
30 |
31 | return this.source[this.file_pointer++];
32 | }
33 |
34 | rewind() {
35 | this.file_pointer = 0;
36 | }
37 |
38 | match(token) {
39 | return this.remainder.search(token) === 0;
40 | }
41 |
42 | consumeMatch(token) {
43 | const match = this.remainder.match(token);
44 |
45 | if (match && match.length && match.index == 0) {
46 | this.file_pointer += match[0].length;
47 |
48 | return true;
49 | }
50 |
51 | return false;
52 | }
53 |
54 | readUntil(condition) {
55 | const start_pointer = this.file_pointer;
56 |
57 | while (!this.eof && !condition(this)) {
58 | this.file_pointer++;
59 | }
60 |
61 | return this.source.substring(start_pointer, this.file_pointer);
62 | }
63 |
64 | readIdentifier() {
65 | return this.readUntil((lexer) => !lexer.match(/\w/));
66 | }
67 |
68 | skipWhitespace() {
69 | return this.readUntil((lexer) => !lexer.match(/\s/));
70 | }
71 |
72 | get eof() {
73 | return this.file_pointer >= this.source.length;
74 | }
75 |
76 | get remainder() {
77 | return this.source.substring(this.file_pointer);
78 | }
79 | }
80 |
81 | function testLexer(html) {
82 | const lexer = new Lexer(html);
83 |
84 | let output = '';
85 |
86 | while (!lexer.eof) {
87 | output += lexer.read();
88 | }
89 |
90 | console.assert(html == output);
91 |
92 | lexer.rewind();
93 |
94 | console.assert(lexer.match(''));
99 | lexer.consumeMatch('-->');
100 | console.log('comment: ', comment);
101 |
102 | lexer.skipWhitespace();
103 | console.assert(lexer.consumeMatch('<'));
104 | const tag = lexer.readIdentifier();
105 | console.log(tag);
106 | }
107 |
--------------------------------------------------------------------------------
/Chapter2/02_04/prettyparse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HTML Pretty Printer
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 | HTML Pretty Printer
25 |
26 |
27 |
56 |
57 | Pretty Output
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Chapter2/02_04/prettyparse.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | refreshOutput(textarea.value);
11 | });
12 |
13 | function refreshOutput(html) {
14 | const output = document.querySelector('pp-output');
15 |
16 | output.innerText = '';
17 | output.appendChild(prettyParse(html));
18 | }
19 |
20 | class Lexer {
21 | constructor(source) {
22 | this.source = source;
23 | this.file_pointer = 0;
24 | }
25 |
26 | read() {
27 | if (this.file_pointer < 0 || this.file_pointer >= this.source.length) {
28 | return undefined;
29 | }
30 |
31 | return this.source[this.file_pointer++];
32 | }
33 |
34 | rewind() {
35 | this.file_pointer = 0;
36 | }
37 |
38 | match(token) {
39 | return this.remainder.search(token) === 0;
40 | }
41 |
42 | consumeMatch(token) {
43 | const match = this.remainder.match(token);
44 |
45 | if (match && match.length && match.index == 0) {
46 | this.file_pointer += match[0].length;
47 |
48 | return true;
49 | }
50 |
51 | return false;
52 | }
53 |
54 | readUntil(condition) {
55 | const start_pointer = this.file_pointer;
56 |
57 | while (!this.eof && !condition(this)) {
58 | this.file_pointer++;
59 | }
60 |
61 | return this.source.substring(start_pointer, this.file_pointer);
62 | }
63 |
64 | readIdentifier() {
65 | return this.readUntil((lexer) => !lexer.match(/\w/));
66 | }
67 |
68 | skipWhitespace() {
69 | return this.readUntil((lexer) => !lexer.match(/\s/));
70 | }
71 |
72 | get eof() {
73 | return this.file_pointer >= this.source.length;
74 | }
75 |
76 | get remainder() {
77 | return this.source.substring(this.file_pointer);
78 | }
79 | }
80 |
81 | function prettyParse(html) {
82 | const lexer = new Lexer(html);
83 |
84 | function parseContent() {
85 | let text = '';
86 | const fragment = document.createDocumentFragment();
87 |
88 | function flushText() {
89 | if (text.length) {
90 | fragment.appendChild(document.createTextNode(text));
91 | text = '';
92 | }
93 | }
94 |
95 | while (!lexer.eof) {
96 | text += lexer.read();
97 | }
98 |
99 | flushText();
100 |
101 | return fragment;
102 | }
103 |
104 | return parseContent();
105 | }
106 |
--------------------------------------------------------------------------------
/Chapter2/02_05/prettyparse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | HTML Pretty Printer
8 |
9 |
10 |
11 |
21 |
22 |
23 |
24 | HTML Pretty Printer
25 |
26 |
27 |
56 |
57 | Pretty Output
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Chapter2/02_05/prettyparse.js:
--------------------------------------------------------------------------------
1 | /* eslint no-undef: "error" */
2 | /* eslint-env browser */
3 |
4 | document.addEventListener('DOMContentLoaded', () => {
5 | const textarea = document.querySelector('textarea[name=source]');
6 | textarea.addEventListener('input', (e) => {
7 | refreshOutput(e.target.value);
8 | });
9 |
10 | refreshOutput(textarea.value);
11 | });
12 |
13 | function refreshOutput(html) {
14 | const output = document.querySelector('pp-output');
15 |
16 | output.innerText = '';
17 | output.appendChild(prettyParse(html));
18 | }
19 |
20 | class Lexer {
21 | constructor(source) {
22 | this.source = source;
23 | this.file_pointer = 0;
24 | }
25 |
26 | read() {
27 | if (this.file_pointer < 0 || this.file_pointer >= this.source.length) {
28 | return undefined;
29 | }
30 |
31 | return this.source[this.file_pointer++];
32 | }
33 |
34 | rewind() {
35 | this.file_pointer = 0;
36 | }
37 |
38 | match(token) {
39 | return this.remainder.search(token) === 0;
40 | }
41 |
42 | consumeMatch(token) {
43 | const match = this.remainder.match(token);
44 |
45 | if (match && match.length && match.index == 0) {
46 | this.file_pointer += match[0].length;
47 |
48 | return true;
49 | }
50 |
51 | return false;
52 | }
53 |
54 | readUntil(condition) {
55 | const start_pointer = this.file_pointer;
56 |
57 | while (!this.eof && !condition(this)) {
58 | this.file_pointer++;
59 | }
60 |
61 | return this.source.substring(start_pointer, this.file_pointer);
62 | }
63 |
64 | readIdentifier() {
65 | return this.readUntil((lexer) => !lexer.match(/\w/));
66 | }
67 |
68 | skipWhitespace() {
69 | return this.readUntil((lexer) => !lexer.match(/\s/));
70 | }
71 |
72 | get eof() {
73 | return this.file_pointer >= this.source.length;
74 | }
75 |
76 | get remainder() {
77 | return this.source.substring(this.file_pointer);
78 | }
79 | }
80 |
81 | function prettyParse(html) {
82 | const lexer = new Lexer(html);
83 |
84 | function parseComment() {
85 | const commentText = lexer.readUntil((lexer) => lexer.match('-->'));
86 | lexer.consumeMatch('-->');
87 |
88 | return document.createComment(commentText);
89 | }
90 |
91 | function parseContent() {
92 | let text = '';
93 | const fragment = document.createDocumentFragment();
94 |
95 | function flushText() {
96 | if (text.length) {
97 | fragment.appendChild(document.createTextNode(text));
98 | text = '';
99 | }
100 | }
101 |
102 | while (!lexer.eof) {
103 | if (lexer.consumeMatch('
30 | HTML5 Cheat Sheet
31 |
32 |
33 |
34 | Sectioning Content
35 |
36 | article
37 | Represents a complete, self-contained composition within a
38 | document.
39 | aside
40 | Content that isn't directly related to the parent section,
41 | frequently displayed as a sidebar.
42 | nav
43 | Contains links to other pages or sections within the
44 | current document.
45 | section
46 | A generic subgrouping of content within a larger document
47 | or application.
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 | Pretty Output
58 |
59 |
60 |
61 |
62 |
63 |