PowerCMS X ブログ

2024-10-18

コンテンツをComponentBlocksプラグインで編集できる状態になるよう取り込むには?

現行サイトのHTMLを取り込みComponentBlocksプラグインで編集できる状態にするには、各プロジェクト毎に変換プログラムを用意する必要があります。これはHTMLの構造も、ComponentBlocksの設定もプロジェクト毎に異なるためです。もしかするとプロジェクト内でもコンテンツ毎に設定が異なる可能性もあります。また、現行サイトのHTMLもモジュール(コンポーネント)が定義されており、各ページが定義された通りにコーディングされている必要もあります。

以上のことから標題の目的を達成するのはなかなか難しそうですが、もし変換プログラムを用意した場合はどのようなコードになるのか、参考程度にご紹介したいと思います。

言語と取り込み方法

言語はPHP・JavaScript(Node.js)いずれでもかまいませんが、HTMLのモジュールを扱うことからフロントエンド・エンジニアの方が触りやすいJavaScriptの方が向いているかもしれません。PHP・JavaScriptでHTMLを処理した後はCSVに書き出してインポートするか、APIをコールして直接PowerCMS Xに登録するかになります。

ComponentBlocksのデータはドキュメントページのブロックデータの解説でも紹介していますが、ID・コンポーネント名と、ブロック編集画面のフィールド・繰り返しフィールドに定義したキーから成るオブジェクトです。

