2013年10月14日月曜日

Apache POI XSSF Eventmodelお試し

Apache POI のイベントモデル(XSSF)でセルの値を読み込み、任意のBeanのフィールドへ設定するjavaのコードです。
メモリ使用量が半端じゃないPOIですが、イベントモデルを使用するとストリーミングで読み込めるので、メモリ使用量を抑えることができます。
その場合、xmlファイルをテキストとして読み込んで処理をすることになるので、ユーザーモデルに比べると分かりにくく日本語のドキュメントもあまり無かったので、勉強がてら実際に動くものを作ってみました。
取りあえず動くことを目標にしていたのでExceptionは全てthrowsで処理してしまっていたり、いろいろと手を抜いているところがあります。
イベントモデルではxmlファイルからの値は文字列型で取得することになるので、このサンプルでは取得した値の型を設定先のbeanのフィールドの型に変換します。ですのでbeanのフィールドがDateなのにセルの値が"abc"などの文字列だと値を取得できません。また1904年始まりのExcelファイルには対応押していません。
使用ライブラリはApachePOI3.9とApacheXerces2.11.0(+それらが使っているライブラリ群)です。
十分なデバッグはしていないので自己責任でご利用ください。

SheetToBeanUtils.java
package poi.example.eventmodel.mapping;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SheetToBeanUtils {

    private static Log log = LogFactory.getLog(SheetToBeanUtils.class);

    // ワークブックへのパスとbeanのインスタンスを渡すと、指定のシートの値をbeanへ設定して返します。
    // ワークブックをイベントモデルで読み込むのでメモリの消費量が少なく済みます。
    public static <B> B mapSheetToBean(String workbookPath, B bean)
            throws Exception {
        // excelから値を取得
        SheetToBeanMapper mapper = new SheetToBeanMapper(workbookPath, bean);
        mapper.execute();
        return bean;
    }
}



SheetToBeanMapper.java
package poi.example.eventmodel.mapping;

import java.io.InputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import poi.example.eventmodel.SheetHandler;
import poi.example.eventmodel.WorkbookHandler;
import poi.example.eventmodel.mapping.annotation.Sheet;

public class SheetToBeanMapper {

    private static Log log = LogFactory.getLog(SheetToBeanMapper.class);

    // 値を設定する対象のbean
    private Object bean = null;
    // ワークブックのパス
    private String filePath = null;
    // 値を取得するシート
    private String sheetName = null;

    public SheetToBeanMapper(String filePath, Object bean) {
        this.filePath = filePath;
        this.bean = bean;
        Sheet sheet = bean.getClass().getAnnotation(Sheet.class);
        if(sheet == null){
            throw new IllegalArgumentException("クラスに@Sheetアノテーションが設定されていません。");
        }
        sheetName = sheet.name();
    }

    // マッピング処理開始
    public void execute() throws Exception {

        OPCPackage pkg = OPCPackage.open(filePath);
        XSSFReader reader = new XSSFReader(pkg);

        // ワークブックの情報から対象シートのrIdを取得する
        XMLReader wbParser = new SAXParser();
        WorkbookHandler wbHandler = new WorkbookHandler(sheetName);
        wbParser.setContentHandler(wbHandler);
        // ワークブックの情報を取得
        InputSource wbSource = new InputSource(reader.getWorkbookData());
        wbParser.parse(wbSource);
        // 指定のシート名に対応するrIdを取得
        String rId= wbHandler.getrId();
        if(rId == null){
            throw new Exception("対象のシートがありません。シート名=" + sheetName);
        }

        // ワークシートから値を取得する
        SharedStringsTable sst = reader.getSharedStringsTable();
        // パーサを取得
        XMLReader parser = new SAXParser();
        SheetHandler handler = new SheetHandler(sst, new FieldValueArranger(bean));
        parser.setContentHandler(handler);
        // シートを指定して取得
        InputStream targetSheet = reader.getSheet(rId);
        InputSource sheetSource = new InputSource(targetSheet);
        // シートの内容をパースしながらbeanへマッピング
        // この中でSheetHandlerが処理を行ってます。
        parser.parse(sheetSource);
        targetSheet.close();
    }

}


WorkbookHandler.java
package poi.example.eventmodel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

// ワークブックの情報を取得するハンドラ
// シート名から特定のシートを取得する際に必要
public class WorkbookHandler extends DefaultHandler {

    private static Log log = LogFactory.getLog(WorkbookHandler.class);

    // 行のタグ
    private static final String TAG_SHT = "sheet";
    // シート名の属性名
    private static final String NM_SNM = "name";
    // r:idの属性名
    private static final String NM_RID = "r:id";
    // 対象のシート
    private String sheetName = null;
    // 対象のシートのr:id
    private String rId = null;

