Przeglądaj źródła

Use RTF data to replace images local paths with their base64 representation when pasting from Word.

Krzysztof Krztoń 7 lat temu
rodzic
commit
88e74c9bfe

+ 97 - 0
packages/ckeditor5-paste-from-office/src/filters/image.js

@@ -0,0 +1,97 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module paste-from-office/filters/image
+ */
+
+import Matcher from '@ckeditor/ckeditor5-engine/src/view/matcher';
+import Range from '@ckeditor/ckeditor5-engine/src/view/range';
+import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
+
+import { convertHexToBase64 } from './utils';
+
+export function transformImages( documentFragment, dataTransfer ) {
+	if ( !documentFragment.childCount ) {
+		return;
+	}
+
+	const imageElements = findAllImageElements( documentFragment );
+
+	if ( imageElements.length ) {
+		const upcastWriter = new UpcastWriter();
+		const imageData = extractImageDataFromRtf( dataTransfer.getData( 'text/rtf' ) );
+
+		replaceImageSourceWithInlineData( imageElements, imageData, upcastWriter );
+	}
+}
+
+function findAllImageElements( documentFragment ) {
+	const range = Range.createIn( documentFragment );
+
+	const listItemLikeElementsMatcher = new Matcher( {
+		name: 'img'
+	} );
+
+	const imgs = [];
+
+	for ( const value of range ) {
+		if ( value.type === 'elementStart' && listItemLikeElementsMatcher.match( value.item ) ) {
+			imgs.push( value.item );
+		}
+	}
+
+	return imgs;
+}
+
+function extractImageDataFromRtf( rtfData ) {
+	if ( !rtfData ) {
+		return [];
+	}
+
+	const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
+	const regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
+	const images = rtfData.match( regexPicture );
+	const result = [];
+
+	if ( images ) {
+		for ( const image of images ) {
+			if ( regexPictureHeader.test( image ) ) {
+				let imageType = false;
+
+				if ( image.indexOf( '\\pngblip' ) !== -1 ) {
+					imageType = 'image/png';
+				} else if ( image.indexOf( '\\jpegblip' ) !== -1 ) {
+					imageType = 'image/jpeg';
+				}
+
+				if ( imageType ) {
+					result.push( {
+						hex: imageType ? image.replace( regexPictureHeader, '' ).replace( /[^\da-fA-F]/g, '' ) : null,
+						type: imageType
+					} );
+				}
+			}
+		}
+	}
+
+	return result;
+}
+
+function replaceImageSourceWithInlineData( imageElements, imagesRtfData, upcastWriter ) {
+	// Assuming there is equal amount of Images in RTF and HTML source, so we can match them accordingly to the existing order.
+	if ( imageElements.length === imagesRtfData.length ) {
+		for ( let i = 0; i < imageElements.length; i++ ) {
+			// Replace only `file` urls of images (shapes get newSrcValue with null).
+			if ( ( imageElements[ i ].getAttribute( 'src' ).indexOf( 'file://' ) === 0 ) && imagesRtfData[ i ] ) {
+				upcastWriter.setAttribute( 'src', createSrcWithBase64( imagesRtfData[ i ] ), imageElements[ i ] );
+			}
+		}
+	}
+}
+
+function createSrcWithBase64( img ) {
+	return img.type ? 'data:' + img.type + ';base64,' + convertHexToBase64( img.hex ) : null;
+}

+ 13 - 1
packages/ckeditor5-paste-from-office/src/filters/utils.js

@@ -7,7 +7,7 @@
  * @module paste-from-office/filters/utils
  */
 
-/* globals DOMParser */
+/* globals DOMParser, btoa */
 
 import DomConverter from '@ckeditor/ckeditor5-engine/src/view/domconverter';
 import { NBSP_FILLER } from '@ckeditor/ckeditor5-engine/src/view/filler';
@@ -49,6 +49,18 @@ export function parseHtml( htmlString ) {
 	};
 }
 
+/**
+ * Converts given HEX string to base64 representation.
+ *
+ * @param {String} hexString The HEX string to be converted.
+ * @returns {String} Base64 representation of a given HEX string.
+ */
+export function convertHexToBase64( hexString ) {
+	return btoa( hexString.match( /\w{2}/g ).map( char => {
+		return String.fromCharCode( parseInt( char, 16 ) );
+	} ).join( '' ) );
+}
+
 // Transforms native `Document` object into {@link module:engine/view/documentfragment~DocumentFragment}.
 //
 // @param {Document} htmlDocument Native `Document` object to be transformed.

+ 6 - 2
packages/ckeditor5-paste-from-office/src/pastefromoffice.js

@@ -12,6 +12,7 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard';
 
 import { parseHtml } from './filters/utils';
 import { transformListItemLikeElementsIntoLists } from './filters/list';
+import { transformImages } from './filters/image';
 
 /**
  * The Paste from Office plugin.
@@ -41,7 +42,7 @@ export default class PasteFromOffice extends Plugin {
 			const html = data.dataTransfer.getData( 'text/html' );
 
 			if ( isWordInput( html ) ) {
-				data.content = this._normalizeWordInput( html );
+				data.content = this._normalizeWordInput( html, data.dataTransfer );
 			}
 		}, { priority: 'high' } );
 	}
@@ -53,11 +54,14 @@ export default class PasteFromOffice extends Plugin {
 	 *
 	 * @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 ) {
+	_normalizeWordInput( input, dataTransfer ) {
 		const { body, stylesString } = parseHtml( input );
+
 		transformListItemLikeElementsIntoLists( body, stylesString );
+		transformImages( body, dataTransfer );
 
 		return body;
 	}