PHPWordで、既存のdocxファイルを無理やり編集するぞ

PHP
PHP Word
docx
word

PHPWordを使って、既存のdocxファイルを開き、パスワードバズワードに置換するコードです。

ついでに、編集履歴もつけちゃいます。

編集履歴をつけれるPHPWordのバージョンは、2018/03/09現在ではまだマスターリリースされていないので、

developのブランチを使います

composer require phpoffice/phpword:dev-develop
composer update
<?php

// 必要モジュールの読み込み
require_once 'vendor/autoload.php';

use PhpOfficePhpWordElementTrackChange;      // 編集履歴系のクラス

$search_word = 'パスワード';
$replace_word = 'バズワード';

// 読み込むファイルのパス
$source = __DIR__ . '/base.docx';

// DOCXファイルの読み込み
$phpWord = PhpOfficePhpWordIOFactory::load($source, 'Word2007');

// コピー先のdocx作成
$phpWord_copy = new PhpOfficePhpWordPhpWord();

// 編集履歴系のクラスをインスタンス化
$trackChangesView = new PhpOfficePhpWordComplexTypeTrackChangesView();

// 編集履歴記録開始
$phpWord->getSettings()->setTrackRevisions(true);

// セクションの取得
$sections = $phpWord->getSections();

// セクションの分だけ、ぶん回せ
foreach ($sections as $key => $value) {

    // コピー先にセクションを追加
    $section_copy = $phpWord_copy->addSection();

    // セクションの要素を全部取得
    $sectionElement = $value->getElements();

    // 要素の分だけくーるくる
    foreach ($sectionElement as $elementKey => $elementValue) {

        // getTextメソッドを持っているか確認 → 持っていたら、それはテキスト情報なので置換対象
        if (method_exists($elementValue, 'getText') === true) {

            // 置換したりとか
            makeTextData($elementValue, $section_copy, $search_word, $replace_word);

        } else {

            // テキストではない場合は、クラスによって処理を変える(無理やり感半端ない)
            $class = get_class($elementValue);

            switch ($class) {

                // 改行の場合
                case 'PhpOfficePhpWordElementTextBreak':
                    // 改行を追加
                    $section_copy->addTextBreak();
                    break;

                // リッチテキストの場合
                case 'PhpOfficePhpWordElementTextRun':
                    // リッチテキストの中の要素全取得
                    getTextRunsTexts($elementValue, $section_copy, $search_word, $replace_word);
                    break;

            }

        }


    }

}


// DOCXファイルに書き出し
$objWriter = PhpOfficePhpWordIOFactory::createWriter($phpWord_copy, 'Word2007');
$objWriter->save('php_output.docx');


/**
 * リッチテキストデータの中に含まれるテキストを抽出してセクションとして追加する
 *
 * @param $elementValue
 * @param $section_copy
 * @param $search_word
 * @param $replace_word
 */
function getTextRunsTexts(&$elementValue, &$section_copy, $search_word, $replace_word)
{

    // リッチテキストの中の要素全取得
    $sectionElement = $elementValue->getElements();

    // 回す
    foreach ($sectionElement as $sectionElementKey => $sectionElementValue) {

        // 純粋なテキストエリアの場合
        if (method_exists($sectionElementValue, 'getText') === true) {

            // 置換したりとか
            makeTextData($sectionElementValue, $section_copy, $search_word, $replace_word);

        }

    }

}


/**
 * テキストデータを作成して新しいdocxデータに追加していく
 *
 * @param $elementValue
 * @param $section_copy
 * @param $search_word
 * @param $replace_word
 */
function makeTextData(&$elementValue, &$section_copy, $search_word, $replace_word)
{

    // テキスト取得
    $text = htmlspecialchars($elementValue->getText());

    // テキストがスペースだけなら無視する(変に改行されるため)
    if ($text === ' ') {
        return;
    }

    // フォントスタイルの取得
    $font_style = $elementValue->getFontStyle();

    // パラグラフスタイル(多分色とかそういうのやと思う)を取得
    $paragraph_style = $elementValue->getParagraphStyle();

    // インデントがあるかの取得
    $indent = $paragraph_style->getIndentation();

    // インデントがあれば、デザインがめっさ崩れるので
    if ( method_exists($indent, 'getLeft') === true && $indent->getLeft() > 0 ){
        // 幅を小さくしてあげる
        $indent->setLeft($indent->getLeft()/1000);
    }

    // 取得したテキストをコピー先に作成
    $text_elem = $section_copy->addText($text, $font_style, $paragraph_style);

    // 特定ワードで置換してみる
    if (preg_match('/' . $search_word . '/', $text) === 1) {

        // 置換
        $text = str_replace($search_word, $replace_word, $text);

        // デリートの変更履歴を設定
        $text_elem->setChangeInfo(TrackChange::DELETED, 'Nな人');

        // 変更後のテキストを設定
        $new_text = $section_copy->addText($text, $font_style, $paragraph_style);

        // インサートの変更履歴を設定
        $new_text->setChangeInfo(TrackChange::INSERTED, 'Nな人');

    }

}

関連リンク

https://www.bricoleur.co.jp/blog/archives/3643