    public String getrId() {
        return rId;
    }

    public WorkbookHandler(String sheetName) {
        this.sheetName = sheetName;
    }

    // 開始タグを取得した際に動作するメソッド
    public void startElement(String uri, String localName, String name,
            Attributes attributes) throws SAXException {
        // シート開始タグの場合
        if (name.equals(TAG_SHT)) {
            String sheetNameTemp = attributes.getValue(NM_SNM);
            String rIdTemp = attributes.getValue(NM_RID);
            if(sheetName.equals(sheetNameTemp)){
                rId = rIdTemp;
            }
        }
    }

}


SheetHandler.java
package poi.example.eventmodel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import poi.example.eventmodel.mapping.FieldValueArranger;
import poi.example.util.ExcelUtils;

// シートを処理するhandler
// ここでシートの値を取得し、FieldValueArrangerに渡します。
public class SheetHandler extends DefaultHandler {

    private static Log log = LogFactory.getLog(SheetHandler.class);

    // 行のタグ
    private static final String TAG_ROW = "row";
    // 列のタグ
    private static final String TAG_COL = "c";
    // 値のタグ
    private static final String TAG_VAL = "v";
    // 行番号の属性名
    private static final String NM_RNO = "r";
    // レンジの属性名
    private static final String NM_RNG = "r";
    // タイプの属性名
    private static final String NM_TYP = "t";
    // 文字列タイプ
    private static final String TYP_STR = "s";

    // 共有している文字列テーブル
    private final SharedStringsTable sst;
    // 取得した値を扱うクラス
    private FieldValueArranger arranger = null;

    // 直前のコンテンツの値
    private String lastContents;

    // 次の値が文字列かどうか
    private boolean nextIsString;
    // 現在の行
    private int currentRow = 0;
    // 現在の列
    private int currentCol = 0;

    public SheetHandler(SharedStringsTable sst, FieldValueArranger arranger) {
        this.sst = sst;
        this.arranger = arranger;
    }

    // 開始タグを取得した際に動作するメソッド
    public void startElement(String uri, String localName, String name,
            Attributes attributes) throws SAXException {

        // 行開始タグの場合
        if (name.equals(TAG_ROW)) {
            // 行タグの場合は行番号を取得し、列番号をクリア
            currentRow = Integer.parseInt(attributes.getValue(NM_RNO));
            currentCol = 0;
        }

        // 列開始タグの場合
        if (name.equals(TAG_COL)) {
            // レンジを取得
            String range = attributes.getValue(NM_RNG);
            String rangeCol = ExcelUtils.getFirstColString(range);
            // 現在の列番号を取得
            currentCol = ExcelUtils.toColIndex(rangeCol);
            // セルタイプを取得する。
            String cellType = attributes.getValue(NM_TYP);
            // セルタイプが"s"の場合は文字列であることをフラグに設定
            if (cellType != null && cellType.equals(TYP_STR)) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
        }
        // コンテンツをクリア
        lastContents = "";
    }

    // 終了タグを取得した際に動作するメソッド
    public void endElement(String uri, String localName, String name)
            throws SAXException {
        // 文字列の場合は、今持っているlastContentsはSharedStringsTableのインデックスなので、文字列に置き換える。
        if (nextIsString) {
            // 直前の値を数値(参照先のインデックス)に変換
            int idx = Integer.parseInt(lastContents);
            // 文字列の場合はSharedStringsTableで別管理になっているので、そちらから取得
            lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
                    .toString();
            // フラグをクリア
            nextIsString = false;
        }

        // 値終了タグの場合
        if (name.equals(TAG_VAL)) {
            // lastContentsにセルの値が入っているので、ここでbeanにマッピングする
            try {
                arranger.arrange(currentRow, currentCol, lastContents);
            } catch (Exception e) {
                log.warn("beanに値を設定できませんでした。row=" + currentRow + ",col="
                        + currentCol + ",value=" + lastContents, e);
            }
        }
    }

    // 要素の値を保持するためのメソッド
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        // 現在の値を取得する
        lastContents += new String(ch, start, length);
    }

}


FieldValueArranger.java
package poi.example.eventmodel.mapping;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import poi.example.eventmodel.mapping.annotation.ImportField;
import poi.example.eventmodel.mapping.annotation.ImportListField;
import poi.example.util.ExcelUtils;
import poi.example.util.ReflectionUtils;

// row,col,valueを受け取り、beanへマッピングするクラス。
public class FieldValueArranger {

    private static Log log = LogFactory.getLog(FieldValueArranger.class);

