アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【C/C++
C言語用最新NKFライブラリlibnkf-2.1.5を作成して文字コード変換
POSTED BY
2023-08-30

libiconv (iconv) はヘッダファイルの数、ライブラリサイズも膨大な割に普通に文字化けしたりする。
もう1つの文字コード変換の定番「nkf」を使うべき。現在も以下サイトでメンテナンスされている。

https://ja.osdn.net/projects/nkf/

しかしながら現在、PerlおよびPython用にはライブラリ化されているが、C/C++言語から直接呼べるライブラリ化はされていないので、自作することにする。

といってもPython用にライブラリ化しているコードが大きなヒントになっており、これを少し改造するだけでC言語用ライブラリは出来上がる。

まずは最新版ソース一式をダウンロードする。上記サイトからnkf-2.1.5.tar.gzを探して落とすか、リポジトリをチェックアウトする。

git clone https://scm.osdn.net/gitroot/nkf/nkf.git

この中の、NKF.python3/NKF_python.cをもとに、libnkfを作成する。
チェックアウトしたnkfディレクトリと同じ階層に作成して../nkf/とソースを参照する形になる。
HOME
--nkfディレクトリ
--libnkfディレクトリ/libnkf.c,libnkf.h,Makefile

mkdir libnkf
cp -p nkf/NKF.python3/NKF_python.c libnkf/libnkf.c
cd libnkf

まず、ヘッダファイルlibnkf.hを作る。以下のような感じになる。

C/C++libnkf.hGitHub Source
/* Custom NKF Library for C/C++ from NKF.python3/NKF_python.c */
#ifndef __libnkf_h__
#define __libnkf_h__

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C" {
#endif

extern unsigned char*
nkf_convert(unsigned char* str, int strlen, char* opts, int optslen);

extern const char*
nkf_guess(unsigned char* str, int strlen);

#ifdef __cplusplus
}   /* extern "C" */
#endif

#endif /* __libnkf_h__ */

nkf_convertが主役関数。NKFコマンドそのもの。
strに入力文字列、strlenにstrlen(str)、optsにNKFコマンドオプション、optslenにstrlen(opts)を指定して呼ぶ。
変換結果がメモリ確保されて返るので、呼び出し側でfreeすること。

nkf_guessは、入力文字列の文字コードを判定して結果を名前で返す関数。
strに入力文字列、strlenにstrlen(str)を指定して呼ぶ。結果はコード名文字列定数が返るので、freeの必要は無い。

そして、libnkf.cを編集する。以下のような感じ。NKF_python.c元コード部分はコメントアウトで残している。
基本的にPython専用の固有な記述をすべてコメントアウトし、malloc/freeは標準関数に置き換える。
../utf8tbl.c,nkf.cとincludeしている箇所があるので、これの階層を../nkfに変更する

C/C++libnkf.cGitHub Source
/* Custom NKF Library for C/C++ from NKF.python3/NKF_python.c */
/*
Changes.
2009.6.2    Remove WISH_TRUE, use get_guessed_code() for nkf-2.0.9
                 by SATOH Fumiyasu (fumiyas @ osstech co jp)
2008.7.17   Change the type of strlen from long to int, by SATOH Fumiyasu.
2007.2.1    Add guess() function.
2007.1.13   Remove pynkf_parseopts(), by SATOH Fumiyasu.
*/
/**  Python Interface to NKF
***************************************************************************
**  Copyright (c) 2005 Matsumoto, Tadashi <ma2@city.plala.jp>
**  All Rights Reserved.
**
**    Everyone is permitted to do anything on this program
**    including copying, modifying, improving,
**    as long as you don't try to pretend that you wrote it.
**    i.e., the above copyright notice has to appear in all copies.
**    Binary distribution requires original version messages.
**    You don't have to ask before copying, redistribution or publishing.
**    THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE.
***************************************************************************/
/*
#include "Python.h"
*/
#include "libnkf.h"
#include <setjmp.h>

