split_text の Manpage風解説

名前

split_text, freesplitedtext, search_delimiter - 文字列を区切り文字で分割する

書式

int split_text (const char *string, const char *delimiter, struct splitedtext **res);

int freesplitedtext (struct splitedtext *res);

int search_delimiter (const char *string, const char *delimiter);

説明

split_text()は、stringdelimiter を渡すと、1つ以上の splitedtext 構造体を返す。それぞれの splitedtext 構造体には分割された文字列が格納されている。

split_text()が用いる splitedtext 構造体は以下のフィールドを含む。

struct splitedtext {
    char   *string;
    struct splitedtext *next;
};

string 引数は、分割対象の文字列を指定し、delimiter には区切り文字を指定する。

split_text() 関数は、splitedtext 構造体のメモリ確保を行い、splitedtext 構造体のリンクリストを初期化し、res にリストの先頭へのポインタを入れて返す。 このとき、各構造体の string フィールドには分割された文字列が格納される 。リンクリストの要素は next フィールドにより連結される。

freesplitedtext() 関数は、 リンクリスト res に対して動的に割り当てられたメモリを解放する。

search_delimiter() 関数は、string で指定された文字列の最初に出現する delimiter 文字列の位置を返す(delimiter が見つからなかった場合は -1 を返す)。

返り値

split_text()関数は分割した回数を返す。

search_delimiter()関数は、delimiter の出現位置を返し、見つからなかった場合は -1 を返す。

以下の例は、標準入力から分割対象文字列と区切り文字を受け取って、分割結果を出力する。

/**
 * split_text_test.c
 * splite_text()のテスト用コード
 */

#include <stdio.h>
#include "split_text.h"

int main (int argv, char *argc[])
{
    
    char *string, *delimiter;
    struct splitedtext *res, *rp;
    
    if (argv < 3) {
        return 1;
    }
    
    string = argc[1];
    delimiter = argc[2];
    
    split_text(string, delimiter, &res);
   
    for (rp = res; rp != NULL ; rp=rp->next) {
      
        printf("%s\n", rp->string);
        
    }
    
    freesplitedtext(res);
    
    return 0;

}

文字列分割用ライブラリを勉強の為に書いてみた

URLパーサをつくるときに文字列分割用にstrtok_r()関数を利用したのだけど、なんかしっくりとこなかった(ただの勉強不足なわけですが...)ので勉強の為にも文字列分割ライブラリをつくってみた。

今回勉強になったもの

  • ユーザー関数
  • 構造体による連結リスト
  • malloc()によるメモリの動的確保
  • プログラムのモジュール化
    • ヘッダーファイル
    • オブジェクトのリンク方法

なんかいきなり盛りだくさんです。

今回つくったソース

とりあえず細かい説明の前に今回つくったソースを貼りつけておきます。(JavaDoc風のコメントをつけてみました。)

ヘッダーファイル
/**
 * split_text.h
 * 文字列分割用ライブラリ
 */

/**
 * @struct 分割された文字列を格納するための連結リスト用構造体
 * 分割された数分ノードが連結されます
 */
struct splitedtext {
    char *string;				//分割された文字列
    struct splitedtext *next;	//次のノード 
};

/**
 * テキストを分割して連結リストに格納する
 * @param string 分割対象テキスト
 * @param delimiter 区切り文字(デリミタ)
 * @param res 分割結果を格納するための連結リスト
 * @return 分割した回数を返す(分割されなかった場合は0を返す)
 */
int split_text (const char *string, const char *delimiter, struct splitedtext **res);

/**
 * リンクリスト res に対して動的に割り当てられたメモリを解放する
 * @param res 開放対象のsplitedtext構造体
 */
int freesplitedtext (struct splitedtext *res);

/**
 * 区切り文字の出現ポイントを返す
 * @param string 対象テキスト
 * @param delimiter 区切り文字
 * @return 区切り文字の出現ポイント
 */
