不要タグを除去できるsummernote-cleanerプラグインを使ってみる

前回まででカスタマイズしたSummernoteにWordからコピーした文章をペーストしたら以下のように不要なタグや属性が付与されていた。ペースト時に不要なタグや属性を除去できるsummernote-cleanerプラグインを利用してみる。

文章の貼り付け結果

動作確認環境

  • jQuery(3.7.1)
  • Bootstrap(5.3.5)
  • Summernote(0.9.1)
    • summernote-cleaner(1.0.9)

参考:前回までのソース

summernote-cleanerの取得と導入

jsDelivrを確認したところ、どうやらCDNには少し古いバージョン(1.0.5)しか置いていないようだったので、2025年8月時点の最新バージョンである1.0.9をGitHubのリポジトリから取得し、前回までのカスタマイズソースにsummernote-cleaner.jsファイルを追加した。

 root
  |-- css
  |    |-- app.css
  |    |-- summernote-bs5-editor-custom.css
  |-- js
  |    |-- summernote-cleaner.js
  |    |-- summernote-init.js
  |-- index.html

追加したsummernote-cleanerを使う側で読み込むだけで有効になる。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Summernoteカスタマイズ</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/summernote-bs5.min.css" type="text/css" />
    <link rel="stylesheet" href="./css/app.css" type="text/css" />
    <link rel="stylesheet" href="./css/summernote-bs5-editor-custom.css" type="text/css" />

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/summernote-bs5.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/lang/summernote-ja-JP.min.js" type="text/javascript"></script>
    <script src="./js/summernote-cleaner.js" type="text/javascript"></script>
    <script src="./js/summernote-init.js" type="text/javascript"></script>
  </head>
  <body>


summernoe-cleaner読み込み後:画面下部にステータスバーが表示される

この状態で利用するとメモ帳で編集したテキストはペーストできるが、Wordからはできなかった。
開発者ツールで確認してみたところ、Wordからの貼り付け時に以下のエラーが発生していた。

どうやらjQueryで「data-(.*?)」を検索しようとしてエラーとなっているようだった。
summernote-cleanerのデフォルトオプションを確認するとbadAttributesオプションのコメントに「‘data-(.*?)’ would fail when cleaning with jQuery」と記載があったので実装を調べてみた。

js/summernote-cleaner.js
$.extend($.summernote.options, {
  cleaner: {
    action: 'both', // both|button|paste 'button' only cleans via toolbar button, 'paste' only clean when pasting content, both does both options.
    icon: '<i class="note-icon"><svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg></i>',
    keepHtml: true,
    keepTagContents: ['span'], //Remove tags and keep the contents
    badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'], //Remove full tags with contents
    badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width', 'data-(.*?)'], //Remove attributes from remaining tags, NB. 'data-(.*?)' would fail when cleaning with jQuery
    limitChars: 0, // 0|# 0 disables option
    limitDisplay: 'both', // none|text|html|both
    limitStop: false, // true/false
    limitType: 'text', // text|html
    notTimeOut: 850, //time before status message is hidden in miliseconds
    keepImages: true,
    imagePlaceholder: 'https://via.placeholder.com/200'
  }
});

summernote-cleanerのソースを追いかけていくとペースト時に「cleanHtmlPasteWithjQuery」または「cleanHtmlPasteWithRegExp」関数で不要タグや属性の除去をしているようだった。

js/summernote-cleaner.js
var cleanHtmlPaste = function (input, badTags, keepTagContents, badAttributes, keepImages, imagePlaceholder) {
  if (typeof (window.jQuery) === 'function') {
    return cleanHtmlPasteWithjQuery(input, badTags, keepTagContents, badAttributes, keepImages, imagePlaceholder)
  } else {
    return cleanHtmlPasteWithRegExp(input, badTags, keepTagContents, badAttributes, keepImages, imagePlaceholder)
  }
}

画面でjQueryを読み込んでいる場合、「cleanHtmlPasteWithjQuery」の関数で除去処理が実施されるが、badAttributesオプションの「data-(.*?)」が正規表現の形式になっているため、不要な属性の検索でエラーとなってしまうようだ。
解決策としてはsummernote-cleanerの利用側で「data-(.*?)」を削除してしまえば良いため、summrenote-initにsummernote-cleanerの設定を追加した(badAttributes以外のオプションはデフォルトのまま)。