    // 値を設定する対象のbean
    private Object bean = null;
    // 設定先のフィールドの情報
    private List<ImportFieldInfo> fieldInfoList = new ArrayList<ImportFieldInfo>();

    public FieldValueArranger(Object bean) {
        this.bean = bean;
        init(bean);
    }

    // マッピング対象のフィールド情報を収集します。
    private void init(Object bean) {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!initImportField(field)) {
                initImportBeanList(field);
            }
        }
    }

    // マッピング対象の単一フィールド情報を収集します。
    private boolean initImportField(Field field) {
        ImportField ifAn = field.getAnnotation(ImportField.class);
        if (ifAn != null) {
            fieldInfoList.add(new ImportFieldInfo(field, ifAn.row(), ExcelUtils
                    .toColIndex(ifAn.col())));
            return true;
        }
        return false;
    }

    // マッピング対象のリストフィールド情報を収集します。
    private void initImportBeanList(Field field) {
        ImportListField ilfAn = field.getAnnotation(ImportListField.class);
        if (ilfAn != null) {
            Class<?> childClazz = ReflectionUtils.getFirstChildClazz(field);
            Field[] childFields = childClazz.getDeclaredFields();
            for (Field childField : childFields) {
                ImportField ifAn = childField.getAnnotation(ImportField.class);
                if (ifAn != null) {
                    int childCol = ExcelUtils.toColIndex(ifAn.col());
                    // 子供のフィールドを追加
                    fieldInfoList.add(new ImportFieldInfo(field, childField,
                            ilfAn.startRow(), ilfAn.endRow(), childCol));
                }
            }
        }
    }

    // row,colを元にフィールドを特定し値を設定します
    public boolean arrange(int row, int col, String value) throws Exception {

        ImportFieldInfo info = getImportField(row, col);

        if (info != null) {
            if (info.isChildField()) {
                // リスト形式の項目へ設定
                info.parentField.setAccessible(true);
                @SuppressWarnings("unchecked")
                List<Object> children = (List<Object>) info.parentField
                        .get(bean);
                info.parentField.setAccessible(false);

                int rowIndex = info.getListIndex(row);

                Object childBean = null;
                if (children.size() - 1 < rowIndex) {
                    // 子クラスを取得する
                    Class<?> childClazz = info.childClazz;
                    // 子クラスのインスタンスを作る
                    childBean = childClazz.newInstance();
                    while (children.size() < rowIndex) {
                        children.add(null);
                    }
                    children.add(childBean);
                } else {
                    childBean = children.get(rowIndex);
                }
                // 子クラスのフィールドへ設定
                return setFieldValue(childBean, info.field, value);
            } else {
                // 単一項目へ設定
                return setFieldValue(bean, info.field, value);
            }
        }
        return false;
    }

    // row,colに対応するフィールド情報を取得する。
    private ImportFieldInfo getImportField(int row, int col) {
        return getImportField(null, row, col);
    }

    // row,colに対応するフィールド情報を取得する。
    private ImportFieldInfo getImportField(Field pareintField, int row, int col) {
        for (ImportFieldInfo range : fieldInfoList) {
            if (range.isIncleded(pareintField, row, col)) {
                return range;
            }
        }
        return null;
    }

    // fieldのclassに合わせた形式に変換し値を設定
    private boolean setFieldValue(Object bean, Field field, String value)
            throws Exception {
        field.setAccessible(true);
        if (field.getType().equals(String.class)) {
            // 文字列の場合
            field.set(bean, value);
        } else if (field.getType().equals(Integer.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getIntegerValue(value));
        } else if (field.getType().equals(Long.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getLongValue(value));
        } else if (field.getType().equals(Short.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getShortValue(value));
        } else if (field.getType().equals(Double.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getDoubleValue(value));
        } else if (field.getType().equals(Date.class)) {
            // 日付の場合
            field.set(bean, ExcelUtils.getDateValue(value));
        } else if (field.getType().equals(Boolean.class)) {
            // 真偽値の場合
            field.set(bean, ExcelUtils.getBooleanValue(value));
        } else {
            // その他
            field.setAccessible(false);
            return false;
        }
        field.setAccessible(false);
        return true;
    }

}


ImportFieldInfo.java
package poi.example.eventmodel.mapping;

import java.lang.reflect.Field;

import poi.example.util.ReflectionUtils;

// 値の設定先のフィールドの情報を管理するクラス
class ImportFieldInfo {

    // Listの子クラス
    Class<?> childClazz = null;
    // 親のフィールド
    Field parentField = null;
    // 対象のフィールド
    Field field = null;
    // 値を取得する対象となる範囲
    int startRow = 0;
    int endRow = 0;
    int col = 0;

