座標でセルにアクセス

odsファイルを開きシートにアクセス」の項目ではシートを表すxmlタグ「table:table」の要素を得るところまでやりました。 ここではテーブル要素の中を調べてセルの要素にアクセスするところまでを示します。

テーブル要素は以下のようになっています。

<table:table table:name="シート名" table:style-name="スタイル名" ...>
    <table:table-column table:style-name="スタイル名" .../>
    table:table-column要素(列ヘッダ情報)の繰り返し...
    
    <table:table-row table:style-name="スタイル名">
        <table:table-cell ...>...</table:table-cell>
        セル要素の繰り返し...
    </table:table-row>
    table:table-row要素(行情報)の繰り返し...
    
</table:table>

table:table-column要素には列ヘッダ情報が記録してあります。 列がグループ化されている場合は、その部分がtable:table-column-groupタグで囲まれます。 列ヘッダ情報には列のスタイル名や列に属するセルのデフォルトスタイル名などが記録されています。 スタイルなどの見た目の情報を使わない場合、読む必要はありません。

table:table-rowタグは各行のセル情報を子に持っています。 N個目の要素がN行目の情報を持っているならば簡単に目的の行にアクセスできるのですが、いくつかの例外があります。 列ヘッダの要素と同じように、行情報もグループ化してあるとtable:table-row-groupタグに囲まれます。 そして、全く同じ内容の列が繰り返される場合、1つの列にまとめて記録されます。 何列がまとめられているかはリピート属性table:number-rows-repeatedに記されています。

<table:table-row> 1行目
    セル要素の繰り返し...
</table:table-row>
<table:table-row> 2行目
    セル要素の繰り返し...
</table:table-row>
<table:table-row-group>
    <table:table-row> 3行目
        セル要素の繰り返し...
    </table:table-row>
    <table:table-row> 4行目
        セル要素の繰り返し...
    </table:table-row>
</table:table-row-group>
<table:table-row table:number-rows-repeated="3"> 5行目〜7行目
    セル要素の繰り返し...
</table:table-row>
...

行タグの子にあるtable:table-cellにセルの記述内容が記録されています。 セルタグも行タグと同じようにリピート属性を持っています。 属性名はtable:number-columns-repeatedです。 セルの繰り返しは横方向のみで縦に並んだセルには影響しません。

セルが結合されている場合、htmlのrowspanの様に何マス結合したかを属性に記録します。 行の結合数がtable:number-rows-spannedで、列の結合数がtable:number-columns-spannedです。 htmlの場合、属性にspanを書いたとき結合されたマスの記述は省略します。 Calcドキュメントの場合は省略されるのではなく、table:table-cellタグの代わりにtable:covered-table-cellタグが記入されます。 セルの結合数が多い場合、table:covered-table-cellタグにもリピート属性が付きます。

リピート属性の注意

Calcドキュメントのシートは最大で65536行256列(ver3.0では1024列)のセルを扱えます。 ですが、その全てを使うドキュメントは稀です。 普通のドキュメントは左上の一部のセルしか使っていないでしょう。 そのようなドキュメントでも65536行256列分の情報が記録されることがあります。

例えば、列ヘッダをクリックして列全体の背景色を変更すると、65536行の全てのスタイル情報が保存されます。 100行x10列を使ったシートの場合、A列の背景色をグレーにすると65536行x10列の情報が保存されます。 このとき101行目から65536行目までは空白列で全く同じ内容になります。 これらの行はリピート属性でまとめられます。 ただし、最後の行はなぜか独立して記録されます。 101行目以降を表すタグは次のようになります。

<table:table-row table:number-rows-repeated="65435"> 101行目〜最後の手前
    <table:table-cell table:number-columns-repeated="10"/>
</table:table-row>
<table:table-row> 最後の行
    <table:table-cell table:number-columns-repeated="10"/>
</table:table-row>

同様に行ヘッダをクリックして行全体のスタイルを変更すると、最後の方の空白セル情報にリピートが付きます。 こちらは最後のセルもまとめられるようです。

どのような操作をするとリピート属性が付くか詳しくは分かっていません。 ドキュメントを読み込むコードを作る場合、リピートが有っても無くても読み込めるようにコーディングしなければなりません。 65536行256列の情報があっても、そのほとんどが空白なら全て読み込むのは無駄です。 必要な情報のみ読み込むようにしなければなりません。 このサンプルでは、32行以上リピートが有った場合は空白行とみなしてシートの読み込みを終了しています。 セルも同様に32個以上リピートされている場合、以降のセルは空白とみなして列の読み込みを終了します。

