Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #62 from ckeditor/t/61
Browse files Browse the repository at this point in the history
Feature: Prevent of bolding entire content pasted from Google Docs. Closes #61.
  • Loading branch information
jodator committed Jul 29, 2019
2 parents 70fe6fd + ee7e3a0 commit 8102de3
Show file tree
Hide file tree
Showing 31 changed files with 770 additions and 394 deletions.
25 changes: 25 additions & 0 deletions src/filters/removeboldwrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module paste-from-office/filters/removeboldwrapper
*/

/**
* Removes `<b>` tag wrapper added by Google Docs to a copied content.
*
* @param {module:engine/view/documentfragment~DocumentFragment} documentFragment element `data.content` obtained from clipboard
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
*/
export default function removeBoldWrapper( documentFragment, writer ) {
for ( const child of documentFragment.getChildren() ) {
if ( child.is( 'b' ) && child.getStyle( 'font-weight' ) === 'normal' ) {
const childIndex = documentFragment.getChildIndex( child );

writer.remove( child );
writer.insertChild( childIndex, child.getChildren(), documentFragment );
}
}
}
34 changes: 34 additions & 0 deletions src/normalizer.jsdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module paste-from-office/normalizer
*/

/**
* Interface defining a content transformation pasted from an external editor.
*
* Normalizers are registered by the {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin and run on
* {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. They detect environment-specific
* quirks and transform it into a form compatible with other CKEditor features.
*
* @interface Normalizer
*/

/**
* Must return `true` if the `htmlString` contains content which this normalizer can transform.
*
* @method #isActive
* @param {String} htmlString full content of `dataTransfer.getData( 'text/html' )`
* @returns {Boolean}
*/

/**
* Executes the normalization of a given data.
*
* @method #execute
* @param {Object} data object obtained from
* {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}.
*/
36 changes: 36 additions & 0 deletions src/normalizers/googledocsnormalizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module paste-from-office/normalizers/googledocsnormalizer
*/

import removeBoldWrapper from '../filters/removeboldwrapper';
import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';

const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i;

/**
* Normalizer for the content pasted from Google Docs.
*
* @implements module:paste-from-office/normalizer~Normalizer
*/
export default class GoogleDocsNormalizer {
/**
* @inheritDoc
*/
isActive( htmlString ) {
return googleDocsMatch.test( htmlString );
}

/**
* @inheritDoc
*/
execute( data ) {
const writer = new UpcastWriter();

removeBoldWrapper( data.content, writer );
}
}
41 changes: 41 additions & 0 deletions src/normalizers/mswordnormalizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module paste-from-office/normalizers/mswordnormalizer
*/

import { parseHtml } from '../filters/parse';
import { transformListItemLikeElementsIntoLists } from '../filters/list';
import { replaceImagesSourceWithBase64 } from '../filters/image';

const msWordMatch1 = /<meta\s*name="?generator"?\s*content="?microsoft\s*word\s*\d+"?\/?>/i;
const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i;

/**
* Normalizer for the content pasted from Microsoft Word.
*
* @implements module:paste-from-office/normalizer~Normalizer
*/
export default class MSWordNormalizer {
/**
* @inheritDoc
*/
isActive( htmlString ) {
return msWordMatch1.test( htmlString ) || msWordMatch2.test( htmlString );
}

/**
* @inheritDoc
*/
execute( data ) {
const { body, stylesString } = parseHtml( data.dataTransfer.getData( 'text/html' ) );

transformListItemLikeElementsIntoLists( body, stylesString );
replaceImagesSourceWithBase64( body, data.dataTransfer.getData( 'text/rtf' ) );

data.content = body;
}
}
72 changes: 34 additions & 38 deletions src/pastefromoffice.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import { parseHtml } from './filters/parse';
import { transformListItemLikeElementsIntoLists } from './filters/list';
import { replaceImagesSourceWithBase64 } from './filters/image';
import GoogleDocsNormalizer from './normalizers/googledocsnormalizer';
import MSWordNormalizer from './normalizers/mswordnormalizer';
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';