    // beanのfield用のコンストラクタ
    ImportFieldInfo(Field field, int row, int col) {
        this(null, field, row, row, col);
    }

    // Listに保持されたbeanのfield用のコンストラクタ
    ImportFieldInfo(Field parentField, Field field, int startRow, int endRow,
            int col) {
        this.field = field;
        this.startRow = startRow;
        this.endRow = endRow;
        this.col = col;
        this.parentField = parentField;
        if (parentField != null) {
            this.childClazz = ReflectionUtils.getFirstChildClazz(parentField);
        }
    }

    // 指定のrow,colはfieldが値を取得する対象か
    boolean isIncleded(int row, int col) {
        return isIncleded(null, row, col);
    }

    // 指定のrow,colはfieldが値を取得する対象か(Listに保持されたbeanのfield用)
    boolean isIncleded(Field parentField, int row, int col) {
        if (parentField == null || parentField == this.parentField) {
            if (startRow <= row && row <= endRow && this.col == col) {
                return true;
            }
        }
        return false;
    }

    // rowに対応するListのindexを取得
    int getListIndex(int row) {
        return row - startRow;
    }

    // List配下のbeanのfieldか
    boolean isChildField() {
        return parentField != null;
    }

    @Override
    public String toString() {
        return "ImportFieldInfo [childClazz=" + childClazz + ", parentField="
                + parentField + ", field=" + field + ", startRow=" + startRow
                + ", endRow=" + endRow + ", col=" + col + "]";
    }

}


ExcelUtils.java
package poi.example.util;

import java.util.Date;

import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.util.CellReference;

// Excel関連の便利なものいろいろ
public class ExcelUtils {

    // TRUE扱いの値
    public static final String STRING_TRUE = "1";

    private ExcelUtils() {
    }

    // 日付形式で値を取得
    public static Date getDateValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = null;
        try {
            dblVal = getDoubleValue(value);
        } catch (Exception e) {
            throw new IllegalStateException("日付に変換できません。value=" + value, e);
        }
        // 1900年始まりのみ対応
        return DateUtil.getJavaDate(dblVal.doubleValue());
    }

    // 数値形式で値を取得
    public static Integer getIntegerValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Integer.valueOf(dblVal.intValue());
    }

    // 数値形式で値を取得
    public static Long getLongValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Long.valueOf(dblVal.longValue());
    }

    // 数値形式で値を取得
    public static Short getShortValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Short.valueOf(dblVal.shortValue());
    }

    // 数値形式で値を取得
    public static Double getDoubleValue(String value) {
        if (value == null) {
            return null;
        }
        try {
            return Double.parseDouble(value);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("数値に変換できません。value=" + value, e);
        }
    }

    // 真偽値形式で値を取得
    public static Boolean getBooleanValue(String value) {
        return Boolean.valueOf(STRING_TRUE.equals(value));
    }

    // A1:B1などの表記から先頭のカラム文字列を取り出す
    public static String getFirstColString(String range) {
        char[] cs = range.toCharArray();
        String col = "";
        for (char c : cs) {
            if ('0' <= c && c <= '9') {
                return col;
            } else {
                col += c;
            }
        }
        return col;
    }

    // A1表記からR1C1表記のカラムへ変換
    public static int toColIndex(String colString) {
        return CellReference.convertColStringToIndex(colString) + 1;
    }
}


ReflectionUtils.java
package poi.example.util;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

// リフレクション用のユーティリティクラス
public class ReflectionUtils {

    private ReflectionUtils() {
    }

    // Listの子要素のクラスを取得するメソッド
    public static Class<?> getFirstChildClazz(Field field) {
        // Genericも含めたTypeを取得
        Type type = field.getGenericType();
        // GenericのTypeを取得
        ParameterizedType pType = (ParameterizedType) type;
        return (Class<?>) pType.getActualTypeArguments()[0];
    }

}


Sheet.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 値の取得元になるシートを指定する為のアノテーション
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sheet {
    // シート名
    String name();

}


ImportField.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 単独の値のマッピングを行うためのアノテーション
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportField {
    // 行のデフォルト値
    int DFAULT_ROW = 0;
    // 行
    int row() default DFAULT_ROW;
    // 列
    String col();
}


ImportListField.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 繰り返し項目に設定するアノテーション
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportListField {
    // 終了行のデフォルト値
    int DFAULT_END_ROW = Integer.MAX_VALUE;
    // 開始行
    int startRow();
    // 終了行
    int endRow() default DFAULT_END_ROW;
}


