본문 바로가기
frontend

[TIL] javascript html parser

by marble25 2023. 12. 16.

API로부터 html string을 받아와서 동적으로 파싱해야 하는 기능이 있었다. 이 기능 구현하는 동안의 시행착오를 기록해두고자 한다.

#1. regex

처음 시도한 방법은 regex이다. Regular expression을 잘 사용하면 빠른 시간 안에 string에서 유용한 정보를 추출하는 것이 가능하고, filter out시키거나 replace 등 다양한 케이스에 사용하기 때문에 이전에도 많이 사용했던 방법이다.

const entities = html.split("\\n")
  .map(entity => {
    <b>\\s*(.+?)\\s*</b>
    const id = [...entity.matchAll(/id='entity-(\\w+)'/g)];
    if(!id || id.length === 0) return ["", entity];
    return [id[0][1], entity];
  })
  .filter(([id, entity]) => id !== "");

이 것이 가능했던 것은

  1. 태그들이 \\n 를 구분자로 해서 온다는 전제가 있었고,
  2. 이 태그들은 entity-XXXX 라는 아이디를 가진다는 전제가 있었기 때문이다.

그래서 [id, tag] 한 쌍으로 이루어진 array를 추출할 수 있었다. 한동안은 잘 동작하는 것으로 보였지만, API 스펙이 변경되면서 위의 전제가 모두 깨지게 되었다. nested tag도 가능하게 되었고, 태그들이 반드시 \\n를 구분자로 온다는 전제도 없다. 다른 방법을 찾아봐야 했다.

#2. dom parser

해결 방법을 서치하던 중 vanilla javascript에 DomParser interface가 존재한다는 것을 알았다.

html을 파싱해서 dom element로 자동으로 생성해주는 메소드로, 결과물이 html document이기 때문에 javascript에서 실제 html을 조작하는 것처럼 자유롭게 쓸 수 있다.

const parser = new DOMParser();
const htmlDoc = parser.parseFromString(html, 'text/html');
const tags = Array.from(htmlDoc.body.getElementsByTagName("*"));

문제가 해결된 듯 보였다. 하지만 실제 요청을 받아보니 제대로 안되는 케이스가 많았다. 확인해 보니 다음과 같이 잘못된 html 태그가 문제였다.

<table>this is test string<br></table>

table 내에 단순히 텍스트를 내장시키면 html에서는 다음과 같이 렌더링되어 나왔다.

<table></table>
this is test string<br>

API를 보내주는 측에서 정확한 html string을 보내주면 해결되는 문제였으나, 여기도 html을 text 기반으로 생성해주기 때문에 이 이외에도 오류가 생길 수 있는 가능성이 컸다. 결국 받은 html string을 기반으로 렌더링을 하는 frontend에서 backend에서 오류가 생길 수 있음을 가정하고 방어적으로 코딩해야 했다. dom parser는 좋지만, 정확한 html에 대해서만 올바르게 동작했다.

#3. node-html-parser

node에서 사용할 수 있는 또 다른 방법을 찾아보니 node-html-parser 패키지가 존재했다. Simplified DOM tree를 생성한다고 하니 full dom tree를 생성하는 dom parser보다 빠를 것으로 예상했고, 실제 벤치마크 결과도 나쁘지 않다고 되어 있었다.

html-parser     :24.1595 ms/file ± 18.7667
htmljs-parser   :4.72064 ms/file ± 5.67689
html-dom-parser :2.18055 ms/file ± 2.96136
html5parser     :1.69639 ms/file ± 2.17111
cheerio         :12.2122 ms/file ± 8.10916
parse5          :6.50626 ms/file ± 4.02352
htmlparser2     :2.38179 ms/file ± 3.42389
htmlparser      :17.4820 ms/file ± 128.041
high5           :3.95188 ms/file ± 2.52313
node-html-parser:2.04288 ms/file ± 1.25203
node-html-parser (last release):2.00527 ms/file ± 1.21317

사용하기는 정말 쉬웠다.

npm install --save node-html-parser
import { parse } from 'node-html-parser';

const root = parse(html);
const tags = root.getElementsByTagName("*");

일단 테스트해본 결과 위의 잘못된 html 태그에 대해서도 parsing이 가능했다.

<table>this is test string<br></table>

결론적으로, node-html-parser를 사용하기로 결정했다.

'frontend' 카테고리의 다른 글

[TIL] Prism.js: code prettier  (0) 2024.01.11
[TIL] npm ci vs npm install  (0) 2023.10.17
Canvas vs Svg  (0) 2023.09.10
JS로 파일 다운로드  (0) 2023.09.10
가로 세로 스크롤 되는 테이블 만들기  (0) 2023.04.24