int search_delimiter (const char *string, const char *delimiter);
実動作部
/**
 * split_text.c
 * 文字列分割用ライブラリ
 */

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

/**
 * テキストを分割して連結リストに格納する
 * @param string 分割対象テキスト
 * @param delimiter 区切り文字(デリミタ)
 * @param res 分割結果が格納された連結リストの最初のノード
 * @return 分割した回数を返す(分割されなかった場合は0を返す)
 */
int split_text (char const *string, char const *delimiter, struct splitedtext **res)
{
	
	char *chunk;
	int	counter,
		current_index,
		search_index,
		len;
	struct	splitedtext *current_splitedtext, *prev;
	
	/* chunkを空文字で初期化 */
	chunk = malloc(sizeof(char));
	chunk[0] = '\0';
	
	/* resを初期化 */
	*res = malloc(sizeof(struct splitedtext));
	(*res)->string = chunk;
	(*res)->next = NULL;
	
	current_splitedtext = *res; /* current_splitedtextをresで初期化 */
	prev = NULL;
	
	/* 各変数を初期化 */
	counter = 0;
	current_index = 0;
	search_index = 0;
	
	while (search_index != -1) { /* 探索点以降にデリミタが見つからなかった場合は分割を終了 */
		
		/* 探索点を起点としてデリミタの位置を検索 */
		search_index = search_delimiter(&string[current_index], delimiter);
		
		/* 探索点にデリミタがあった場合は分割せずスルー */
		if (search_index != 0) {
				
			/* 探索点からデリミタが見つかった点までの長さを算出:デリミタが探索点以降見つからなかった場合は残りの文字列の長さ */	
			len = (search_index == -1)? strlen(string) - current_index : search_index;
			
			/* 探索点からデリミタが見つかった地点までの文字列を抜き出して、
			 * 構造体に代入
			 */
			chunk = malloc(sizeof(char) * (len + 1));
			strncpy(chunk, &string[current_index], len);
			chunk[len] = '\0'; /* 抜き出した文字列の最後がNULL文字であるとは限らないため、NULL文字を追加。 */
			current_splitedtext->string = chunk;
			current_splitedtext->next = NULL;
			
			if(prev != NULL) {
				prev->next = current_splitedtext; /* 一つ前のノードに連結 */
			}
			
			/* 新しいノードを作成 */
			prev = current_splitedtext;
			current_splitedtext = malloc(sizeof(struct splitedtext));
			
			/* 分割した数を記録 */
			counter++;
		
		}
		
		/* デリミタが見つかった位置の後に探索点を移動 */
		current_index += search_index + strlen(delimiter);
		
		/* 探索点が文字長を超えた場合は分割を終了 */
		if(current_index >= strlen(string)) {
			break;
		}
		
	}
	
	return counter; /* 分割数を返す */
	
}

/**
 * リンクリスト res に対して動的に割り当てられたメモリを解放する
 * @param res 開放対象のsplitedtext構造体
 */
int freesplitedtext (struct splitedtext *res) {
	
	struct splitedtext *pr, *deleted;
	pr = res;
	
	do {
		free(pr->string);
		
		deleted = pr;
		pr=deleted->next;
		
		free(deleted);
	} while (pr != NULL);
	
	return 0;
	
}

/**
 * 区切り文字の出現ポイントを返す
 * @param string 対象テキスト
 * @param delimiter 区切り文字
 * @return 区切り文字の出現ポイント
 */
int search_delimiter (const char *string, const char *delimiter)
{
	
	char *pointer;
	int index;
	
	pointer = strstr(string, delimiter);
	index = (pointer == NULL)? -1 : pointer-string;
	
	return index;
	
}

HTTP Client の動作フロー その9 : useragent.c

今まで勉強したことのまとめとして、指定ホストのトップページをGETで読み込み、HTTPレスポンスを標準出力に表示するツールをつくってみました。

/**
 * useragent.c
 **/

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define  BUF_LEN  1024