/**
* The Paste from Office plugin.
*
* This plugin handles content pasted from Office apps (for now only Word) and transforms it (if necessary)
* This plugin handles content pasted from Office apps and transforms it (if necessary)
* to a valid structure which can then be understood by the editor features.
*
* Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer normalizers}.
* This plugin includes following normalizers:
* * {@link module:paste-from-office/normalizer/mswordnormalizer~MSWordNormalizer Microsoft Word normalizer}
* * {@link module:paste-from-office/normalizer/googledocsnormalizer~GoogleDocsNormalizer Google Docs normalizer}
*
* For more information about this feature check the {@glink api/paste-from-office package page}.
*
* @extends module:core/plugin~Plugin
Expand All @@ -31,49 +36,40 @@ export default class PasteFromOffice extends Plugin {
return 'PasteFromOffice';
}

/**
* @inheritDoc
*/
static get requires() {
return [ Clipboard ];
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const normalizers = [];

this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', ( evt, data ) => {
const html = data.dataTransfer.getData( 'text/html' );
normalizers.push( new MSWordNormalizer() );
normalizers.push( new GoogleDocsNormalizer() );

if ( data.pasteFromOfficeProcessed !== true && isWordInput( html ) ) {
data.content = this._normalizeWordInput( html, data.dataTransfer );
editor.plugins.get( 'Clipboard' ).on(
'inputTransformation',
( evt, data ) => {
if ( data.isTransformedWithPasteFromOffice ) {
return;
}

// Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44).
data.pasteFromOfficeProcessed = true;
}
}, { priority: 'high' } );
}

/**
* Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}.
*
* **Note**: this function was exposed mainly for testing purposes and should not be called directly.
*
* @protected
* @param {String} input Word input.
* @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance.
* @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input.
*/
_normalizeWordInput( input, dataTransfer ) {
const { body, stylesString } = parseHtml( input );
const htmlString = data.dataTransfer.getData( 'text/html' );
const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) );

transformListItemLikeElementsIntoLists( body, stylesString );
replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) );
if ( activeNormalizer ) {
activeNormalizer.execute( data );

return body;
data.isTransformedWithPasteFromOffice = true;
}
},
{ priority: 'high' }
);
}
}