ParentBean.java
package poi.example.eventmodel.mapping.bean;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import poi.example.eventmodel.mapping.annotation.ImportField;
import poi.example.eventmodel.mapping.annotation.ImportListField;
import poi.example.eventmodel.mapping.annotation.Sheet;

@Sheet(name = "Sheet1")
public class ParentBean {
    // セルB1の値をStringで取得
    @ImportField(row = 1, col = "B")
    private String field1 = null;
    // セルB2の値をDateで取得
    @ImportField(row = 2, col = "B")
    private Date field2 = null;
    // セルH1の値をBooleanで取得
    @ImportField(row = 1, col = "H")
    private Boolean field3 = null;
    // 6行目以降をChildBeanのリストとして取得
    @ImportListField(startRow = 6)
    private List<ChildBean> listField = new ArrayList<ChildBean>();

    @Override
    public String toString() {
        return "ParentBean [field1=" + field1 + ", field2=" + field2
                + ", field3=" + field3 + ", listField=" + listField + "]";
    }

}


ChildBean.java
package poi.example.eventmodel.mapping.bean;

import java.util.Date;

import poi.example.eventmodel.mapping.annotation.ImportField;

public class ChildBean {
    // ParentBeanのlistFieldで指定された行以降の、列Aの値をLongで取得
    @ImportField(col = "A")
    private Long field1 = null;
    // ParentBeanのlistFieldで指定された行以降の、列Bの値をStringで取得
    @ImportField(col = "B")
    private String field2 = null;
    // ParentBeanのlistFieldで指定された行以降の、列Cの値をDateで取得
    @ImportField(col = "C")
    private Date field3 = null;

    @Override
    public String toString() {
        return "ChildBean [field1=" + field1 + ", field2=" + field2
                + ", field3=" + field3 + "]";
    }

}

2012年8月12日日曜日

VB用Elements値取得関数


getElementsByName等で取得したInput等のElementsから値を取得する関数です。
input(text,password,file,hidden,checkbox,radio),select,textareaに対応しています。
異なるtype(tag)が混在しているElementsを渡した場合に正しく動作しない事があります。
'エレメントから値を取得
Function getElementsValue(ByRef objElms)
    getElementsValue = ""
    If objElms(0).type = "checkbox" Then
        getElementsValue = getCheckedElementsValue(objElms)
    ElseIf objElms(0).type = "radio" Then
        getElementsValue = getSelectedElementValue(objElms)
    Else
        getElementsValue = getTextElementsValue(objElms)
    End If
End Function

'text,password,select,file,hidden,textarea
Function getTextElementsValue(ByRef objElms)
    getTextElementsValue = ""
    Dim lngIu: lngIu = objElms.Length - 1
    If lngIu = 0 Then
        getTextElementsValue = objElms(0).Value
    ElseIf lngIu > 0 Then
        Dim strVals: ReDim strVals(lngIu)
        Dim lngI
        For lngI = 0 To lngIu
            strVals(lngI) = objElms(lngI).Value
        Next
        getTextElementsValue = strVals
    End If
End Function

'checkbox(選択されているものの値を取得)
Function getCheckedElementsValue(ByRef objElms)
    getCheckedElementsValue = ""
    Dim lngIu: lngIu = objElms.Length - 1
    If lngIu = 0 Then
        If objElms(0).Checked Then
            getCheckedElementsValue = objElms(0).Value
        End If
    ElseIf lngIu > 0 Then
        Dim strVals: ReDim strVals(lngIu)
        Dim lngI
        Dim lngSize: lngSize = 0
        For lngI = 0 To lngIu
            If objElms(lngI).Checked Then
                strVals(lngSize) = objElms(lngI).Value
                lngSize = lngSize + 1
            End If
        Next
        ReDim Preserve strVals(lngSize - 1)
        getCheckedElementsValue = strVals
    End If
End Function

'checkbox(選択されているかどうかを取得)
Function getElementsChecked(ByRef objElms)
    getElementsChecked = False
    Dim lngIu: lngIu = objElms.Length - 1
    If lngIu = 0 Then
        getElementsChecked = objElms(0).Checked
    ElseIf lngIu > 0 Then
        Dim strVals: ReDim strVals(lngIu)
        Dim lngI
        For lngI = 0 To lngIu
            strVals(lngI) = objElms(lngI).Checked
        Next
        getElementsChecked = strVals
    End If
End Function

'radio
Function getSelectedElementValue(ByRef objElms)
    getSelectedElementValue = ""
    Dim lngIu: lngIu = objElms.Length - 1
    Dim lngI
    For lngI = 0 To lngIu
        If objElms(lngI).Checked Then
            getSelectedElementValue = objElms(lngI).Value
        End If
    Next