int main(int argc, char *argv[])
{

        struct addrinfo hints;
        struct addrinfo *result, *rp;
        int error, sfd;
        char *host, *err_mess, buf[BUF_LEN];
	size_t len;
	ssize_t n;

        if (argc < 2) {
                fprintf(stderr, "Usage: %s hostname\n", argv[0]);
		return 1;
        }

	//host情報を代入
	host = argv[1];
	
	//hintsを初期化
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC; //IPv4とIPv6どちらでもOK
        hints.ai_socktype = SOCK_STREAM; //TCP
        hints.ai_flags = 0;
        hints.ai_protocol = 0; //Any protocol

        //名前解決
        error = getaddrinfo(host, "http", &hints, &result);

        //エラー処理
        if (error != 0) {
                fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error));
                return 1;
        }

        //ホストに接続
        for (rp = result; rp != NULL; rp = rp->ai_next) {

		//ソケットを生成
		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

		//ソケットエラー処理
		if (sfd == -1) {
			//ソケットが生成できなかった場合,次のアドレスでリトライ
			err_mess = "socket";
			continue;
		}

		//ホストに接続
		if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
			//接続できた場合はループを抜ける
			break;	
		}

		err_mess = "connect";
		//接続できかなった場合はソケットを閉じて次のアドレスでリトライ
		close(sfd);

        }

	//すべてのアドレスで接続に成功しなかった場合は終了
	if (rp == NULL) {
		fprintf(stderr, "error : %s\n", err_mess);
		return 1;
	}
	
	//resultのメモリ領域を開放
	freeaddrinfo(result);

	//送信内容を作成
	snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", host);
	
	//リクエストを送信
	len = strlen(buf);
	error = write(sfd, buf, len);
	if(error < len) {
		fprintf(stderr, "error : HTTP");
		close(sfd);
		return 1;
	}
	
	//バッファを空に
	memset(buf, 0, sizeof(buf));
	
	//レスポンスを受信して出力
	do {
		//バッファ分読み込む
		n = read(sfd, buf, sizeof(buf));		
		
		//標準出力に表示
		if(write(STDOUT_FILENO, buf, n) < 0) {
			perror("write STDOUT");
			return 1;
		}
		
	} while(n > 0);
	
	//エラー処理
	if(n == -1) {
		perror("");
		return 1;
	}
	
	close(sfd);

        return 0;

}

使用方法

引数で与えられたホストのトップページをGETで読み込み、HTTPレスポンスを標準出力に表示。

$ useragent localhost
HTTP/1.1 200 OK
Date: Sat, 28 May 2011 21:31:04 GMT
Server: Apache/2.2.17 (Ubuntu)
Last-Modified: Sat, 09 Apr 2011 05:55:14 GMT
ETag: "42154-b1-4a075fc77fa69"
Accept-Ranges: bytes
Content-Length: 177
Vary: Accept-Encoding
Connection: close
Content-Type: text/html

<html><body><h1>It works!</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added, yet.</p>
</body></html>

TODO

  • URLを指定できるようにする
  • ファイルとして保存できるようにする
  • 処理をサブルーチンに分割
  • リクエストとレスポンスのヘッダー情報を構造体として取り扱えるようにする

日本語も勉強しないと...

ブログを書き始めて感じたこと。
伝えたい内容を簡潔にまとめる能力をもっとつけないといけない。

特にソースコード中のコメントとか、バージョン管理システムにコミットするときのメッセージをもっとわかりやすく書けるようになりたい。

昔はもっとちゃんとした文章が書けた気がするのですが、こんな短文を書くのにもけっこう時間がかかったりしてます。マズイ。

オープンソースのコードを取り込んだ時のライセンス表記について

GPLのコードを1行でも取り込んだ場合は、ソフトウェア全体をGPLで配布しなければいけませんが、BSDやMITライセンスのコードを一部取り込んだ場合のライセンス表記ってどうなってるんだろう?と思っていろいろ調べてみた。