// Checks if given HTML string is a result of pasting content from Word.
//
// @param {String} html HTML string to test.
// @returns {Boolean} True if given HTML string is a Word HTML.
function isWordInput( html ) {
return !!( html && ( html.match( /<meta\s*name="?generator"?\s*content="?microsoft\s*word\s*\d+"?\/?>/gi ) ||
html.match( /xmlns:o="urn:schemas-microsoft-com/gi ) ) );
}
51 changes: 51 additions & 0 deletions tests/_data/paste-from-google-docs/bold-wrapper/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import simpleText from './simple-text/input.html';
import simpleTextWindows from './simple-text-windows/input.html';

import simpleTextNormalized from './simple-text/normalized.html';
import simpleTextWindowsNormalized from './simple-text-windows/normalized.html';

import simpleTextModel from './simple-text/model.html';
import simpleTextWindowsModel from './simple-text-windows/model.html';

export const fixtures = {
input: {
simpleText,
simpleTextWindows
},
normalized: {
simpleText: simpleTextNormalized,
simpleTextWindows: simpleTextWindowsNormalized
},
model: {
simpleText: simpleTextModel,
simpleTextWindows: simpleTextWindowsModel
}
};

import simpleTextFirefox from './simple-text/input.firefox.html';
import simpleTextWindowsFirefox from './simple-text-windows/input.firefox.html';

import simpleTextNormalizedFirefox from './simple-text/normalized.firefox.html';
import simpleTextWindowsNormalizedFirefox from './simple-text-windows/normalized.firefox.html';

export const browserFixtures = {
firefox: {
input: {
simpleText: simpleTextFirefox,
simpleTextWindows: simpleTextWindowsFirefox
},
normalized: {
simpleText: simpleTextNormalizedFirefox,
simpleTextWindows: simpleTextWindowsNormalizedFirefox
},
model: {
simpleText: simpleTextModel,
simpleTextWindows: simpleTextWindowsModel
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<html><body>
<!--StartFragment--><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" id="docs-internal-guid-f8b26bf1-7fff-40c0-af18-1234567890ab"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p><!--EndFragment-->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
<!--StartFragment--><b style="font-weight:normal;" id="docs-internal-guid-0954d9f2-7fff-2b3c-4978-1234567890ab"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p></b><br class="Apple-interchange-newline"><!--EndFragment-->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<paragraph>Hello world</paragraph>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p dir="ltr" id="docs-internal-guid-f8b26bf1-7fff-40c0-af18-1234567890ab" style="line-height:1.38;margin-bottom:0pt;margin-top:0pt"><span style="background-color:transparent;color:#000000;font-family:Arial;font-size:11pt;font-style:normal;font-variant:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Hello world</span></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p dir="ltr" style="line-height:1.38;margin-bottom:0pt;margin-top:0pt"><span style="background-color:transparent;color:#000000;font-family:Arial;font-size:11pt;font-style:normal;font-variant:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Hello world</span></p><br class="Apple-interchange-newline">
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"><style type="text/css">ol{margin:0;padding:0}table td,table th{padding:0}.c2{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Arial";font-style:normal}.c1{padding-top:0pt;padding-bottom:0pt;line-height:1.15;orphans:2;widows:2;text-align:left}.c0{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.title{padding-top:0pt;color:#000000;font-size:26pt;padding-bottom:3pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:0pt;color:#666666;font-size:15pt;padding-bottom:16pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Arial"}p{margin:0;color:#000000;font-size:11pt;font-family:"Arial"}h1{padding-top:20pt;color:#000000;font-size:20pt;padding-bottom:6pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-size:16pt;padding-bottom:6pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:16pt;color:#434343;font-size:14pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:14pt;color:#666666;font-size:12pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}</style></head><body class="c0"><p class="c1"><span>Hello world</span></p></body></html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<meta charset="utf-8"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" id="docs-internal-guid-b205dcb1-7fff-2468-bad5-1234567890ab"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-30db46f5-7fff-15a1-e17c-1234567890ab"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p></b><br class="Apple-interchange-newline">
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<paragraph>Hello world</paragraph>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p dir="ltr" id="docs-internal-guid-b205dcb1-7fff-2468-bad5-1234567890ab" style="line-height:1.38;margin-bottom:0pt;margin-top:0pt"><span style="background-color:transparent;color:#000000;font-family:Arial;font-size:11pt;font-style:normal;font-variant:normal;font-weight:400;text-decoration:none;vertical-align:baseline;white-space:pre-wrap">Hello world</span></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Hello world</span></p><br class="Apple-interchange-newline">
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"><style type="text/css">ol{margin:0;padding:0}table td,table th{padding:0}.c1{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Arial";font-style:normal}.c0{padding-top:0pt;padding-bottom:0pt;line-height:1.15;orphans:2;widows:2;text-align:left}.c2{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.title{padding-top:0pt;color:#000000;font-size:26pt;padding-bottom:3pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:0pt;color:#666666;font-size:15pt;padding-bottom:16pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Arial"}p{margin:0;color:#000000;font-size:11pt;font-family:"Arial"}h1{padding-top:20pt;color:#000000;font-size:20pt;padding-bottom:6pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-size:16pt;padding-bottom:6pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:16pt;color:#434343;font-size:14pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:14pt;color:#666666;font-size:12pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:12pt;color:#666666;font-size:11pt;padding-bottom:4pt;font-family:"Arial";line-height:1.15;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}</style></head><body class="c2"><p class="c0"><span class="c1">Hello world</span></p></body></html>
8 changes: 6 additions & 2 deletions tests/_utils/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ import { fixtures as image, browserFixtures as imageBrowser } from '../_data/ima
import { fixtures as link, browserFixtures as linkBrowser } from '../_data/link/index.js';
import { fixtures as list, browserFixtures as listBrowser } from '../_data/list/index.js';
import { fixtures as spacing, browserFixtures as spacingBrowser } from '../_data/spacing/index.js';
import { fixtures as googleDocsBoldWrapper, browserFixtures as googleDocsBoldWrapperBrowser }
from '../_data/paste-from-google-docs/bold-wrapper/index';

// Generic fixtures.
export const fixtures = {
'basic-styles': basicStyles,
image,
link,
list,
spacing
spacing,
'google-docs-bold-wrapper': googleDocsBoldWrapper
};

// Browser specific fixtures.
Expand All @@ -25,5 +28,6 @@ export const browserFixtures = {
image: imageBrowser,
link: linkBrowser,
list: listBrowser,
spacing: spacingBrowser
spacing: spacingBrowser,
'google-docs-bold-wrapper': googleDocsBoldWrapperBrowser
};
Loading

0 comments on commit 8102de3

Please sign in to comment.