본문 바로가기
Converter

HTML 엔티티 인코더 · 디코더

< > & 를 엔티티로, 엔티티를 문자로 양방향 변환

< > & " ' 를 엔티티로 바꿉니다. 한글·이모지는 옵션에 따라 처리합니다

왜 엔티티가 필요한가: HTML은 <를 명령으로 읽는다

HTML에서 <는 “여기서 태그가 시작된다”는 명령입니다. 그래서 화면에 부등호 <글자로보여 주려면, 브라우저에게 “이건 태그의 시작이 아니라 그냥 문자야”라고 따로 말해 줘야 합니다. 그 장치가 엔티티입니다. 3 < 5를 그대로 쓰면 브라우저는 < 5를 정체불명의 태그로 여겨 삼켜 버립니다. 3 &lt; 5라고 써야 “3 < 5”로 보입니다.

엔티티는 항상 &로 시작해 ;로 끝납니다. 이 두 기호 사이에 어떤 문자를 원하는지를 세 가지 방식 중 하나로 적습니다 — 그리고 세 방식은 전부 같은 글자를 가리킵니다.

<   →   &lt;      (이름 named)
    →   &#60;     (10진 숫자)
    →   &#x3C;    (16진 숫자)

이름 방식은 사람이 읽기 좋지만 외워야 할 이름이 약 2,000개입니다. 숫자 방식은 유니코드 코드포인트를 그대로 적는 것이라 어떤 문자든 표현할 수 있고 표가 필요 없습니다. 위 변환기가 디코딩할 때 숫자 엔티티는 항상 정확히 풀고, 이름 엔티티는 자주 쓰는 것만 처리하는 이유가 이 차이에 있습니다.

핵심 다섯 글자, 그리고 &가 특별한 이유

HTML에서 실제로 조심해야 하는 글자는 다섯 개뿐입니다.

  • < (&lt;) — 태그의 시작
  • > (&gt;) — 태그의 끝
  • & (&amp;) — 엔티티 자신의 시작
  • " (&quot;) — 큰따옴표 속성값의 경계
  • ' (&#39;) — 작은따옴표 속성값의 경계

이 중 &가 가장 미묘합니다. 엔티티가 &로 시작하니까, & 자체를 글자로 쓰려면 그것도 엔티티로 감싸야 합니다. 안 그러면 브라우저가 & 뒤를 엔티티 이름으로 해석하려 듭니다. AT&T는 대개 무사하지만, 우연히 & 뒤가 실제 엔티티 이름과 겹치면 엉뚱한 문자가 튀어나옵니다. 그래서 텍스트의 &는 예외 없이 &amp; 씁니다.

이중 인코딩 — 가장 흔한 사고

같은 이유로 가장 자주 나는 실수가 이중 인코딩입니다. 이미 엔티티로 바뀐 문자열을 한 번 더 인코딩하면, &amp;&가 또 인코딩되어 &amp;amp;가 됩니다. 화면에는 &amp;라는 글자가 그대로 보이죠. 서버에서 인코딩하고 템플릿 엔진이 또 인코딩하는 식으로 파이프라인에 인코딩 단계가 두 번 끼면 이렇게 됩니다. 위 변환기로 디코딩을 한 번 돌려서 &amp;amp;가 나오면 어딘가에서 두 번 인코딩되고 있다는 신호입니다.

여기서부터가 진짜 중요합니다: 맥락이 인코딩을 정한다

“특수문자를 엔티티로 바꾸면 안전하다”는 말은 절반만 맞습니다. 무엇을 인코딩해야 하는지는 그 값이 HTML의 어디에 들어가느냐에 따라 달라집니다. 변환기 화면은 이걸 대신 판단해 주지 못합니다 — 그래서 여기 적습니다.

  • 태그 사이 본문 <p>여기</p> — 위험한 건 <, >, &뿐입니다. 따옴표는 그냥 써도 됩니다.
  • 큰따옴표 속성값 value="여기" — 위 셋에 더해 "가 위험합니다. 값 안의 "가 속성을 조기에 닫아 버리기 때문입니다.
  • 작은따옴표 속성값 value='여기' — 이번엔 '가 경계라 &#39;로 막아야 합니다.

그리고 엔티티가 아예 통하지 않는 자리가 있습니다. 이게 XSS의 단골 경로입니다.

  • <script>은 자바스크립트라, HTML 엔티티가 아니라 자바스크립트 문자열 이스케이프가 필요합니다.
  • href·src 같은 URL 속성 안은 javascript: 스킴이 들어갈 수 있어, 엔티티로 감싸도 클릭 한 번에 실행됩니다. 여기는 URL 검증·인코딩의 몫입니다.
  • onclick 같은 이벤트 속성은 HTML이면서 동시에 자바스크립트라 가장 까다롭습니다. &quot;만으로는 막이지 않습니다.

요점은 하나입니다. 엔티티 인코딩은 “HTML 맥락”을 위한 도구입니다. 이 도구는 그 변환을 정확히 해 주지만, 값이 실제로 HTML 본문에 들어가는지 스크립트 안에 들어가는지는 코드를 짜는 사람이 판단해야 합니다.

URL 인코딩과 헷갈리지 않기

두 인코딩은 이름이 비슷해 자주 뒤섞입니다. 나란히 놓으면 분명해집니다.

<   HTML 엔티티 →  &lt;     (HTML 파서용)
<   URL 인코딩  →  %3C      (URL 파서용)

둘은 서로 다른 계층이라 한 문자열에 겹쳐 나올 수 있습니다. 예를 들어 검색어를 링크로 만들 때는 먼저 URL 인코딩을 하고, 그 결과를 HTML 속성에 넣으니 다시 엔티티 인코딩을 합니다.

