 |
|
마크업 렌더링
사용자들이 크리키에서 엔트리를 만들고 수정할 수 있게 되었다. 이제 이 엔트리를 읽을 수 있는 방식으로 만들어 보여줘야 한다. 즉, 마크업을 렌더링해야 한다.
위키 마크업이 데이터가 저장되기 전에 렌더링되지 않았음을 눈치챘을 것이다. 이것은 의도적인 디자인이다. 만약 데이터베이스에 내용을 작성하기 전에 위키 마크업이 HTML로 렌더링됐다면 사용자가 엔트리를 수정하고자 할 때마다 마크업을 원래대로 만들어야 한다. 엔트리 내용을 원래대로 돌려 놓는 것은 더 많은 문제를 일으킨다. 그 자체가 말이 안 되기 때문이다.
이 문제를 해결하는 더 나은 접근 방식은 사용자가 제출한 그대로 내용을 작성하고 엔트리를 보여줄 때 위키 마크업을 렌더링하는 것이다.
마크업 복습
크리키가 사용할 위키 마크업 목록을 위해 Part 1을 다시 보자. 이 목록은 적절한 개행문자 처리를 빠뜨렸다. 특히 크리키에서 개행문자는 다음과 같이 동작한다.
- 행 위의 개행문자 자체는 [missing text]로 렌더링된다.
- 목록 요소 끝 부분(*나 #으로 시작하는 행)의 개행문자는 그 목록 요소의 끝임을 나타낸다.
- 열린 목록이나 문단에서의 개행문자는 목록이나 문단을 닫는다.
이는 위키 마크업 코드 자체에 대해 작업하면서 더 확실해질 것이다. 시작하기 전에 테스트 목적으로 마크업으로 가득 찬 엔트리를 만들어보는 것이 도움이 될 것이다.
테스트 엔트리 설정하기
마크업 렌더링을 테스트할 때 볼 수 있는 테스트 엔트리가 있으면 도움이 된다. 테스트 엔트리에는 각 마크업 예제가 들어있으면 좋다. 다음 텍스트를 테스트를 위해 사용하자.
Listing 15. 마크업 검증하기
=== This is a h3 ===
''' this should be italic '''
!!! this should be bold !!!
___this should be underlined___
&&& this should be pre &&&
[[[ftp://foo.bar.com]]]
[[[ftp://foo.bar.com|not a real site]]]
[[[how to do it]]] [[[howtodoit]]] [[[How To Do It]]] \
[[[how_to_do_it]]] [[[howtodoit|How To Do It]]]
* this
* should
* be
* a
* list
# this
# should
# be
# a
# numbered list
---
http://cakephp.org
|
테스트용으로 편집할 수 있는 엔트리가 없다면 페이지를 편집하고 추가하는 것은 기본적으로 같다는 사실을 기억하자. http://localhost/entries/edit/markuptest에 가서 테스트 마크업을 붙여넣자. 엔트리를 저장한 후 보면 작업한 것이 어떤 것인지 볼 수 있다.
그림 2. 렌더링하지 않은 텍스트
이렇게 하면위키 마크업에 대해 모든 기초 긍정 테스트(마크업이 정상으로 보이는지 확인)을 다룰 수 있다. 마크업 렌더링을 위해 코드를 개발할 때 부정 테스트(마크업이 렌더링되지 말아야 할 모양으로 보이는 것)를 다루는 것 역시 중요하다.
마크업 렌더링하기
이제 마크업 렌더링이라는 핵심 주제를 다룰 때가 왔다. Part 1에서 이미 좀 다루었기 때문에 이 특정 작업(특히 중첩된 태그에서 발생하는 문제점)에 대해 이미 생각해봤을 것이다.
마크업 크리키는 세 가지 범주로 나눌 수 있다. 첫 번째는 ===, ''', !!! 등 같은 캐릭터 세트로 열고 닫기를 하는 마크업이다. 두 번째 범주에는 #나 *(개행문자는 태그를 닫는다)로 시작되는 행처럼 시작은 하지만 닫지는 않는 모든 마크업과 http://(빈칸이나 개행문자는 태그를 닫는다)로 시작되는 텍스트로서 링크를 직접 렌더링하는 것이 포함된다. 세 번째는 ---처럼 스스로 닫는 마크업과 개행문자다.
지금은 사용자가 위키 마크업을 입력할 때 틀릴 수 있다는 생각은 하지 말자. 기본 마크업 렌더링 작업을 한 후 원하는 대로 잘못된 코드를 받아들일지 수정할지 결정하면 된다.
이제 위키 마크업을 HTML로 렌더링하면 대체로 다음과 같이 보일 것이다.
- 유효한 마크업이 발견될 때까지 엔트리를 처리하자. 태그를 열고, 이것이 열렸음을 기억하자.
- 다음 마크업이 발견될 때까지 계속해서 엔트리를 처리하자. 새 태그의 경우엔 열고 연 태그는 닫자.
이 규칙의 예외는 &&& 마크업이다. 이 마크업은 <pre> 태그로 텍스트를 둘러싸므로 마크업 렌더링은 &&& 마크업을 처리하는 동안 작동하지 않을 것이고 열린 마크업 태그는 닫혀야 할 것이다.
어려운 방법으로 하기
어쩌면 위의 방식은 속임수처럼 느껴질 수 있다. 더 정확하게 하려면 "긴 형태로 입력"해야 한다.
다음은 위에서 만든 긍정 테스트 포스트를 통과하는 방법을 설명한 마크업을 어떻게 렌더링할 수 있는지를 다루는 예다. 뷰 동작 코드를 여러 부분으로 나누면서 entries_controller.php를 참조하여 그대로 따르면 된다.
변수 초기화하기
마크업 렌더링을 위해 변수 두 개를 사용할 것이다. 첫 번째로는 크리키가 마크업을 처리한다면 $processMarkup = true;를 사용한다.
두 번째 변수는 현재 처리중인 태그를 추적할 때 사용될 것이다.
Listing 16. 처리되고 있는 태그 추적하기
$processing = array(
"&&&" => false,
"===" => false,
"" => false,
"!!!" => false,
"___" => false,
"[[[" => false,
"]]]" => false,
"*" => false,
"#" => false,
);
|
배열 키는 마크업 태그 자체다. 키의 값은 false로 초기화된다.
이제 초기 변수를 가지므로 행을 처리할 수 있다.
행 처리
내용 행을 처리하기 위해 개행문자 $lines = explode("\n", $content);의 내용을 배열로 만드는 것에서 시작한다.
그리고 그 행들의 각각의 첫 문자를 주의 깊게 보자. 목록 엔트리는 늘 행의 처음에서 시작하므로 목록 엔트리를 먼저 처리할 수 있다.
Listing 17. 배열로 만들어진 내용의 첫 문자 확인하기
switch (substr($line, 0, 1)) {
case "*" :
if ($processMarkup) {
if (!$processing["*"]) {
$processing["*"] = true;
$line = " <ul><li> " . substr($line, 1) . " </li> ";
} else {
$line = " <li> " . substr($line, 1) . " </li> ";
}
}
break;
|
이는 $processMarkup이 true인지 확인하고, 만약 그렇다면 계속 처리한다. 만약 만난 마크업(여기선 정렬되지 않은 목록)이 지금 처리되고 있지 않다면, 필수 HTML 엔티티는 열려있고 처리 플래그는 설정된 것이다. 그리고 나면 행을 <li> tags 태그로 감싸고 초기 문자(마크업 자체)는 제외된다. 이 기본적인 접근이 나머지 위키 마크업을 렌더링할 때 사용될 것이다.
switch문의 기본 경우가 열린 마크업을 닫을 때 사용되고 처리 플래그를 리셋하며 <br /> 태그를 추가한다.
행이 처리되면 지속적으로 낱말을 처리할 수 있다.
낱말 처리하기
텍스트를 계속 처리하면서 여백에서 처리된 행을 배열로 만들고 각 낱말을 개별적으로 처리한다.
Listing 18. 각 낱말 개별적으로 처리하기
$words = explode(" ", $line);
foreach ($words as $word) {
$word = trim($word);
|
중요한 것은 지금 다루는 것이 보편적으로 말하는 낱말이라는 것을 이해하는 것이다. 이는 문자 블록으로 앞과 뒤에 빈칸이 있다. 이 텍스트 블록은 URL이 될 수도 있고, 수학 공식이 될 수도 있으며 잘못된 구두점 결정의 연속일 수도 있다. 편의상 낱말이라고 지칭하는 것이다.
첫 번째 프로세싱
각 낱말은 두 가지 방식으로 보여야 한다. 낱말의 처음에(첫 세 문자) ===This와 같은 시작 마크업을 가지고 있는지 확인해 봐야 한다. 시작 마크업이 보일 때마다 만약 $processMarkup이 true고 마크업이 처리되지 않았다면 마크업은 열려야 한다. 만약 $processMarkup이 true고 마크업이 처리중이라면 마크업은 닫혀야 한다. Listing 19는 예를 보여준다.
Listing 19. 첫 세 문자 확인하기
case "===" :
if ($processing["==="] && $processMarkup) {
$processing["==="] = false;
$word = '</h3>' . substr($word,3);
} else {
$processing["==="] = true;
$word = '<h3>' . substr($word,3);
}
break;
|
이제 $processMarkup에 대해 확인하는 것을 여러 차례 봤을 것이다. 이는 &&& 마크업을 처리하면서 나온다.
Listing 20. &&& 마크업 처리
case '&&&' :
if ($processing["&&&"]) {
$processing["&&&"] = false;
$processMarkup = true;
$word = '</pre>' . substr($word,3);
} else {
$processing["&&&"] = true;
$processMarkup = false;
$word = '<pre>' . substr($word,3);
}
break;
|
&&& 마크업을 처리하는 데 있어 다른 점은 마크업이 처리를 시작할 때 $processMarkup이 false로 설정되고 &&& 마크업이 완전히 처리되기 전까지는 꺼지지 않는다는 것이다.
첫 번째 문자 처리의 두 가지 다른 예외는 --- 마크업(이는 스스로 닫기로 처리가 될 때 이를 추적할 필요가 없다는 뜻이다)과 "htt"(URLs 처리를 위한)다. 이 경우의 코드는 스스로 설명할 수 있어야 한다.
첫 번째 처리의 기본 경우는 두 번째 처리를 보기 전까지는 선뜻 이해가 가지 않을 것이다. 하지만 요약을 하자면 "내가 링크를 처리한다면 나는 마지막 낱말이 아닐 것이고, 낱말을 내용 더미가 아닌 링크로 밀어붙일 것이다"라는 뜻이다.
두 번째 프로세싱
지금까지 낱말의 처음을 보아왔다면 이제는 같은 낱말의 마지막을 보아야 한다. 특히 낱말 하나가 ===LikeThis=== 마크업에 포함되어 있다면 더 그렇다. 기본적으로 두 번째는 링크의 마지막을 표시하는 ]]] 마크업의 예외와 함께 첫 번째 프로세싱과 거의 비슷하게 보인다. 코드를 살펴보자.
Listing 21. 낱말의 마지막 보기
case "]]]" :
if ($processing["]]]"] && $processMarkup) {
if (!$processing["[[["]) {
$processing["]]]"] .= ' ' . substr($word,0,-3);
} else {
$processing["]]]"] = substr($processing["]]]"],0,-3);
}
|
[[[ / ]]]는 가장 까다로운데 그 이유는 행이 아닌 낱말을 메우고 다음 낱말이 무엇이냐에 따라 출력이 달라지기 때문이다. [[[ 태그가 보일 때 두 처리 플래그가 설정되었다. processing[']]]'] 태그가 설정되어 낱말이 보이는 동안 processing['[[['] 플래그가 true로 설정되었다. 첫 번째 프로세싱의 기본 경우 때문에 다음에 오는 각 낱말은 processing[']]]'] 플래그에 추가된다. 하지만 만약 마크업이 [[[likethis]]]와 같은 형식으로 낱말 하나를 둘러 쌓고 있다면 그 스택에 추가되지 않아야 한다.
Listing 22. | 문자를 위해 링크 확인하기
if (strpos($processing["]]]"], "|")) {
list($alink, $atitle) = explode('|', $processing["]]]"]);
} else {
$alink = $processing["]]]"];
$atitle = false;
}
|
| 문자(제목과 함께 링크 표시)를 위해 링크를 확인하자. 만약 제목이 있다면 이를 추출하고, 제목이 없다면 제목을 false로 설정한다.
Listing 23. 링크가 외부 사이트인지 확인하기
if (strpos($alink, "://")) {
$word = "<a href='" . $alink . "'>";
} else {
$word = "<a href='/entries/view/" .
strtolower($alink)) ."'>";
}
|
링크가 외부 사이트면 그 사이트로 연결한다. 그렇지 않다면 링크를 엔트리 제목으로 다룬다.
Listing 24. 링크를 제목으로 사용하기
if ($atitle) {
$word .= $atitle;
} else {
$word .= $alink;
}
|
제목이 없다면 링크를 제목으로 사용한다.
Listing 25. 링크 마무리 짓고 닫기
$word .= "</a>";
$processing["[[["] = false;
$processing["]]]"] = false;
}
break;
|
마지막으로 링크를 닫고 관련된 처리 태그를 모두 지운다.
링크 처리는 가장 까다로운 것이 되기 쉽다. 두 번째 프로세싱의 나머지 부분은 첫 번째 프로세싱과 거의 비슷하다.
코드를 이해한 것 같으면 컨트롤러를 복사하고 앞에서 만든 테스트 엔트리를 본다. 이는 그림 3처럼 보여야 한다.
그림 3. 렌더링된 텍스트
부정 테스트의 경우를 생각해보자. 나중에 다뤄야 하기 때문이다.
|