End Function


VBScript用入力フォーム表示関数


InternetExplorer Objectを利用してVBScriptで入力フォームを表示する関数です。
入力フォームの内容をプロパティの指定だけで表示する簡易版と、HTMLで自由に設定できるHTML版を作成しました。
実行するにはVB用Elements値取得関数が必要です。
'配列で指定したプロパティの値を入力フォームから取得する。
Dim strProps: strProps = Array("FIELD1", "FIELD2", "FIELD3")
Dim strVals: strVals = showInputFormLite("Input", strProps, 300, 200)

'取得結果の表示
Dim lngIu: lngIu = UBound(strVals)
Dim lngI
Dim strMsg: strMsg = ""
For lngI = 0 To lngIu
    If IsArray(strVals(lngI)) Then
        Dim lngJu: lngJu = UBound(strVals(lngI))
        Dim lngJ
        For lngJ = 0 To lngJu
            strMsg = strMsg & strProps(lngI) & "='" & strVals(lngI)(lngJ) & "'" & vbCrLf
        Next
    Else
        strMsg = strMsg & strProps(lngI) & "='" & strVals(lngI) & "'" & vbCrLf
    End If
Next
WScript.echo strMsg

'============================================================
'入力フォーム表示(簡易版)
Function showInputFormLite(ByRef strTitle, ByRef strProps, ByRef lngWidth, ByRef lngHeight)
    Dim lngIu: lngIu = UBound(strProps)
    Dim lngI
    Dim strHtml: strHtml = ""
    strHtml = strHtml & "<form>"
    For lngI = 0 To lngIu
        strHtml = strHtml & strProps(lngI) & ": <input type='text' name='" & strProps(lngI) & "' /><br/>"
    Next
    strHtml = strHtml & "    <input type='hidden' name='submit_flg' value='0' />"
    strHtml = strHtml & "    <input type='button' onclick = ""getElementsByName('submit_flg')(0).value = '1';"" value='OK' />"
    strHtml = strHtml & "</form>"
    
    showInputFormLite = showInputForm(strTitle, strHtml, strProps, lngWidth, lngHeight)
End Function

'入力フォーム表示(HTML版)
Function showInputForm(ByRef strTitle, ByRef strHtml, ByRef strProps, ByRef lngWidth, ByRef lngHeight)
    'IEのインスタンスを生成
    Dim objIe: Set objIe = CreateObject("InternetExplorer.Application")
    With objIe
        'インプットフォーム初期値設定
        .Width = lngWidth
        .Height = lngHeight
        .ToolBar = False
        .StatusBar = False
        .Visible = True
        .Navigate "about:blank"
        'フォームが表示されるまで待機
        Do While .Busy
            WScript.Sleep 500
        Loop
        .Document.Title = strTitle
        .Document.Body.InnerHtml = strHtml
        'ボタン押下まで待機
        On Error Resume Next
        Dim blnErr: blnErr = False
        Do While objIe.Busy Or (getElementsValueByName(objIe, "submit_flg") <> "1")
            If Err.Number <> 0 Then
                On Error GoTo 0
                blnErr = True
                Exit Do
            End If
            WScript.Sleep 500
        Loop
        Dim lngIu: lngIu = UBound(strProps)
        Dim strVals: ReDim strVals(lngIu)
        If blnErr = False Then
            Dim lngI
            For lngI = 0 To lngIu
                strVals(lngI) = getElementsValueByName(objIe, strProps(lngI))
            Next
            .Quit
        End If
    End With
    Set objIe = Nothing
    showInputForm = strVals
End Function

'指定したnameのエレメントから値を取得
Function getElementsValueByName(Byref objIe, ByRef strName)
    getElementsValueByName = getElementsValue(objIe.Document.GetElementsByName(strName))
End Function

サクラエディタ用GrepDeleteStrategy