js/summernote-init.js
(function($) {

  // 日本語未対応の文言を日本語化
  $.extend(true, $.summernote.lang, {
    'ja-JP': {
      font: {
        subscript: '下付き',
        superscript: '上付き',
      },
    },
  });

  // Summernote初期化
  $.fn.initSummernote = function (options) {

    // デフォルトオプション
    const defaultOptions = {
      lang: 'ja-JP',
      fontSizes: ['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72'],
      toolbar: [
        ['style', ['style']],
        ['font', ['fontsize', 'fontsizeunit', 'forecolor', 'backcolor']],
        ['fontstyle', ['bold', 'italic', 'strikethrough', 'underline', 'subscript', 'superscript', 'paragraph', 'clear']],
        ['para', ['ul', 'ol']],
        ['table', ['table']],
        ['insert', ['link', 'picture', 'video']],
        ['operation', ['undo', 'redo']],
        ['view', ['codeview', 'fullscreen', 'help']],
      ],
      styleTags: [
        {tag: 'h1', title: '主見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h1'},
        {tag: 'h2', title: '見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h2'},
        {tag: 'h3', title: '小見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h3'},
      ],
      cleaner: {
        action: 'both',
        icon: '<i class="note-icon"><svg height="14" id="libre-paintbrush" viewbox="0 0 14 14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"></path></svg></i>',
        keepHtml: true,
        keepTagContents: ['span'],
        badTags: ['applet', 'col', 'colgroup', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'],
        badAttributes: ['bgcolor', 'border', 'height', 'cellpadding', 'cellspacing', 'lang', 'start', 'style', 'valign', 'width'],
        limitChars: 0,
        limitDisplay: 'both',
        limitStop: false,
        limitType: 'text',
        notTimeOut: 850,
        keepImages: true,
        imagePlaceholder: 'https://via.placeholder.com/200'
      },
      callbacks: {
        onInit: function(note) {
          // ファイル選択をBootstrapのレイアウトにする
          note.editor.find('.note-modal .form-control-file').each(function () {
            $(this).addClass('form-control').removeClass('form-control-file');
          });
          // optionsに拡張プロパティのonInitAfterが設定されてたらコールする
          $(this).data('summernote').options?.callbacks?.onInitAfter?.(note);
        },
      }
    }

    // オプションをマージ
    settings = $.extend(true, {}, defaultOptions, options);

    // Summernoteを初期化
    $(this).summernote(settings);

    return $(this);
  };
})(jQuery);

これで試してみると無事Wordからコピーした文章を貼付けできており不要なタグや属性が削除されていた。

summernote-cleaner導入後の文章の貼り付け結果

貼り付け時にdata属性も除去したい場合は、summernote-cleanerの「cleanHtmlPasteWithRegExp」を利用する必要があるが、jQueryが読み込まれていると「cleanHtmlPasteWithjQuery」が呼び出されてしまう。
SummernoteはjQueryが必要なので、必ず「cleanHtmlPasteWithRegExp」を呼び出すようにsummernote-cleanerのソースを修正する必要があると思われる。

summernote-cleanerのカスタマイズ

ペースト時に不要なタグと属性を除去できるようになったが、せっかくなので設定を調整して自分好みにカスタマイズしてみる。

①除去するタグと属性を調整する

badTags、badAttributesプロパティに除去されるタグと属性がそれぞれ設定されているので、これを変更する。
badTagsでは’col’と’colgroup’を除外し、badAttributesでは’width’、’height’、’style’を除外することとした。

js/summernote-init.js



      cleaner: {
        action: 'both',
        icon: '<i class="note-icon"><svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg></i>',
        keepHtml: true,
        keepTagContents: ['span'],
        badTags: ['applet', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'],
        badAttributes: ['bgcolor', 'border', 'cellpadding', 'cellspacing', 'lang', 'start', 'valign'],
        limitChars: 0,
        limitDisplay: 'both',
        limitStop: false,
        limitType: 'text',
        notTimeOut: 850,
        keepImages: true,
        imagePlaceholder: 'https://via.placeholder.com/200'
      },


②入力可能文字数を制限する

limitCharsなど、limit〇〇のプロパティで入力可能文字数を制限できる。今回は以下の設定に変更した。

プロパティ設定値備考
limitType‘text’テキストの文字数で制限をかける(HTMLではない)
limitChars3000入力可能文字数を3000文字に制限
limitStoptrue入力可能文字数に達したら入力を制限する
limitDisplay‘text’ステータス領域の表示内容をテキストの文字数のみにする
js/summernote-init.js



      cleaner: {
        action: 'both',
        icon: '<i class="note-icon"><svg xmlns="http://www.w3.org/2000/svg" id="libre-paintbrush" viewBox="0 0 14 14" width="14" height="14"><path d="m 11.821425,1 q 0.46875,0 0.82031,0.311384 0.35157,0.311384 0.35157,0.780134 0,0.421875 -0.30134,1.01116 -2.22322,4.212054 -3.11384,5.035715 -0.64956,0.609375 -1.45982,0.609375 -0.84375,0 -1.44978,-0.61942 -0.60603,-0.61942 -0.60603,-1.469866 0,-0.857143 0.61608,-1.419643 l 4.27232,-3.877232 Q 11.345985,1 11.821425,1 z m -6.08705,6.924107 q 0.26116,0.508928 0.71317,0.870536 0.45201,0.361607 1.00781,0.508928 l 0.007,0.475447 q 0.0268,1.426339 -0.86719,2.32366 Q 5.700895,13 4.261155,13 q -0.82366,0 -1.45982,-0.311384 -0.63616,-0.311384 -1.0212,-0.853795 -0.38505,-0.54241 -0.57924,-1.225446 -0.1942,-0.683036 -0.1942,-1.473214 0.0469,0.03348 0.27455,0.200893 0.22768,0.16741 0.41518,0.29799 0.1875,0.130581 0.39509,0.24442 0.20759,0.113839 0.30804,0.113839 0.27455,0 0.3683,-0.247767 0.16741,-0.441965 0.38505,-0.753349 0.21763,-0.311383 0.4654,-0.508928 0.24776,-0.197545 0.58928,-0.31808 0.34152,-0.120536 0.68974,-0.170759 0.34821,-0.05022 0.83705,-0.07031 z"/></svg></i>',
        keepHtml: true,
        keepTagContents: ['span'],
        badTags: ['applet', 'embed', 'noframes', 'noscript', 'script', 'style', 'title', 'meta', 'link', 'head'],
        badAttributes: ['bgcolor', 'border', 'cellpadding', 'cellspacing', 'lang', 'start', 'valign'],
        limitChars: 3000,
        limitDisplay: 'text',
        limitStop: true,
        limitType: 'text',
        notTimeOut: 850,
        keepImages: true,
        imagePlaceholder: 'https://via.placeholder.com/200'
      },


③クリーナーボタンをツールバーに表示する

クリーナーボタンを表示すると、ボタン押下で本文全体に対して不要なタグと属性を除去を実行できるようになる。
actionプロパティが’both’または’button’であれば以下の設定を追加することで表示できる。
※本文全体に対して適用されるので文章が長いとかなり時間がかかるため注意が必要

js/summernote-init.js



    // デフォルトオプション
    const defaultOptions = {
      lang: 'ja-JP',
      fontSizes: ['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72'],
      toolbar: [ 
        ['style', ['style']],
        ['font', ['fontsize', 'fontsizeunit', 'forecolor', 'backcolor']],
        ['fontstyle', ['bold', 'italic', 'strikethrough', 'underline', 'subscript', 'superscript', 'paragraph', 'clear']],
        ['para', ['ul', 'ol']],
        ['table', ['table']],
        ['insert', ['link', 'picture', 'video']],
        ['cleaner', ['cleaner']],
        ['operation', ['undo', 'redo']],
        ['view', ['codeview', 'fullscreen', 'help']],
      ],


④クリーナーボタンのツールチップやメッセージの文言をカスタマイズする

クリーナーボタンにマウスをフォーカスした際のツールチップや、除去実行時にステータスバーに表示される文言を自分好みに変更した。

js/summernote-init.js
(function($) {

  // 日本語未対応の文言を日本語化
  $.extend(true, $.summernote.lang, {
    'ja-JP': {
      font: {
        subscript: '下付き',
        superscript: '上付き',
      },
      cleaner: {
        tooltip: '不要なタグを除去',
        not: '不要なタグを除去しました',
        limitText: '入力可能文字数',
        limitHTML: '入力可能文字数(タグ含む)'
      },
    },
  });


実装結果

コメント