BSDライセンスに関しては、Wikipediaによると以下のような記載がある。

「無保証」であることの明記と著作権およびライセンス条文自身の表示を再頒布の条件とするライセンス規定である。この条件さえ満たせば、BSDライセンスソースコードを複製・改変して作成したオブジェクトコードをソースコードを公開せずに頒布できる。

ようするに、「無保証の明記」と「著作権表示」をどこかに書いておけばOKということのよう。

ちょっとひっかかるのが「ライセンス条文自身の表示」の部分。ライセンス条文を書いてしまったらBSDライセンスのコードを再利用しているソフトウェア全体がBSDライセンスで配布されているものと勘違いされる可能性がでてくるけど、そのへんどうなってるんだろうか?

デュアルライセンス - jQueryの場合

ちなみに、MITとGPLのデュアルライセンスで配布されているjQueryは一部分で「Sizzle.js」というMIT/BSD/GPLのトリブルライセンスのコードを利用しているようで、ライセンス表記部分は下記のようになっている。

/*!
 * jQuery JavaScript Library v1.6.1
 * http://jquery.com/
 *
 * Copyright 2011, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 * Copyright 2011, The Dojo Foundation
 * Released under the MIT, BSD, and GPL Licenses.
 *
 * Date: Thu May 12 15:04:36 2011 -0400
 */

著作権表示」はあるけれど、"AS IS"でおなじみの「無保証の明記」が書いてない。代わりにライセンスの種類とライセンス本文が閲覧できるURLが記載されている。

jQueryの場合はMTとGPLのデュアルライセンスなので、併記というかたちで問題ない模様。

APSLとBSDライセンスの併記例

http://www.opensource.apple.com/source/Libc/Libc-594.9.4/x86_64/gen/makecontext.cAppleが配布しているソースコードに下記のような記載をみつけた。

/*
 * Copyright (c) 2007, 2009 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * Copyright (c) 2001 Daniel M. Eischen <deischen@freebsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

APSLとBSDライセンスのデュアル表記。
もともとはBSDライセンスのコードだったのを、Appleが手をいれてAPSLで再配布している模様。

LGPLのコード中でMITライセンスのコードを再利用している例

こちらLGPLのコード中で一部MITライセンスのコードを再利用している例。

/*
   socket handling routines
   Copyright (C) 1998-2001, Joe Orton <joe@light.plus.com>,
   except where otherwise indicated.

   Portions are:
   Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi>
   Originally under GPL in Mutt, http://www.mutt.org/
   Relicensed under LGPL for neon, http://www.webdav.org/neon/

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA

   The sock_readline() function is:

   Copyright (c) 1999 Eric S. Raymond

   Permission is hereby granted, free of charge, to any person
   obtaining a copy of this software and associated documentation
   files (the "Software"), to deal in the Software without
   restriction, including without limitation the rights to use, copy,
   modify, merge, publish, distribute, sublicense, and/or sell copies
   of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.

*/


下記のように特定の部分(sock_readline()関数)に限定した著作権表記になっているので分かりやすい。

The sock_readline() function is:

   Copyright (c) 1999 Eric S. Raymond


一部分だけBSDライセンスやMITライセンスのコードを再利用した場合はこういうふうに表記すればいいみたいです。(というか、エリック・レイモンド!!)


あと、「一部は一度GPLでリリースされたものだけど、LGPLで再ライセンスしたよ」といったような表記もみられて参考になります。

   Portions are:
   Copyright (C) 1999-2000 Tommi Komulainen <Tommi.Komulainen@iki.fi>
   Originally under GPL in Mutt, http://www.mutt.org/
   Relicensed under LGPL for neon, http://www.webdav.org/neon/

まとめ

BSDやMITライセンスのコードを一部利用する際は、「ここの部分はもともと○○○ライセンスで配布されてたコードの再利用で、△△△さんが著作権を保持してて、かつ無保証ですよー」ということを表記すれば良いみたい。