サクラエディタのGrep結果を基に行の削除を行うStrategyです。
実行するにはサクラエディタ用Grep結果処理マクロが必要です。
Class Strategy

    Private lngLSize
    Private lngFSize
    Private strPreviousPath

    'コンストラクタ
    Private Sub Class_Initialize()
        strPreviousPath = ""
        lngLSize = 0
        lngFSize = 0
    End Sub
    
    'デストラクタ
    Private Sub Class_Terminate()
    End Sub
    
    '開始処理
    Public Function execStartProc()
        execStartProc = False

        Dim objDialog: Set objDialog = CreateObject("WScript.Shell")
        Dim lngRtn: lngRtn = objDialog.Popup( _
            "Grep結果を基に行の削除を行います。" & vbCrLf & _
            "Grep結果以外のファイルは閉じてください。" & vbCrLf & _
            "またGrep結果はGrepReplace実行時に自動的に閉じられますので" & vbCrLf & _
            "結果を残したい場合は事前に保存をしてください。", _
            0, "GrepReplace", 1)
            
        If lngRtn = 1 Then
            execStartProc = True
        Else
            MsgBox "処理をキャンセルしました。"
        End If
        Set objDialog = Nothing
    End Function
    
    '前処理
    Public Function execPreProc(ByRef objGrepResults)
        execPreProc = True
    End Function
    
    '本処理
    Public Function execProc(ByRef objGrepResult)
        With objGrepResult
            If strPreviousPath <> .strFilePath Then
                If strPreviousPath <> "" Then
                    Call Editor.FileSave()
                    Call Editor.FileClose()
                End If
                Call Editor.FileOpen(.strFilePath)
                lngFSize = lngFSize + 1
            End If
            Call deleteLine(.strFilePath, .lngRow)
            lngLSize = lngLSize + 1
            strPreviousPath = .strFilePath
        End With
        execProc = True
    End Function

    '後処理
    Public Function execPostProc(ByRef objGrepResults)
        Call Editor.FileSave()
        Call Editor.FileClose()
        execPostProc = True
    End Function
    
    '終了処理
    Public Sub execEndProc()
        MsgBox lngFSize & "ファイル、合計" & lngLSize & "行削除しました。"
    End Sub
    
    'エラー処理
    Public Sub execErrProc(ByRef lngErrCode)
        If lngErrCode = ERR_FORMAT Then
            MsgBox "Grepファイルフォーマットエラー"
        Else
            MsgBox "エラー"
        End If
    End Sub
    
    Private Sub deleteLine(ByRef strFilePath, ByRef lngRow)
        With Editor
            Call .GoFileTop(0)
            Call .Jump(lngRow, 1)
            Call .DeleteLine()
            Call .ReDraw(0)
        End With
    End Sub

End Class

Call main(New Strategy)

サクラエディタ用GrepReplaceStrategy


サクラエディタのGrep結果を基に置換を行うStrategyです。
実行するにはサクラエディタ用Grep結果処理マクロが必要です。
Const OPT_WORD = 1    '単語単位で探す
Const OPT_DIFF = 2    '英大文字と小文字を区別する
Const OPT_RE = 4    '正規表現
Const OPT_MSG = 8    '見つからないときにメッセージを表示
Const OPT_DCLS = 16    '検索ダイアログを自動的に閉じる
Const OPT_LOOP = 32    '先頭(末尾)から再検索する
Const OPT_CB = 64    'クリップボードから貼り付ける
Const OPT_TGT = 128    '設定なし:ファイル全体、設定あり:選択範囲

Class Strategy

    Private lngLSize
    Private lngFSize
    Private strBefore
    Private strAfter
    Private lngReplaceOption
    Private strPreviousPath

    'コンストラクタ
    Private Sub Class_Initialize()
        strPreviousPath = ""
        lngReplaceOption = OPT_TGT + OPT_DCLS + OPT_DIFF
        lngLSize = 0
        lngFSize = 0
    End Sub
 
    'デストラクタ
    Private Sub Class_Terminate()
    End Sub
 
    '開始処理
    Public Function execStartProc()
        execStartProc = False

        strBefore = InputBox("置換前の文字列を入力してください。")
        If strBefore = "" Then
            MsgBox "処理をキャンセルしました。"
            Exit Function
        End If
        strAfter = InputBox("置換後の文字列を入力してください。")

        Dim objDialog: Set objDialog = CreateObject("WScript.Shell")
        Dim lngRtn: lngRtn = objDialog.Popup( _
            "Grep結果を基に下記の置換を行います。" & vbCrLf & _
            "置換前='" & strBefore & "'" & vbCrLf & _
            "置換後='" & strAfter & "'" & vbCrLf & _
            "Grep結果以外のファイルは閉じてください。" & vbCrLf & _
            "またGrep結果はGrepReplace実行時に自動的に閉じられますので" & vbCrLf & _
            "結果を残したい場合は事前に保存をしてください。", _
            0, "GrepReplace", 1)
         
        If lngRtn = 1 Then
            execStartProc = True
        Else
            MsgBox "処理をキャンセルしました。"
        End If
        Set objDialog = Nothing
    End Function
 
    '前処理
    Public Function execPreProc(ByRef objGrepResults)
        execPreProc = True
    End Function
 
    '本処理
    Public Function execProc(ByRef objGrepResult)
        With objGrepResult
            If strPreviousPath <> .strFilePath Then
                If strPreviousPath <> "" Then
                    Call Editor.FileSave()
                    Call Editor.FileClose()
                End If
                Call Editor.FileOpen(.strFilePath)
                lngFSize = lngFSize + 1
            End If
            Call replaceLine(.strFilePath, .lngRow, strBefore, strAfter)
            lngLSize = lngLSize + 1
            strPreviousPath = .strFilePath
        End With
        execProc = True
    End Function

    '後処理
    Public Function execPostProc(ByRef objGrepResults)
        Call Editor.FileSave()
        Call Editor.FileClose()
        execPostProc = True
    End Function
 
    '終了処理
    Public Sub execEndProc()
        MsgBox "'" & strBefore & "'→'" & strAfter & "'" & vbCrLf & _
            lngFSize & "ファイル、合計" & lngLSize & "行置換しました。"
    End Sub
 
    'エラー処理
    Public Sub execErrProc(ByRef lngErrCode)
        If lngErrCode = ERR_FORMAT Then
            MsgBox "Grepファイルフォーマットエラー"
        Else
            MsgBox "エラー"
        End If
    End Sub
 
    Private Sub replaceLine(ByRef strFilePath, ByRef lngRow, ByRef strBfr, ByRef strAft)
        With Editor
            Call .GoFileTop(0)
            Call .Jump(lngRow, 1)
            Call .GoLineTop(1)
            Call .BeginSelect()
            Call .GoLineEnd_Sel()
            Call .ReplaceAll(strBfr, strAft, lngReplaceOption)
            Call .ReDraw(0)
        End With
    End Sub