サンプルコード

odsファイルを開きシートにアクセス」の項目で得たsheetElementを調べるコードです。 このサンプルでは、シートを選択したときに全てのセルを読んで2次元配列に収めています。 後から(行,列)の座標を指定してセル情報にアクセスできるようにしてあります。

次のコードはシートの行要素を読み込んで配列に収める部分です。

private Cell[][] _cells;

// 各セルの情報を文字列に変換し_cellsに記憶
private void parseSheetElement(Element sheetElement)
{
    _cells = null;
    if(sheetElement == null)
        return;
    
    // 同じ内容の行の繰り返しは1つのタグに記憶されているので
    // 行要素の数だけではシート上の行数はわからない
    // 読み込んだ行情報はひとまずcellsTmpに記憶する
    ArrayList<Cell[] > cellsTmp = new ArrayList<Cell[] >();
    
    int rowsRepeated;
    String rowsRepeatedStr;
    
    // 行情報は「table:table-row」タグに収められている
    // グループ化されている場合は「table:table-row-group」の中にある
    // getChildNodesでは直接列挙できないのでタグ名で探す。
    NodeList rowList = sheetElement.getElementsByTagName("table:table-row");
    int rowLen = rowList.getLength();
    for(int i = 0; i < rowLen; i++)
    {
        Element rowElement = (Element)rowList.item(i);
        
        // 行の繰り返し回数を記憶
        rowsRepeatedStr = rowElement.getAttribute("table:number-rows-repeated");
        if(rowsRepeatedStr.isEmpty() )
        {
            rowsRepeated = 1;
        }
        else
        {
            rowsRepeated = Integer.parseInt(rowsRepeatedStr);
            
            // 繰り返し回数が多すぎる場合、シートの終わりと判断し終了
            if(CRITERION_OF_END < rowsRepeated)
            {
                break;
            }
        }
        
        // 各行の情報は下請けメソッドで調べる
        Cell[] row = parseRowElement(rowElement);
        
        // 繰り返し回数分、cellsTmpに記憶
        for(int j = 0; j < rowsRepeated; j++)
        {
            cellsTmp.add(row);
        }
    }
    
    // cellsTmpを配列に直して_cellsに記憶
    _cells = new Cell[cellsTmp.size() ][];
    _cells = (Cell[][] )cellsTmp.toArray(_cells);
}

次のコードは行の子のセル要素を読み込んで配列に収める部分です。 おおまかな作りはparseSheetElementと同じです。 セルのタグがtable:table-cellの場合、結合セルの結合数を属性から読んでいます。 セルの値の読み方は次の項目で説明します。 セルのタグがtable:covered-table-cellの場合、定数インスタンスを配列に収めます。

// 行の各要素を文字列に変換してCellに入れて返す
private Cell[] parseRowElement(Element rowElement)
{
    ArrayList<Cell> rowTmp = new ArrayList<Cell>();
    int colsRepeated;
    String colsRepeatedStr;
    
    NodeList cellList = rowElement.getChildNodes();
    int cellLen = cellList.getLength();
    
    for(int i = 0; i < cellLen; i++)
    {
        Element cellElement = (Element)cellList.item(i);
        colsRepeatedStr = cellElement.getAttribute("table:number-columns-repeated");
        if(colsRepeatedStr.isEmpty() )
        {
            colsRepeated = 1;
        }
        else
        {
            colsRepeated = Integer.parseInt(colsRepeatedStr);
            if(CRITERION_OF_END < colsRepeated)
            {
                break;
            }
        }
        String tagName = cellElement.getTagName();
        
        Cell cell = null;
        if(tagName.equals("table:table-cell") )
        {
            // セルの結合サイズとスタイル名を調べる
            String rowSpan = cellElement.getAttribute("table:number-rows-spanned");
            String colSpan = cellElement.getAttribute("table:number-columns-spanned");
            String styleName = cellElement.getAttribute("table:style-name");
            
            // 下請け関数parseCellElementで各セルを文字列に変換し、Cellクラスを作る
            cell = new Cell(parseCellElement(cellElement), rowSpan, colSpan, styleName);
        }
        else if(tagName.equals("table:covered-table-cell") )
        {
            // 結合セルの場合、左上のセル以外はcoveredになる
            cell = Cell.COVERED;
        }
        else
        {
            System.out.println("Unkown tag [" + tagName + "]");
        }
        
        for(int j = 0; j < colsRepeated; j++)
        {
            rowTmp.add(cell);
        }
    }
    
    Cell[] res = new Cell[rowTmp.size() ];
    return rowTmp.toArray(res);
}