[PHP] 한국어 조사 처리 함수

Updated: 코드에 버그가 있어 2017년 12월 14일에 수정했습니다.

Updated: "로"와 "으로"도 지원하도록 수정했습니다. (2021. 01. 14)

트위터에서 녹풍님의 [PHP] 명사 뒤에 나오는 ‘은는이가을를’ 판단할 수 있게 해 주는 함수를 봤는데, 조사가 없는 글자를 판단하기 위해 죄다 문자열에 넣어두는 식으로 구현했더군요. 그런데 유니코드는 완성형과 달리 규칙적인 문자 체계라서 코드만으로도 조사의 여부를 판단할 수 있기 때문에 굳이 이런 식으로 번거롭게 작성하지 않아도 됩니다.

그래서 대략 10년 전쯤에 작성했던  한국어 조사를 판단해주는 함수의 자바스크립트로 코드를 가져와서 PHP로 만들어보았습니다. 또한 아마도 메시지 템플릿을 처리할 때 편하게 사용할 수 있도록 "{은}"과 같이 중괄호를 조사 양쪽에 씌워두면 해당 부분을 자동으로 판단해서 치환해주도록 바꿨습니다. 편의를 위해 "은" 또는 "는", "이" 또는 "가", "을" 또는 "를" 중 무엇을 사용해도 상관없도록 했습니다. 다만 "로"와 "으로" 중에서는 한 글자인 "로"만 사용할 수 있습니다.

클로저를 사용했기 때문에 PHP 5.3 이상이 필요하지만, preg_replace_callback에 사용된 콜백 함수를 따로 빼면 조금 더 하위 버전에서도 사용할 수 있습니다. 파일의 인코딩 및 mb_* 계열 함수 내부 인코딩은 UTF-8으로 설정되어 있다고 가정했습니다.

function josa($str) {
  $josa = '이가은는을를과와으로';

  return preg_replace_callback(
    "/(.)\\{([{$josa}])\\}/u",
    function($matches) use($josa) {
      list($_, $last, $pp) = $matches;

      $pp1 = $pp2 = $pp;
      $idx = mb_strpos($josa, $pp);
      ($idx % 2) ? ($pp1 = mb_substr($josa,--$idx,1)) : ($pp2 = mb_substr($josa,++$idx,1));
      $pp1 .= ($pp1 === '으') ? $pp2 : '';

      if (strlen($last) > 1) {
        $last_ucs2 = mb_convert_encoding($last, 'UCS-2BE', 'UTF-8');
        $code = (hexdec(bin2hex($last_ucs2)) - 16) % 28;
      } else {
        $code = (strpos('2459', $last) > -1) ? 0 : 1;
      }

      return $last.($code ? $pp1 : $pp2);
    },
    $str
  );
}

// 사용예
echo josa('한글{와} 컴퓨터의 한글2015{은} 언제 출시될까.'), "\n";
echo josa('영등포{은} 문제없습니다.'), "\n";
echo josa('집{로} 가는 길'), "\n";

데모 링크에서 실제 동작하는 예제를 볼 수 있습니다.

  1. 안녕하세요. 오래된 글이라 답변을 받을수 있을지는 모르겠지만 남겨 봅니다.
    좋은 코드 감사합니다. 엄청 필요한 코드였어요.
    근데 ㄷ, ㅍ 등 일부 처리가 되지 않는 케이스가 있는데 왜인지 알수가 없어서요.
    예를들면 josa('영등포{은}') 이라고 실행시 영등포는을 기대 했는데 영등포은 을 그대로 출력 하더라고요.
    혹시나 하는마음에댓글 남겨 봅니다.
    저만 그런가요?ㅜㅜ

    1. 안녕하세요. 댓글을 보고 확인해보니 코드에 버그가 있었습니다. ㅠㅠ UTF-8 한글 코드의 배열이 제 생각하고 달랐는데 그걸 간과하고 코드를 작성했던 게 문제였습니다. 지금은 수정해두었고 코드 아래 데모 링크도 추가해두었으니 확인해보세요~

  2. 안녕하세요. 정말 유용하게 잘 사용하고 있습니다. 좋은 정보 감사합니다.
    사용하다보니 한가지 추가되었으면 좋겠다 싶은 것이 있어서요.
    '을를이가'... 같은 주격 조사와 목적격 조사는 처리가 완벽합니다만,
    혹시 '로' '으로' 같은 것도 처리가 가능할까요?
    이 댓글을 보실지는 모르겠지만 혹시나 싶어서 글 남겨봅니다.
    감사합니다.

    1. 오랫동안 잊어먹고 있다가 오늘 우연히 이 글을 발견해서 요청에 따라 코드를 업데이트 해보았습니다. 혹시 다른 분이라도 필요하신 분이 있다면 유용하게 사용하셨으면 합니다.

댓글을 남겨주세요

This site uses Akismet to reduce spam. Learn how your comment data is processed.