MS Officeの形状の中のテキストの取得方法の確認（TextRangeオブジェクト）

# はじめに

## 概要

MSOfficeの書式付きテキストから一部を取得（抽出）する方法について、備忘録がてらまとめます。

ここでいう「書式付きテキスト」は以下のものです。

|Office|対象|
|:--|:--|
|Excel|挿入したテキストボックスや、形状内のテキスト。|
|PowerPoint|通常使用する各種形状内のテキスト。|
|Word|本文および、挿入したテキストボックスや形状内のテキスト。|

## なぜ一部を取得する必要があるのか

書式付きのテキストの場合、それぞれの場所で書式が異なります。
そのため無造作に中のテキストを変更してしまうと、意図しない結果になることがあります（書式がすべて同じになるなど）。

意図しない動作を防ぐためには、必要な部分だけを取得して操作を行う必要があります。

## 今回の内容

以下のサイトの内容に加えて、類似メソッドの紹介等を含めたものとなります。

> [TextRange.RunsメソッドとTextRange2.Runsプロパティ:パワーポイントマクロ・PowerPoint VBAの使い方-TextFrame・TextRange](https://www.relief.jp/docs/powerpoint-vba-textrange-runs-and-textrange2-runs.html "TextRange.RunsメソッドとTextRange2.Runsプロパティ:パワーポイントマクロ・PowerPoint VBAの使い方-TextFrame・TextRange")

## その他

内容の間違い等あれば指摘していただけると助かります。
特に、Wordはあまり使用したことがないので誤りが含まれる可能性が高いです。


# 対象オブジェクト

今回説明の対象とするオブジェクトは以下の通りです。

## Office.TextRange2

`Microsoft Office X.X Object Library`内のオブジェクトです。

Excelの書式付きテキスト、PowerPointの書式付きテキストの操作に使用できます~~（Wordでも使用できそうですがアクセス方法がわかりません）~~。


2017/1/16 追記
Wordの場合について詳しい解説を頂きました。
> [Word VBAでTextFrame2・TextRange2を利用する:ワードマクロ・Word VBAの使い方-Shape・図形](https://www.relief.jp/docs/word-vba-textframe2-textrange2.html "Word VBAでTextFrame2・TextRange2を利用する:ワードマクロ・Word VBAの使い方-Shape・図形")

基本的にはWordでOffice.TextRange2は使用できないと思って良さそうです。
2017/1/16 追記ここまで

MSOffice共通のオブジェクトなのでコードの移植もしやすいです。

## PowerPoint.TextRange

`Microsoft PowerPoint X.X Object Library`内のオブジェクトです。

ライブラリが示す通り、PowerPointの書式付きテキストの操作に使用できます。

`Office.TextRange2`と比較すると、やや機能が少ないです。

## Word.Range

`Microsoft Word X.X Object Library`内のオブジェクトです。

ライブラリが示す通り、Wordの書式付きテキストの操作に使用できます。

Wordの本文のオブジェクトでもあるので、多くの機能が含まれています。

`Excel.Range`と名前が同じですが、全く別物なので注意が必要です。


# オブジェクトへのアクセス方法

各対象オブジェクトへのアクセス方法を簡単にまとめます。

## Excel

基本的に`Excel.Shape`または`Excel.ShapeRange`オブジェクトから辿っていきます。

```vb
式.TextFrame2.TextRange
```
式：`Excel.Shape`または`Excel.ShapeRange`オブジェクト

### サンプルコード

```vb:選択している形状のOffice.TextRange2を取得するforExcel
'現在選択している形状の持つOffice.TextRange2を取得する
    'セルを選択している場合、テキストを持っていない場合はNothing
    '複数選択されている場合は後処理が必要（ForEach）
Function SelectedTextRangeXl() As Office.TextRange2
    '選択対象チェック
    Select Case VBA.TypeName(Excel.Selection)
        Case "Range"    'セルを選択中
            GoTo Fail
            
        Case Else
            'Next
            
    End Select
    
    '今選択している形状群
    Dim selShpRng As Excel.ShapeRange
    Set selShpRng = Excel.Selection.ShapeRange
    
    'Shape/ShapeRange→TextFrame/TextFrame2→TextRange
    Dim selTxtFrm As Office.TextFrame2
    Set selTxtFrm = selShpRng.TextFrame2
    
    'テキスト存在チェック
    If selTxtFrm.HasText Then
        Set SelectedTextRangeXl = selTxtFrm.TextRange
    Else
        GoTo Fail
    End If
    
Exit Function

Fail:
    Set SelectedTextRangeXl = Nothing
Exit Function

End Function
```


## PowerPoint

基本的に`PowerPoint.Shape`または`PowerPoint.ShapeRange`オブジェクトから辿っていきます。

```vb
式.TextFrame.TextRange    'PowerPoint.TextRange
式.TextFrame2.TextRange   'Office.TextRange2
```
式：`PowerPoint.Shape`または`PowerPoint.ShapeRange`オブジェクト

### サンプルコード

`Office.TextRange2`を取得する場合のコードです。
最初の条件判定部分を除けば、上記の`SelectedTextRangeXl`とほぼ同じことがわかるかと思います。

```vb:選択している形状のOffice.TextRange2を取得するforPowerPoint
'選択しているテキスト、または形状の持つOffice.TextRange2を取得する
    '形状・テキスト以外を選択している場合、テキストを持っていない場合はNothing
    'テキストを選択している場合はそのテキストを示すOffice.TextRange2
    '形状を複数選択されている場合は後処理が必要（ForEach）
Function SelectedTextRangePpt() As Office.TextRange2
    Dim mySel As PowerPoint.Selection
    Set mySel = PowerPoint.ActiveWindow.Selection
    
    '選択対象チェック
    Select Case mySel.Type
        Case ppSelectionNone, ppSelectionSlides '未選択 Or スライドを選択中
            GoTo Fail

        Case ppSelectionText    'テキストにカーソルがある
            '特定の文字列を選択していた場合
            If mySel.TextRange2.Length <> 0 Then
                Set SelectedTextRangePpt = mySel.TextRange2
                Exit Function
            End If
            '何も文字を選択していない場合は形状選択の場合とみなす。
            
        Case ppSelectionShapes
            'Next
            
    End Select
    
    
    '今選択している形状群
    Dim selShpRng As PowerPoint.ShapeRange
    Set selShpRng = mySel.ShapeRange
    
    'Shape/ShapeRange→TextFrame/TextFrame2→TextRange
    Dim selTxtFrm As PowerPoint.TextFrame2
    Set selTxtFrm = selShpRng.TextFrame2

    'テキスト存在チェック
    If selTxtFrm.HasText Then
        Set SelectedTextRangePpt = selTxtFrm.TextRange
    Else
        GoTo Fail
    End If

Exit Function

Fail:
    Set SelectedTextRangePpt = Nothing
Exit Function

End Function
```

## Word

本文の場合は`Word.Document`の`Range`から、
形状内テキストの場合は`Word.ShapeRange`の`TextFrame.TextRange`から取得できそうです。

あまり触ったことがないのでこのあたりで。

WordのVBAに関しては以下のサイトが詳しいです。
>[みんなのワードマクロ](http://www.wordvbalab.com/ "みんなのワードマクロ")

# 書式付きテキストから一部を取得（抽出）する

とっちらかってきましたが、本題である「書式付きテキストから一部を取得（抽出）する」方法を確認していきます。

`Office.TextRange2`・`PowerPoint.TextRange`オブジェクトでは、以下のメソッドを使用することで取得単位毎に分解された一部を取得することができます。

|メソッド名|取得単位|
|:--|:--|
|Characters|文字|
|Lines|行|
|Paragraphs|段落|
|Runs|書式|
|Sentences|文章|
|Words|単語|

`Word.Range`の場合も同名のプロパティ（オブジェクト）があればそちらから取得することができます。

## 共通動作
StartとLengthの省略可能引数があります。
両方省略した場合は、含まれる文字列を取得単位ごとに分解した全てのTextRangeをItemに持つTextRangeを返します。

```text
最初のTextRange → メソッド戻り値のTextRange → ItemプロパティのTextRange（取得単位毎）
```

## Characters

元のTextRangeを、1文字毎に区切って取得できます。

ちなみに
純粋な改行<kbd><kbd>Shift</kbd>+<kbd>Enter</kbd></kbd>は
Chr(11)=vbVerticalTab

改段落<kbd>Enter</kbd>は
Chr(13)=vbCr

で表されます。

## Lines

元のTextRangeを、改行・改段落・自動折り返しによる改行毎（＝表示されている行毎）に区切って取得できます。

## Paragraphs

元のTextRangeを、改段落毎に区切って取得できます。

## Runs

元のTextRangeを、書式毎に区切って取得できます。

Officeのデフォルトだと、半角文字には英数字用フォント・全角文字には日本語用フォントが割り当てられるため、日本語と英語で書式が異なると判定され分割されます。

書式を維持しながら単語を差し替えたい場合などに便利です。

## Sentences

元のTextRangeを、文章毎に区切って取得できます。

文章の区切りとして扱われるのは改段落と「. 」（ドット + スペース）のようです。
つまり、日本語の文章の分割にはほぼ使えないと思ってよいです。

## Words

元のTextRangeを、単語毎に区切って取得できます。

日本語の文章で使用する場合、Sentencesに比べればいくらかましですが、精度に期待してはいけません。

# その他参考情報

### 段落の増殖

TextRangeを操作したとき、場合によっては段落が増えることがあります。
明確な条件はわかりませんが、どこかのタイミングで末尾にvbCrが自動付与されるようです。


### リンク
> [TextRange2 Object (Office) | Microsoft Docs](https://docs.microsoft.com/en-us/office/vba/api/office.textrange2 "TextRange2 Object (Office) | Microsoft Docs")
> [VBA 似ているようで違うExcel・Word・PowerPointのシェイプをオブジェクトブラウザーで比較する - t-hom’s diary](https://thom.hateblo.jp/entry/2016/01/05/005254 "VBA 似ているようで違うExcel・Word・PowerPointのシェイプをオブジェクトブラウザーで比較する - t-hom’s diary")