#undef getc
#undef ungetc
#define getc(f)         pynkf_getc(f)         
#define ungetc(c,f)     pynkf_ungetc(c,f)

#undef putchar
#undef TRUE
#undef FALSE
#define putchar(c)      pynkf_putchar(c)

static int pynkf_ibufsize, pynkf_obufsize;
static unsigned char *pynkf_inbuf, *pynkf_outbuf;
static int pynkf_icount,pynkf_ocount;
static unsigned char *pynkf_iptr, *pynkf_optr;
static jmp_buf env;
static int pynkf_guess_flag;

static int 
pynkf_getc(FILE *f)
{
  unsigned char c;
  if (pynkf_icount >= pynkf_ibufsize) return EOF;
  c = *pynkf_iptr++;
  pynkf_icount++;
  return (int)c;
}

static int 
pynkf_ungetc(int c, FILE *f)
{
  if (pynkf_icount--){
    *(--pynkf_iptr) = c;
    return c;
  }else{ return EOF; }
}

static void
pynkf_putchar(int c)
{
  size_t size;
  unsigned char *p;

  if (pynkf_guess_flag) {
    return;
  }

  if (pynkf_ocount--){
    *pynkf_optr++ = c;
  }else{
    size = pynkf_obufsize + pynkf_obufsize;
/*
    p = (unsigned char *)PyMem_Realloc(pynkf_outbuf, size + 1);
*/
    p = (unsigned char *)realloc(pynkf_outbuf, size + 1);
    if (pynkf_outbuf == NULL){ longjmp(env, 1); }
    pynkf_outbuf = p;
    pynkf_optr = pynkf_outbuf + pynkf_obufsize;
    pynkf_ocount = pynkf_obufsize;
    pynkf_obufsize = size;
    *pynkf_optr++ = c;
    pynkf_ocount--;
  }
}

#define PERL_XS 1
/*
#include "../utf8tbl.c"
#include "../nkf.c"
*/
#include "../nkf/utf8tbl.c"
#include "../nkf/nkf.c"

/*
static PyObject *
pynkf_convert(unsigned char* str, int strlen, char* opts, int optslen)
*/
extern unsigned char*
nkf_convert(unsigned char* str, int strlen, char* opts, int optslen)
{
/*
  PyObject * res;
*/

  pynkf_ibufsize = strlen + 1;
  pynkf_obufsize = pynkf_ibufsize * 1.5 + 256;
/*
  pynkf_outbuf = (unsigned char *)PyMem_Malloc(pynkf_obufsize);
*/
  pynkf_outbuf = (unsigned char *)malloc(pynkf_obufsize);

  if (pynkf_outbuf == NULL){
/*
    PyErr_NoMemory();
*/
    return NULL;
  }
  pynkf_outbuf[0] = '\0';
  pynkf_ocount = pynkf_obufsize;
  pynkf_optr = pynkf_outbuf;
  pynkf_icount = 0;
  pynkf_inbuf  = str;
  pynkf_iptr = pynkf_inbuf;
  pynkf_guess_flag = 0;

  if (setjmp(env) == 0){

    reinit();

    options(opts);

    kanji_convert(NULL);

  }else{
/*
    PyMem_Free(pynkf_outbuf);
    PyErr_NoMemory();
*/
    free(pynkf_outbuf);
    return NULL;
  }

  *pynkf_optr = 0;
/*
  res = PyBytes_FromString(pynkf_outbuf);
  PyMem_Free(pynkf_outbuf);
  return res;
*/
  return pynkf_outbuf;
}