검색어:  <b>
URL 인코딩:      %3Cb%3E
HTML 속성에 삽입: <a href="/s?q=%3Cb%3E">

퍼센트 인코딩 쪽이 궁금하면 URL 인코더에서 같은 원리를 양방향으로 확인할 수 있습니다. 핵심만 기억하면 됩니다 — %는 URL, &는 HTML.

이모지와 비ASCII: 코드포인트 하나가 엔티티 하나

비ASCII 문자를 숫자 엔티티로 바꿀 때 조용히 틀리기 쉬운 지점이 있습니다. 이모지는 자바스크립트 문자열에서 두 조각(서로게이트 쌍)으로 저장됩니다. 문자열을 글자 단위로 어설프게 훑으면 😀(U+1F600)가 &#xD83D;&#xDE00;라는 깨진 엔티티두 개로 나옵니다. 올바른 값은 코드포인트 하나짜리 &#x1F600;입니다.

위 변환기는 코드포인트 단위로 순회하므로 이모지가 쪼개지지 않습니다. 이 “글자 수와 코드포인트 수가 다르다”는 감각은 글자수를 셀 때도 똑같이 문제가 되는데, 글자수 세기에서 같은 이야기를 다룹니다.

정리

HTML 엔티티는 “이 문자를 마크업이 아니라 글자로 읽어라”는 신호입니다. 핵심은 다섯 글자(< > & " ')이고, 그중 &는 엔티티 자신의 시작이라 특히 조심해야 하며, 무엇을 인코딩할지는 값이 들어가는 자리(본문·속성·스크립트)가 정합니다. URL 인코딩과는 계층이 다르고, 이모지는 코드포인트 하나가 엔티티 하나입니다. 디코딩할 때 이 도구는 아는 것만 풀고 모르는 엔티티는 그대로 둡니다 — 지어내지 않는 것이 지우거나 틀리게 바꾸는 것보다 낫기 때문입니다.

자주 묻는 질문

HTML 엔티티와 URL 인코딩은 뭐가 다른가요?
둘 다 '인코딩'이지만 서로 다른 문제를 풉니다. HTML 엔티티(&lt;)는 HTML 파서에게 '이건 태그가 아니라 글자다'라고 말하는 것이고, URL 인코딩(%3C)은 URL 파서에게 '이건 구분자가 아니라 값이다'라고 말하는 것입니다. 계층이 달라서 한 문자열에 둘이 겹쳐 나오기도 합니다. HTML 안에 URL을 넣으면 URL 인코딩을 먼저 하고, 그 결과를 다시 HTML 엔티티로 감쌉니다.
엔티티로 인코딩하면 XSS가 막히나요?
맥락에 따라 다릅니다. HTML 본문이나 속성값에 사용자 입력을 넣을 때는 엔티티 인코딩이 맞는 방어입니다. 하지만 <script> 안(자바스크립트), <style> 안(CSS), 또는 href/src 같은 URL 속성 안에서는 HTML 엔티티가 XSS를 막지 못합니다. 그 자리에는 각각 자바스크립트 이스케이프, CSS 이스케이프, URL 인코딩이 필요합니다. '엔티티로 바꿨으니 안전하다'는 생각이 오히려 취약점을 만드는 이유입니다.
&amp; 를 그냥 & 로 쓰면 안 되나요?
대부분 브라우저가 관대해서 그냥 보이기도 하지만, 표준적으로는 틀립니다. & 는 엔티티의 시작 문자라서, & 뒤에 오는 글자를 브라우저가 엔티티로 해석하려 시도합니다. 예를 들어 'Tom & Jerry'의 '& J...'는 대개 그대로 보이지만, 'AT&T'를 'AT&Times'처럼 우연히 엔티티 이름과 겹치게 쓰면 엉뚱한 문자가 나옵니다. 그래서 텍스트의 & 는 항상 &amp; 로 쓰는 것이 안전합니다.
한글도 엔티티로 바꿔야 하나요?
아니요. 문서가 UTF-8이면(요즘은 거의 전부) 한글·이모지를 그대로 써도 됩니다. 엔티티로 바꿔야 하는 건 HTML에서 특별한 의미를 갖는 다섯 글자(< > & " ')뿐입니다. 한글을 숫자 엔티티로 바꾸는 '비ASCII 전부' 옵션은 레거시 이메일이나 ASCII만 허용하는 XML 통로처럼 특수한 경우에만 씁니다.
&apos; 를 써도 되나요?
HTML5에서는 됩니다. 다만 &apos;는 원래 XML 엔티티라 아주 오래된 HTML4 환경에서는 인식되지 않을 수 있습니다. 어디서나 확실히 통하는 건 숫자 엔티티 &#39;입니다. 그래서 이 도구는 작은따옴표를 인코딩할 때 상황에 맞게 named 또는 숫자로 낼 수 있게 해 둡니다.
모르는 엔티티는 왜 그대로 두나요?
HTML5의 named 엔티티는 약 2,000개라 이 도구는 그중 실무에서 자주 보는 것만 표로 갖고 있습니다. 표에 없는 이름(&foobar; 같은)을 만나면 지어내지 않고 원문 그대로 둡니다 — 이건 브라우저가 하는 것과 같은 동작이고, 조용히 지우거나 물음표로 바꾸는 것보다 정직합니다. 반면 숫자 엔티티(&#169;, &#xA9;)는 코드포인트를 직접 계산하므로 표와 무관하게 항상 정확히 풀립니다.