[
    // 1ブロック目のデータ
    {
        "id":"1zzjuott",
        "type":"Text",
        "text":"<p>これは段落1です。</p>\n<p>これは段落2です。</p>"
    }

// 2ブロック目以降のデータが続く ]

サンプルコード

移行元のHTML

簡単なHTMLですが、p要素の他に2カラム構成、リストを入れてみました。

<body>
    <h1>サンプルドキュメント</h1>
    <div class="article-body">
        <p>これは段落1です。</p>
        <p>これは段落2です。</p>
        <div class="columns">
            <div class="col">
                <p>これは1カラム目の段落です。</p>
            </div>
            <div class="col">
                <p>これは2カラム目の段落です。</p>
            </div>
        </div>
        <p>これは段落3です。</p>
        <ul>
            <li>リストA</li>
            <li>リストB</li>
            <li>リストC</li>
        </ul>
    </div>
</body>

HTMLの分解部分

指定したURLにアクセスしてHTMLを取得し、多くの方がなじみのあるjQueryで処理してみます。ます、div.article-body(本文エリア)の子要素がどのような要素であるかを調べ、要素毎(つまりモジュールであり、ブロック定義であると思われます)にブロック定義を作成する関数を呼び出します。

const block = []; // ブロックのデータを保存していく
let stack = []; // p要素の仮置き場

// 取得したHTMLでjQueryを使えるようにする const { window } = new JSDOM(html); $ = jQuery(window);
// タイトルを取得 const title = $('title').text();
// .article-bodyの直下の子要素を順にたどる $('.article-body').children().each((index, element) => { const $element = $(element);
if (element.tagName.toLowerCase() === 'p') { // p要素の場合…複数のp要素をまとめたいので、一旦stackに保存 stack.push($element.prop('outerHTML')); } else { // p要素以外の場合 // stackに貯めているp要素をブロックデータに変換する block.push(makeTextBlock(stack)); stack = [];
if (element.tagName.toLowerCase() === 'ul') { // ul要素の場合… block.push(makeListBlock($element)); } else if (element.tagName.toLowerCase() === 'div' && $element.hasClass('columns')) { // classにcolumnsが指定されたdiv要素の場合 block.push(makeMultiColumnsBlock($element)); } } });
// stackに貯めているp要素をブロックデータに変換する if (stack.length) { block.push(makeTextBlock(stack)); }

ブロック定義を作成する関数

ブロックデータの解説のようなオブジェクトを目指し、ブロック毎にJSONオブジェクトを作成します。idは適当に自動生成、typeにブロック編集画面で設定したコンポーネント名、それ以外のデータはフィールド・繰り返しフィールドに定義したキーと対応するコンテンツ、というのが基本です。

/**
 * Textブロックのデータを生成する
 */
const makeTextBlock = (paragraphs) => {
    return {
        id: generateId(),
        type: 'Text',
        text: paragraphs.join("\n"),
    }
}

繰り返しフィールドの場合はfieldsに1フィールド毎のデータを詰めていきます。

/**
 * UnorderedListのデータを生成する
 */
const makeListBlock = ($ul) => {
    const fields = [];

$ul.find('li').each((index, element) => { const $element = $(element); // 1フィールド毎のデータを作成する fields.push({ id: generateId(), item: $element.html(), }); });
// 繰り返しフィールドを持つブロックのデータを返す return { id: generateId(), type: 'UnorderedList', fields, }; }

コードの全貌

下記コードを実行する前に、npm i dayjs jsdom jqueryで依存モジュールをインストールしておきます。

const fs = require('fs');
const dayjs = require('dayjs');
const { JSDOM } = require('jsdom');
const jQuery = require('jquery');
let $;

/** * 指定のURLからHTMLを取得 */ const getHTML = async (url) => { const response = await fetch(url); const html = await response.text(); return html; }
/** * IDの生成 */ const generateId = (length = 8) => { let id = ''; const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i += 1) { id += characters.charAt(Math.floor(Math.random() * charactersLength)); }
return id; }
/** * Textブロックのデータを生成する */ const makeTextBlock = (paragraphs) => { return { id: generateId(), type: 'Text', text: paragraphs.join("\n"), } }
/** * MultiColumnsブロックのデータを生成する */ const makeMultiColumnsBlock = ($container) => { const containers = [];
$container.children().each((index, element) => { const blocks = []; let stack = [];
$(element).children().each((index, childElement) => { const $childElement = $(childElement);
if (childElement.tagName.toLowerCase() === 'p') { stack.push($childElement.prop('outerHTML')); } else { blocks.push(makeTextBlock(stack)); stack = []; } });
if (stack.length) { blocks.push(makeTextBlock(stack)); }
containers.push({ id: generateId(), blocks, }); });
return { id: generateId(), type: 'MultiColumns', containers, } };
/** * UnorderedListのデータを生成する */ const makeListBlock = ($ul) => { const fields = [];
$ul.find('li').each((index, element) => { const $element = $(element); fields.push({ id: generateId(), item: $element.html(), }); });
return { id: generateId(), type: 'UnorderedList', fields, }; }
/** * URLのリスト */ const urls = [ 'http://develop.localhost/component_blocks/index.html', ];
/** * CSVファイル名 */ const date = dayjs().format('YYYYMMDDHHmmss'); const file = `entry_${date}.csv`; const BOM = '\ufeff'; fs.writeFileSync(file, BOM); fs.writeFileSync(file, "entry_title,entry_block_edit\n", { flag: 'a' }); // CSVの先頭行に記事モデルのカラム名を書き込む
/** * URLを巡回してブロックデータを作成する */ urls.forEach((url) => { getHTML(url) .then((html) => { const block = []; // ブロックのデータを保存していく let stack = []; // p要素の仮置き場
// 取得したHTMLでjQueryを使えるようにする const { window } = new JSDOM(html); $ = jQuery(window);
// タイトルを取得 const title = $('title').text();
// .article-bodyの直下の子要素を順にたどる $('.article-body').children().each((index, element) => { const $element = $(element);
if (element.tagName.toLowerCase() === 'p') { // p要素の場合…複数のp要素をまとめたいので、一旦stackに保存 stack.push($element.prop('outerHTML')); } else { // p要素以外の場合 // stackに貯めているp要素をブロックデータに変換する block.push(makeTextBlock(stack)); stack = [];
if (element.tagName.toLowerCase() === 'ul') { // ul要素の場合… block.push(makeListBlock($element)); } else if (element.tagName.toLowerCase() === 'div' && $element.hasClass('columns')) { // classにcolumnsが指定されたdiv要素の場合 block.push(makeMultiColumnsBlock($element)); } } });
// stackに貯めているp要素をブロックデータに変換する if (stack.length) { block.push(makeTextBlock(stack)); }
// データをコンソールに表示 // console.dir(block, { depth: null })
// カンマ区切りで書き出す const blockData = JSON.stringify(block).replace(/(?<!\\)"/g, '""'); fs.writeFileSync(file, `${title},"${blockData}"\n`, { flag: 'a' }); }); });

HTMLを変換した結果のJSON

一度管理画面のブロックエディタで手入力を行って目指すべきJSONを確認し、JavaScriptで下記のようなJSONが得られるようになりました。

[
  {
    id: 'ryhnd92f',
    type: 'Text',
    text: '<p>これは段落1です。</p>\n<p>これは段落2です。</p>'
  },
  {
    id: 'lh0cfz4w',
    type: 'MultiColumns',
    containers: [
      {
        id: 't8ciitau',
        blocks: [
          {
            id: 'igtjp79d',
            type: 'Text',
            text: '<p>これは1カラム目の段落です。</p>'
          }
        ]
      },
      {
        id: 'l2w60fd2',
        blocks: [
          {
            id: '3637m8tt',
            type: 'Text',
            text: '<p>これは2カラム目の段落です。</p>'
          }
        ]
      }
    ]
  },
  {
    id: '99boe8xz',
    type: 'Text',
    text: '<p>これは段落3です。</p>'
  },
  {
    id: 'qqantaav',
    type: 'UnorderedList',
    fields: [
      { id: 'fpy040ix', item: 'リストA' },
      { id: 'tty6uum9', item: 'リストB' },
      { id: 'f7528hx1', item: 'リストC' }
    ]
  }
]

サンプルコードの実行結果を取り込んだ画面

定義したブロック毎にコンテンツが入った状態になりました。(クリックすると拡大表示します。)
ブロックエディタにインポートしたデータが表示された画面

カテゴリー:技術情報

投稿者:安倍

ブログ内検索

アーカイブ