/*
static PyObject *
pynkf_convert_guess(unsigned char* str, int strlen)
*/
extern const char*
nkf_guess(unsigned char* str, int strlen)
{
/*
  PyObject * res;
*/
  const char *codename;

  pynkf_ibufsize = strlen + 1;
  pynkf_icount = 0;
  pynkf_inbuf  = str;
  pynkf_iptr = pynkf_inbuf;

  pynkf_guess_flag = 1;
  reinit();
  guess_f = 1;

  kanji_convert(NULL);

  codename = get_guessed_code();

/*
  res = PyUnicode_FromString(codename);
  return res;
*/
  return codename;
}

/*
#ifndef EXTERN_NKF
static
#endif
PyObject *pynkf_nkf(PyObject *self, PyObject *args)
{
  unsigned char *str;
  int strlen;
  char *opts;
  int optslen;
  PyObject* res;

  if (!PyArg_ParseTuple(args, "s#y#", &opts, &optslen, &str, &strlen)) {
    return NULL;
  }
  res = pynkf_convert(str, strlen, opts, optslen);
  return res;
}

#ifndef EXTERN_NKF
static
#endif
PyObject *pynkf_guess(PyObject *self, PyObject *args)
{
  unsigned char *str;
  int strlen;
  PyObject* res;

  if (!PyArg_ParseTuple(args, "y#", &str, &strlen)) {
    return NULL;
  }
  res = pynkf_convert_guess(str, strlen);
  return res;
}

#ifndef EXTERN_NKF
static PyMethodDef
nkfMethods[] = {
  {"nkf", pynkf_nkf, METH_VARARGS, ""},
  {"guess", pynkf_guess, METH_VARARGS, ""},
  {NULL, NULL, 0, NULL}
};

static struct PyModuleDef nkfmodule = {
  PyModuleDef_HEAD_INIT,
  "nkf",
  "",
  -1,
  nkfMethods
};
*/

/* Module initialization function */
/*
PyMODINIT_FUNC
PyInit_nkf(void)
{
  return PyModule_Create(&nkfmodule);
}
#endif
*/

ソースコードの準備ができたので、スタティックライブラリlibnkf.a、シェアードライブラリlibnkf.so.1.0をビルドするMakefileを以下のように作成する。

sourceMakefileGitHub Source
CC    =  gcc
#CC_DBG  +=  -g
CC_OPT  +=
CC_INC  +=
CFLAGS  +=  $(CC_OPT) $(CC_DBG) $(CC_INC)
DESTLIB  =  /usr/local/lib
DESTINC  =  /usr/local/include

all:  libnkf.a libnkf.so.1.0

libnkf.oa:  libnkf.c libnkf.h
  $(CC) $(CFLAGS) -o $@ -c $<

libnkf.os:  libnkf.c libnkf.h
  $(CC) $(CFLAGS) -fPIC -o $@ -c $<

libnkf.a:  libnkf.oa
  ar rv $@ $?
  ranlib $@

libnkf.so.1.0:  libnkf.os
  $(CC) -shared -Wl,-soname,libnkf.so.1 -o libnkf.so.1.0 $?

clean:
  rm -f *.o* *.a *.so*

install:  all
  install libnkf.a $(DESTLIB)
  install libnkf.so.1.0 $(DESTLIB)
  ldconfig $(DESTLIB)
  ln -f -s -r $(DESTLIB)/libnkf.so.1 $(DESTLIB)/libnkf.so
  install -m 0644 libnkf.h $(DESTINC)

gccコンパイルオプションの異なるスタティックとシェアードを同時に作るのでオブジェクトファイルも.oでなく.oa、.osなどと分ける必要がある。
シェアードライブラリをビルドする場合-shared -Wl,-soname,libxxx.so.1とするのはgccの固定ルールである。

make clean
make

gcc    -o libnkf.oa -c libnkf.c
ar rv libnkf.a libnkf.oa
ar: libnkf.a を作成しています
a - libnkf.oa
ranlib libnkf.a
gcc    -fPIC -o libnkf.os -c libnkf.c
gcc -shared -Wl,-soname,libnkf.so.1 -o libnkf.so.1.0 libnkf.os

