- PR -

RTFファイルからテキストの内容のみを抽出する方法について

1
投稿者投稿内容
ぶー
会議室デビュー日: 2006/09/09
投稿数: 1
投稿日時: 2006-09-10 00:03
Webの画面からRTF(リッチテキストフォーマット形式)ファイルをアップロードし、
そのテキスト内容のみを取り出すプログラムをJavaで作成しようとしておりますが
どうやって取ればいいかわからず難儀しています。

ちなみに
javax.swing.text.rtf.RTFEditorKit
が使えるかなと思って実験してみましたが
日本語がうまくとれません。

どなたか知恵を貸していただけないでしょうか。
よろしくお願いします。
gami
会議室デビュー日: 2006/09/27
投稿数: 2
投稿日時: 2006-10-02 15:43
完全な対応とは言えませんが、RTFで使われている日本語SJISの表現(ex \'fa\'96)を、\u65331 の表現に直してやると認識するようです。
下記のようなプログラムにて、一時ファイルに日本語の変換を行ったファイルを書き出し、そのファイルに対してRTFEditorKitを用いれば、日本語でも対応することができます。しかしながら、Wordで作成した作成したRTFだと読めない場合があったり、ワードパッド等でもうまく読めなくなるときがあります。
自作のプログラム等から書き出すような簡単なRTFの文法やRTFEditorKitから書き出す分には全く問題ないのですが。。

下記のソースのような方法以外に、他に読み込める方法があればご教授等おねがいします
(一応、Apache FOP 等のライブラリ等は用いないで、というふうに回答が頂ければありがたいです。)。

--- readRTF.java

import java.io.*;
import java.util.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.rtf.RTFEditorKit;

/**
* RTF の入出力に関するクラス
*/
public class readRTF {
public readRTF() {}

/** file から文字列の読み込み **/
private static StringBuffer readString(File file) {
StringBuffer inb = new StringBuffer();
try {
String str = "";
InputStreamReader fis = new InputStreamReader(new FileInputStream(file.getPath()));
BufferedReader br = new BufferedReader(fis);
while((str = br.readLine())!=null) {
inb.append(str); inb.append("\n");
}
br.close(); fis.close();
} catch(Exception ex) {
ex.printStackTrace();
}
return inb;
}

/** RTF内日本語文字列の変換 **/
private static File ConvertJapanese(File file) {
File tmpfile = null;
StringBuffer inb = readString(file);
try {
tmpfile = File.createTempFile("rtfRead-",".rtf");
tmpfile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tmpfile);
// find sjis strings
byte b[] = new byte[2];
for(int i=0;i<inb.length();i++){
if(i < inb.length()-8 && inb.substring(i,i+2).equals("\\\'")){
byte v1,v2;
v1 = (byte)Character.digit((char)inb.substring(i+2,i+4).getBytes("SJIS")[0],16);
v2 = (byte)Character.digit((char)inb.substring(i+3,i+4).getBytes("SJIS")[0],16);
b[0] = (byte)(v1 * 16 + v2);
if(inb.substring(i+4,i+5).equals(" ")) i++;
if(inb.substring(i+4,i+6).equals("\\\'")) {
v1 = (byte)Character.digit((char)inb.substring(i+6,i+7).getBytes("SJIS")[0],16);
v2 = (byte)Character.digit((char)inb.substring(i+7,i+8).getBytes("SJIS")[0],16);
b[1] = (byte)(v1 * 16 + v2);
} else {
v1 = v2 = 0;
b[1] = 0;
}
fos.write(escapeJavaStyleString(new String(b,"SJIS"),true).getBytes("iso-8859-1"));
i += 7;
} else {
fos.write(inb.charAt(i));
}
}
fos.close();
} catch(IOException ex) {
ex.printStackTrace();
} catch(Exception ex) {
ex.printStackTrace();
}
return tmpfile;
}

/** 文字列から1行ずつ抽出**/
private static ArrayList splitLine(String s) {
String body = s;
body = body.replaceAll("\\Q\r\n","\n");
body = body.replaceAll("\\Q\r" ,"\n");
String[] ss = body.split("\\Q\n");
ArrayList v = new ArrayList();
for(int i=0;i<ss.length;i++) v.add(ss[i]);
return v;
}

/** RTFファイルから1行ずつ抽出(word document not supported) **/
public static ArrayList readRTF(File file) {
StringBuffer sb = new StringBuffer();
File tmpfile = ConvertJapanese(file);
try {
InputStream is = new FileInputStream(tmpfile);
InputStreamReader isr = new InputStreamReader(is);
RTFEditorKit rtf = new RTFEditorKit();
javax.swing.text.Document doc = rtf.createDefaultDocument();
rtf.read(isr,doc,0);
if(doc.getLength() != 0){
sb.append(doc.getText(0,doc.getLength()));
} else {
JOptionPane.showMessageDialog(null,"対応しないRTFフォーマットです。");
return null;
}
} catch(Exception ex) {
System.err.println("RTF read error: "+ex);
return null;
}
return splitLine(new String(sb));
}

private static String escapeJavaStyleString(String str, boolean escapeSingleQuote) throws IOException
{
StringBuffer sb = new StringBuffer();
if (str == null) {
return "";
}
int sz;
sz = str.length();
if(sz > 1) {
sb.append("{\\ul");
} else {
sb.append("{");
}
for (int i = 0; i < sz; i++) {
char ch = str.charAt(i);
int v = ch;
// handle unicode
if (ch > 0xfff) {
sb.append("\\u" + v);
} else if (ch > 0xff) {
sb.append("\\u" + v);
} else if (ch > 0x7f) {
sb.append("\\u" + v);
} else if (ch < 32) {
switch (ch) {
case '\b':
sb.append('\\');
sb.append('b');
break;
case '\n':
sb.append('\\');
sb.append('n');
break;
case '\t':
sb.append('\\');
sb.append('t');
break;
case '\f':
sb.append('\\');
sb.append('f');
break;
case '\r':
sb.append('\\');
sb.append('r');
break;
default :
if (ch > 0xf) {
sb.append("\\u" + v);
} else {
sb.append("\\u" + v);
}
break;
}
} else {
switch (ch) {
case '\'':
if (escapeSingleQuote) {
sb.append('\\');
}
sb.append('\'');
break;
case '"':
sb.append('\\');
sb.append('"');
break;
case '\\':
sb.append('\\');
sb.append('\\');
break;
default :
sb.append(ch);
break;
}
}
if(sz > 1) sb.append(" ? ");
}
if(sz > 1) {
sb.append("\\ul0 }");
} else {
sb.append("}");
}
return new String(sb);
}
}