End Class

Call main(New Strategy)


2012年8月11日土曜日

サクラエディタ用Grep結果処理Strategy


サクラエディタのGrep結果を基に各種の処理をするStrategyのサンプルです。
これに処理を実装していけばサクラエディタ用Grep結果処理マクロを使用して、
Grep結果に対していろいろな処理を行うことができます。

Class Strategy

  'コンストラクタ
 Private Sub Class_Initialize()
 End Sub
 
 'デストラクタ
 Private Sub Class_Terminate()
 End Sub
 
 '開始処理
 Public Function execStartProc()
     MsgBox "開始処理"
     execStartProc = True
 End Function
 
 '前処理
 Public Function execPreProc(ByRef objGrepResults)
     MsgBox "前処理:" & UBound(objGrepResults)
     execPreProc = True
 End Function
 
 '本処理
 Public Function execProc(ByRef objGrepResult)
     With objGrepResult
          MsgBox "本処理:" & vbCrLf & "Path='" & .strFilePath & "'" & vbCrLf & "Row='" & .lngRow & "'" & vbCrLf & "Col='" & .lngCol & "'" & vbCrLf & "Text='" & .strText & "'"
     End With
     execProc = True
 End Function

  '後処理
 Public Function execPostProc(ByRef objGrepResults)
     MsgBox "後処理:" & UBound(objGrepResults)
     execPostProc = True
 End Function
 
 '終了処理
 Public Sub execEndProc()
     MsgBox "終了処理"
 End Sub
 
 'エラー処理
 Public Sub execErrProc(ByRef lngErrCode)
     MsgBox "エラー処理"
 End Sub

End Class

Call main(New Strategy)

サクラエディタ用Grep結果処理マクロ


サクラエディタのGrep結果を基に各種の処理をするために作成しました。
実行するにはサクラエディタ用Grep結果取得マクロが必要です。
--------------------------------------------------------------------------------
Const ERR_FORMAT = 1
Function main(ByRef objStrategy)
    '開始処理
    If objStrategy.execStartProc() = False Then
        Exit Function
    End If
    'Grepしたフォルダのパスを取得
    Dim strFolderPath: strFolderPath = getFolderPath()
    If strFolderPath = "" Then
        'フォルダのパスが取得できない場合はフォーマットエラー
        Call objStrategy.execErrProc(ERR_FORMAT)
        Exit Function
    End If
    'Grepの結果を全て取得
    Dim objGrepResults: objGrepResults = getGrepResults()
    Call Editor.FileClose()
    '前処理
    If objStrategy.execPreProc(objGrepResults) = False Then
        Exit Function
    End If
    Dim objGrepResult
    For Each objGrepResult In objGrepResults
        '本処理
        If objStrategy.execProc(objGrepResult) = False Then
            Exit For
        End If
    Next
    '後処理
    If objStrategy.execPostProc(objGrepResults) = False Then
        Exit Function
    End If
    '終了処理
    Call objStrategy.execEndProc()
End Function
--------------------------------------------------------------------------------