簡単に使うなら、出来上がったlibnkf.aをそのままリンクして使うことができる。

gcc -o sample.x sample.c libnkf.a

システム/usr/localにヘッダとライブラリをインストールするなら、以下sudoで入れる。

sudo make install

install libnkf.a /usr/local/lib
install libnkf.so.1.0 /usr/local/lib
ldconfig /usr/local/lib
ln -f -s /usr/local/lib/libnkf.so.1 /usr/local/lib/libnkf.so
install -m 0644 libnkf.h /usr/local/include

/usr/local/include/libnkf.h、/usr/local/lib/libnkf.aが入り、さらにシェアードライブラリlibnkf.so.1.0をコピー後ldconfigによりlibnkf.so.1が生成されるのでlibnkf.soとしてリンクが貼られて完了。

/usr/local/lib/libnkf.so,/usr/local/include/libnkf.hを利用する版のコンパイルは以下のようにする。

gcc -I/usr/local/include -L/usr/local/lib -o sample.x sample.c -lnkf

目的ライブラリをリンクしているかは以下コマンドで確認できる。

ldd sample.x
        linux-vdso.so.1 (0x00007ffe65729000)
        libnkf.so.1 => /usr/local/lib/libnkf.so.1 (0x00007ff4cb9da000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff4cb63b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff4cbe2f000)

サンプルプログラムsample.cは以下のような感じでUTF-8で保存。

C/C++sample.cGitHub Source
#include "libnkf.h"

int main(void)
{
    const char *a  = "アァイアイAB12AB12"; /* 半角・全角カナ交じり文字列 */
    /* テスト1 UTF->SJIS変換 半角カナ->全角カナ変換 */
    const char *o1 = "-W -s -X";
    /* テスト2 UTF->SJIS変換 半角カナを全角にしない 全角を半角に 全角カナを半角カナに */
    const char *o2 = "-W -s -x -Z -Z4";
    char *b = NULL;

    printf("%s\n", nkf_guess((unsigned char*)a, strlen(a)));

    b = (char*)nkf_convert((unsigned char*)a, strlen(a), (char*)o1, strlen(o1));

    if(b)
    {
        printf("%s\n", b);
        free(b);
    }

    b = (char*)nkf_convert((unsigned char*)a, strlen(a), (char*)o2, strlen(o2));

    if(b)
    {
        printf("%s\n", b);
        free(b);
    }

    return 0;
}

シフトJISに変換するサンプルなので、ターミナルの文字コードをSJISにして実行。
実行結果

./sample.x
UTF-8
アァイアイAB12AB12
アァイアイAB12AB12

nkfコマンドを打ったのと全く同じ、期待通りの結果が得られている模様。
結果からもわかるように、元々、nkfには半角カナ以外の半角を全角にする機能は無い

NKFコマンドオプションの詳細

入力文字コードの指定
-J : JIS
-E : EUC
-S : SJIS
-W : UTF-8

出力文字コードの指定
-j : JIS
-e : EUC
-s : SJIS
-w : UTF-8

改行コード変換指定
-Lu LFに統一
-Lw CRLFに統一

半角全角変換指定
-X 半角カナを全角カナにする
-x 半角カナを全角カナにしない
-Z 全角を半角にする
-Z4 全角カナを半角カナにする

さらにサンプル

//EUC+LFをSJIS+CRLFに変換
char euc[128],*sjis;
strcpy( euc,"あいうえお\n" );
sjis = (char*)nkf_convert( (unsigned char*)euc,strlen(euc),(char*)"-E -s -Lw",9 );

//SJIS+CRLFをUTF-8+LFに変換
char sjis[128],*utf;
strcpy( sjis,"あいうえお\r\n" );
utf = (char*)nkf_convert( (unsigned char*)sjis,strlen(sjis),(char*)"-S -w -Lu",9 );
※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】