[ メッセージ編集済み 編集者: gami 編集日時 2006-10-02 15:48 ]

[ メッセージ編集済み 編集者: gami 編集日時 2006-10-02 15:57 ]
gami
会議室デビュー日: 2006/09/27
投稿数: 2
投稿日時: 2006-10-04 21:20
自己レスです。
JavaのRTFEditorKitはUnicode表現の文字しか読みこまないようなので、RTF内の文字をすべてUnicode表現に直してあげれば読み込むことができるようになります。しかしながら、RTF内部には日本語だけではなくて、記号やその他の言語の表現が多々含まれることになりますので、日本語だけ考えれば良いというだけの話ではなさそうです。

RTF内にはフォントの情報が {\fNNN 〜 \fcharsetNNN;} のように含まれていますので、この情報を利用して、文字列をすべてUnicode表現に直していけばRTFEditorKitで読み込むことができるようになります。

以下、前回の例題を修正したものです。読みづらいかもしれませんが参考までに。
この例題なら、Word等で作成したRTFでもうまく読み取ることができます。

--- RTFio.java
/**
* RTFio: RTFファイルに対するテキストの入出力
*
* @author gami
* @version 1.0
*/
import java.io.*;
import java.util.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.rtf.RTFEditorKit;

/**
* RTF の入出力に関するクラス
*/
public class RTFio {

static CFontSet myFonts = new CFontSet();

/** RTFフォント情報 **/
private static class CFontSet {
ArrayList fontNum,fontEnc;
final String[] rtfFontset = new String[1500];

public CFontSet() {
fontNum = new ArrayList();
fontEnc = new ArrayList();
for(int i=0;i<1500;i++) rtfFontset[i] = "";

rtfFontset[0] = "iso-8859-1";//0: ANSI
rtfFontset[1] = "SJIS"; //1: Default
rtfFontset[2] = "MacSymbol"; //2: Symbol
rtfFontset[3] = ""; //3: Invalid

rtfFontset[77] = "MacRoman"; //77: Mac
rtfFontset[78] = "SJIS"; //Japanese
rtfFontset[79] = "Cp950";
rtfFontset[80] = "EUC_KR";

rtfFontset[102] = "MS936";
rtfFontset[128] = "SJIS"; //128: Shift Jis
rtfFontset[129] = "MS949"; //129: Hangul
rtfFontset[130] = "x-Johab"; //130: Johab
rtfFontset[134] = "MS936"; //134: GB2312
rtfFontset[136] = "Big5"; //136: Big5
rtfFontset[161] = "Cp1253"; //161: Greek
rtfFontset[162] = "Cp1254"; //162: Turkish
rtfFontset[163] = "Cp1258"; //163: Vietnamese
rtfFontset[177] = "Cp1255"; //177: Hebrew
rtfFontset[178] = "Cp1256"; //178: Arabic
rtfFontset[179] = "Cp1256"; //179: Arabic Traditional
rtfFontset[180] = "Cp864"; //180: Arabic user
rtfFontset[181] = "Cp862"; //181: Hebrew user
rtfFontset[186] = "Cp775"; //186: Baltic

rtfFontset[204] = "Cp866"; //204: Russian
rtfFontset[238] = "Cp1250"; //238: Eastern European
rtfFontset[222] = "Cp874"; //222: Thai
rtfFontset[254] = "Cp437"; //254: PC 437
rtfFontset[255] = "SJIS"; //255: OEM
rtfFontset[256] = "MacRoman"; //256:

rtfFontset[437] = "Cp437"; //437: United States IBM

rtfFontset[708] = "Cp1256"; //708: Arabic (ASMO 708)
rtfFontset[709] = "Cp1256"; //709: Arabic (ASMO 449+, BCON V4)
rtfFontset[710] = "Cp1256"; //710: Arabic (transparent Arabic)
rtfFontset[711] = "Cp1256"; //711: Arabic (Nafitha Enhanced)
rtfFontset[720] = "Cp1256"; //720: Arabic (transparent ASMO)
rtfFontset[819] = "Cp1250"; //819: Windows 3.1 (United States and Western Europe)
rtfFontset[850] = "Cp850"; //850: IBM multilingual
rtfFontset[852] = "Cp852"; //852: Eastern European
rtfFontset[860] = "Cp860"; //860: Portuguese
rtfFontset[862] = "Cp862"; //862: Hebrew
rtfFontset[863] = "Cp863"; //863: French Canadian
rtfFontset[864] = "Cp864"; //864: Arabic
rtfFontset[865] = "Cp865"; //865: Norwegian
rtfFontset[866] = "Cp866"; //866: Soviet Union
rtfFontset[874] = "MS874"; //874: Thai
rtfFontset[932] = "MS932"; //932: Japanese
rtfFontset[936] = "MS936"; //936: Simplified Chinese
rtfFontset[949] = "MS949"; //949: Korean
rtfFontset[950] = "MS950"; //950: Traditional Chinese
rtfFontset[1250] = "Cp1250"; //1250: Windows 3.1 (Eastern European)
rtfFontset[1251] = "Cp1251"; //1251: Windows 3.1 (Cyrillic)
rtfFontset[1252] = "Cp1252"; //1252: Western European
rtfFontset[1253] = "Cp1253"; //1253: Greek
rtfFontset[1254] = "Cp1254"; //1254: Turkish
rtfFontset[1255] = "Cp1255"; //1255: Hebrew
rtfFontset[1256] = "Cp1256"; //1256: Arabic
rtfFontset[1257] = "Cp1257"; //1257: Baltic
rtfFontset[1258] = "Cp1258"; //1258: Vietnamese
rtfFontset[1361] = "x-Johab"; //1361: Johab
}

public String fontname(int n) {
return rtfFontset[n];
}

public void add(int n_font,int n_fcharset) {
for(int i=0;i<fontNum.size();i++){
if(n_font == ((Integer)fontNum.get(i)).intValue()){
fontEnc.set(i,new Integer(n_fcharset));
return;
}
}
fontNum.add(new Integer(n_font));
fontEnc.add(new Integer(n_fcharset));
}
public String get(int n_font) {
for(int i=0;i<fontNum.size();i++){
if(n_font == ((Integer)fontNum.get(i)).intValue()){
int cset = ((Integer)fontEnc.get(i)).intValue();
if(cset >= 0 && cset < 1500){
return rtfFontset[cset];
}
return "";
}
}
return "";
}
}

/** ファイル内容をテキストとして取得 **/
private static StringBuffer readString(File file) {
StringBuffer inb = new StringBuffer();
try {
String str = "";
InputStreamReader fis = new InputStreamReader(new FileInputStream(file.getPath()));
BufferedReader br = new BufferedReader(fis);
while((str = br.readLine())!=null) {
inb.append(str); inb.append("\n");
}
br.close(); fis.close();
} catch(Exception ex) {
ex.printStackTrace();
}
return inb;
}

/** フォント情報の読み取り **/
private static void getRTFfontset(StringBuffer inb) {
boolean ffont = false;
int n_font = -1;
for(int i=0;i<inb.length();i++){
if(ffont && inb.charAt(i) == ';'){
ffont = false; n_font = -1; continue;
}
// get font set
if(!ffont && i < inb.length()-4 && inb.substring(i,i+2).equals("\\f")){
if(Character.isDigit(inb.substring(i+2,i+3).charAt(0))){
// get font number
for(int fi=3;fi<6;fi++){
if(!Character.isDigit(inb.substring(i+fi,i+fi+1).charAt(0))){
try {
n_font = Integer.valueOf(inb.substring(i+2,i+fi)).intValue();
break;
} catch(java.lang.NumberFormatException e) {
n_font = -1;
break;
}
}
}
if(n_font >= 0){
ffont = true;
}
}
}
// get 'fcharset' of language type
if(ffont && i < inb.length()-9 && inb.substring(i,i+9).equals("\\fcharset")){
int n_fcharset = -1;
// get font number
for(int fi=9;fi<13;fi++){
if(!Character.isDigit(inb.substring(i+fi,i+fi+1).charAt(0))){
try {
n_fcharset = Integer.valueOf(inb.substring(i+9,i+fi)).intValue();
System.out.print("["+n_font+"] fcharset is "+n_fcharset+": ");
if(n_fcharset >= 0){
myFonts.add(n_font,n_fcharset);
System.out.println(myFonts.get(n_font));
} else {
System.out.println();
}
break;
} catch(java.lang.NumberFormatException e) {
n_font = -1;
break;
}
}
}
}
}
}

/** RTF文字列文字の変換(SJIS to UTF8) **/
private static File ConvertJapanese(File file) {
File tmpfile = null;
try {
byte b1[] = new byte[1];
byte b2[] = new byte[2];
int skip = 0,n_font = -1,n_unicode = -1;
String cset = "";
boolean breadrule = false;
boolean bsymbol = false;
boolean bbackslash = false;
boolean breadfont = false;

StringBuffer inb = readString(file);
tmpfile = File.createTempFile("rtftmp",".rtf");
//tmpfile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tmpfile);

// get fontset information
getRTFfontset(inb);

// read strings
for(int i=0;i<inb.length();i++,skip=0){
// get font set
if(i < inb.length()-4 && inb.substring(i,i+2).equals("\\f")){
if(Character.isDigit(inb.substring(i+2,i+3).charAt(0))){
// get font number
for(int fi=3;fi<6;fi++){
if(!Character.isDigit(inb.substring(i+fi,i+fi+1).charAt(0))){
try {
n_font = Integer.valueOf(inb.substring(i+2,i+fi)).intValue();
cset = myFonts.get(n_font);
if(cset.equals("MacSymbol")){
bsymbol = true;
} else {
bsymbol = false;
}
break;
} catch(java.lang.NumberFormatException e) {
break;
}
}
}
}
}
// get 'fcharset' of language type
if(i < inb.length()-9 && inb.substring(i,i+9).equals("\\fcharset")){
breadfont = true;
}
// get 'fchar' and 'lchar' rule
if(i < inb.length()-9 && inb.substring(i,i+9).equals("\\*\\fchars")){
breadrule = true;
}
if(i < inb.length()-9 && inb.substring(i,i+9).equals("\\*\\lchars")){
breadrule = true;
}
// get unicode character
if(i < inb.length()-4 && inb.substring(i,i+2).equals("\\u")){
char c = inb.substring(i+2,i+3).charAt(0);
if(Character.isDigit(c) || c == '-'){
// get unicode number
for(int fi=3;fi<9;fi++){
if(!Character.isDigit(inb.substring(i+fi,i+fi+1).charAt(0))){
try {
if(c == '-'){
n_unicode = -Integer.valueOf(inb.substring(i+3,i+fi)).intValue();
} else {
n_unicode = Integer.valueOf(inb.substring(i+2,i+fi)).intValue();
}
break;
} catch(java.lang.NumberFormatException e) {
break;
}
}
}
}
}
// convert japanese
if(!breadfont && i < inb.length()-8 && inb.substring(i,i+2).equals("\\\'")){
byte v1,v2;

v1 = (byte)Character.digit((char)inb.substring(i+2,i+4).getBytes("iso-8859-1")[0],16);
v2 = (byte)Character.digit((char)inb.substring(i+3,i+4).getBytes("iso-8859-1")[0],16);
String code = "";
if(!bsymbol && !breadrule){
b2[0] = (byte)(v1 * 16 + v2);
if(inb.substring(i+4,i+6).equals("\\\'")) {
v1 = (byte)Character.digit((char)inb.substring(i+6,i+7).getBytes("iso-8859-1")[0],16);
v2 = (byte)Character.digit((char)inb.substring(i+7,i+8).getBytes("iso-8859-1")[0],16);
b2[1] = (byte)(v1 * 16 + v2);
if(cset.equals("")) code = new String(b2,"SJIS");
else code = new String(b2,cset);
skip += 7;
} else {
v1 = v2 = 0;
if(isRtfChar(inb.substring(i+4,i+5).charAt(0))){
b2[1] = inb.substring(i+4,i+5).getBytes("iso-8859-1")[0];
if(cset.equals("")) code = new String(b2,"SJIS");
else code = new String(b2,cset);
skip += 4;
} else {
b1[0] = b2[0];
if(cset.equals("")) code = new String(b1,"iso-8859-1");
else code = new String(b1,cset);
skip += 3;
}
}
} else {
b1[0] = (byte)(v1 * 16 + v2);
if(!breadrule){
code = new String(b1,cset);
if(cset.equals("")) code = new String(b1,"iso-8859-1");
else code = new String(b1,cset);
}
else code = new String(b1,"iso-8859-1");
skip += 3;
}
String ascii = escapeString(code,n_unicode);
if(ascii.indexOf("\\u65533") > 0){
System.out.print(inb.substring(i+0,i+skip+1)+" : ");
System.out.print(code+": "+cset+" : "+ascii+" : ");
for(int ci=0;ci<code.length();ci++){
int v = code.charAt(ci);
System.out.print(code.charAt(ci)+"["+v+"]"+", ");
}
System.out.println();
}
fos.write(ascii.getBytes("iso-8859-1"));
i += skip;
bbackslash = false;
n_unicode = -1;
} else {
char ch = inb.charAt(i);
if(ch == '\\') bbackslash = true;
else if(!isRtfChar(ch)) bbackslash = false;
if(breadfont || bbackslash || !bsymbol || !isRtfChar(ch)) {
fos.write(ch);
} else {
byte[] sb = new byte[1];
sb[0] = inb.substring(i,i+1).getBytes()[0];
String ascii = escapeString(new String(sb,"MacSymbol"),-1);
fos.write(ascii.getBytes("iso-8859-1"));
}
}
if(breadfont){
if(inb.substring(i,i+1).equals(";")){
breadfont = false;
}
}
if(breadrule){
if(inb.substring(i,i+1).equals("}")){
breadrule = false;
}
}
}
fos.close();
} catch(UnsupportedEncodingException ex) {
ex.printStackTrace();
} catch(IOException ex) {
ex.printStackTrace();
} catch(Exception ex) {
ex.printStackTrace();
}
return tmpfile;
}

/** RTF unicode 文字列かどうか **/
private static boolean isRtfChar(char c) {
if(c == '\r' || c == '\n') return false;
if(c == '\\' || c == '{' || c == '}' || c == ' ') return false;
return true;
}

private static String escapeString(String str,int n_unicode) throws IOException
{
StringBuffer sb = new StringBuffer();
if (str == null) {
return "";
}
int sz;
sz = str.length();
for(int i = 0; i < sz; i++) {
char ch = str.charAt(i);
int v = ch;
// handle unicode
if(ch > 0xfff) {
if(n_unicode > 0) {
if(v != n_unicode){
sb.append("{\\u"); sb.append(v); sb.append("}");
}
} else {
if(v != 0x10000 + n_unicode){
sb.append("{\\u"); sb.append(v); sb.append("}");
}
}
} else if(ch > 0xff) {
sb.append("\\u"); sb.append(v); sb.append("? ");
} else if(ch > 0x7f) {
sb.append("\\u"); sb.append(v);
sb.append("? ");
} else if(ch < 32) {
switch(ch) {
case '\b':
sb.append('\\'); sb.append('b'); break;
case '\n':
sb.append('\\'); sb.append('n'); break;
case '\t':
sb.append('\\'); sb.append('t'); break;
case '\f':
sb.append('\\'); sb.append('f'); break;
case '\r':
sb.append('\\'); sb.append('r'); break;
default:
if(ch > 0xf) sb.append("\\u"+v);
else sb.append("\\u"+v);
break;
}
} else {
switch(ch) {
case '\'': case '"': case '\\': case '{': case '}':
sb.append('\\'); sb.append(ch);
break;
default:
sb.append(ch);
break;
}
}
}
return new String(sb);
}

/** 文字列から1行ずつ抽出 **/
private static ArrayList splitLine(String s) {
String body = s;
body = body.replaceAll("\\Q\r\n","\n");
body = body.replaceAll("\\Q\r" ,"\n");
String[] ss = body.split("\\Q\n");
ArrayList v = new ArrayList();
for(int i=0;i<ss.length;i++) v.add(ss[i]);
return v;
}

/** RTFファイルから1行ずつ抽出 **/
public static ArrayList readRTF(File file) {
StringBuffer sb = new StringBuffer();

File tmpfile = ConvertJapanese(file);
try {
InputStream is = new FileInputStream(tmpfile);
InputStreamReader isr = new InputStreamReader(is);//UTF-8
RTFEditorKit rtf = new RTFEditorKit();
javax.swing.text.Document doc = rtf.createDefaultDocument();
rtf.read(isr,doc,0);
if(doc.getLength() != 0){
sb.append(doc.getText(0,doc.getLength()));
} else {
JOptionPane.showMessageDialog(null,"対応しないRTFフォーマットです。");
}
} catch(Exception ex) {
System.err.println("RTF read error: "+ex);
}
return splitLine(sb.toString());
}

public static void writeRTF(String filename,String str) {
File file = new File(filename);
try {
OutputStream os = new FileOutputStream(file);

StyleContext sc = new StyleContext();
DefaultStyledDocument doc = new DefaultStyledDocument(sc);
Style n_style = sc.addStyle("normal",null);
RTFEditorKit rtfwriter = new RTFEditorKit();
StyleConstants.setFontFamily(n_style,"serif");

doc.insertString(0,str,n_style);
rtfwriter.write(os,doc,0,doc.getLength());
} catch(Exception ex) {
System.err.println(ex);
}
}

public static void main(final String[] args) throws Exception {
System.setOut(new PrintStream(System.out, true, "UTF-8"));

ArrayList r = readRTF(new File("multilingual.rtf"));
for(int i=0;i<r.size();i++){
System.out.println(r.get(i));
}

writeRTF("myoutput.rtf","Hello RTF Document!!\n日本語でこんにちは。\n韓国語で\uc548\ub155\ud558\uc2ed\ub2c8\uae4c\n");
}
}


[ メッセージ編集済み 編集者: gami 編集日時 2006-10-06 21:06 ]
1

スキルアップ/キャリアアップ(JOB@IT)