HTML PARSER를 단순화하면, 아래와 같다.
A = <TAG>BODY</TAG>
B = <TAG/>
C = TEXT
const parser = (input) => {
input = input.trim();
const result = { name: 'ROOT', type: 'node', children: [] };
const stack = [{ tag: result }];
let curr,
i = 0,
j = input.length;
while ((curr = stack.pop())) {
// 받아온 문자열 끝까지 도는 스캐너
while (i < j) {
// i를 직접 조작하면 위험하므로 cursor로 지정
const cursor = i;
// i가 0일때 <가 아니면 태그가 아닐 것이다.
if (input[cursor] === '<') {
//A, B의 경우
} else {
//C의 경우 ~ text
const idx = input.indexOf('<', cursor);
// text는 tag의 자식으로 type text 넣는다.
curr.tag.children.push({
type: 'text',
text: input.substring(cursor, idx),
});
// < 가 끝나는 지점을 idx로 지정
i = idx;
}
}
}
return result;
};
이때, textNode쪽의 역할은 정해지고 다른 알고리즘에 영향을 끼치지 않으므로 함수화 한다. ~ 역할을 인식하면 바로 바꾼다
// textNode는 다른 알고리즘에 영향 안끼치므로 함수로 만듬
const textNode = (input, cursor, curr) => {
const idx = input.indexOf('<', cursor);
curr.tag.children.push({
type: 'text',
text: input.substring(cursor, idx),
});
// 값에 의한 i를 복사했으므로 return을 통해 변경
return idx;
};
const parser = (input) => {
input = input.trim();
const result = { name: 'ROOT', type: 'node', children: [] };
const stack = [{ tag: result }];
let curr,
i = 0,
j = input.length;
while ((curr = stack.pop())) {
while (i < j) {
const cursor = i;
if (input[cursor] === '<') {
} else {
i = textNode(input, cursor, curr);
}
}
}
return result;
};
TEXT가 아닌 TAG의 경우의 수를 고려하여 분기 한다.
(1) <a></a>
(2) <div>~<div>
(3) <img/>
const textNode = (input, cursor, curr) => {...};
const parser = (input) => {
input = input.trim();
const result = { name: 'ROOT', type: 'node', children: [] };
const stack = [{ tag: result }];
let curr,
i = 0,
j = input.length;
while ((curr = stack.pop())) {
while (i < j) {
const cursor = i;
if (input[cursor] === '<') {
const idx = input.indexOf('>', cursor);
i = idx + 1;
// <a></a> 일 경우
if (input[cursor + 1] === '/') {
} else {
// <img/>인 경우
// 화이트리스트 작업
// (1)공통 준비사항
let name, isClose;
if (input[idx - 1] === '/') {
(name = input.substring(cursor + 1, idx - 1)), (isClose = true);
} else {
// <div><div>인 경우
(name = input.substring(cursor + 1, idx)), (isClose = false);
}
// (2)공통 처리사항
const tag = { name, type: 'node', children: [] };
curr.tag.children.push(tag);
// isCloser 가 false인 경우 stack에 넣고 back에다가 curr 넣어 놓는다. ~ backpoint 잡는다.
if (!isClose) {
stack.push({ tag, back: curr });
break;
}
}
} else {
i = textNode(input, cursor, curr);
}
}
}
return result;
};
elementNode 역할 분리 후 함수화
const textNode = (input, cursor, curr) => {...};
const elementNode = (input, cursor, idx, curr, stack) => {
let name, isClose;
if (input[idx - 1] === '/') {
name = input.substring(cursor + 1, idx - 1);
isClose = true;
} else {
name = input.substring(cursor + 1, idx);
isClose = false;
}
const tag = { name, type: 'node', children: [] };
curr.tag.children.push(tag);
if (!isClose) {
stack.push({ tag, back: curr });
return true;
}
//플래그를 주어 break 실행
return false;
};
const parser = (input) => {
input = input.trim();
const result = { name: 'ROOT', type: 'node', children: [] };
const stack = [{ tag: result }];
let curr,
i = 0,
j = input.length;
while ((curr = stack.pop())) {
while (i < j) {
const cursor = i;
if (input[cursor] === '<') {
const idx = input.indexOf('>', cursor);
i = idx + 1;
if (input[cursor + 1] === '/') {
curr = curr.back;
} else {
if (elementNode(input, cursor, idx, curr, stack)) break;
}
} else {
i = textNode(input, cursor, curr);
}
}
}
return result;
};
결과