First, be aware that the HTML you receive by pasting from Word (or any other HTML source) is going to vary wildly depending on the source. Even different versions of Word will give you radically different input. If you design some code that works perfectly on content from the version of MS Word that you have, it may not work at all for a different version of MS Word.
Also, some sources will paste content that looks like HTML, but is actually garbage. When you paste HTML content into a rich text area in your browser, your browser has nothing to do with how that HTML is generated. Do not expect it to be valid by any stretch of your imagination. In addition, your browser will further munge the HTML as it is inserted into the DOM of your rich text area.
Because the potential inputs vary so much, and because the acceptable outputs are difficult to define, it is hard to design a proper filter for this sort of thing. Further, you cannot control how future versions of MS Word will handle their HTML content, so your code will be difficult to future-proof.
However, take heart! If all the world's problems were easy ones, it would be a pretty boring place. There are some potential solutions. It is possible to keep the good parts of the HTML and discard the bad parts.
It looks like your HTML-based RTE works like most HTML editors out there. Specifically, it has an iframe, and on the document inside the iframe, it has set designMode
to "on".
You'll want to trap the paste
event when it occurs in the <body>
element of the document inside that iframe. I was very specific here because I have to be: don't trap it on the iframe; don't trap it on the iframe's window; don't trap it on the iframe's document. Trap it on the <body>
element of the document inside the iframe. Very important.
var iframe = your.rich.text.editor.getIframe(), // or whatever
win = iframe.contentWindow,
doc = win.document,
body = doc.body;
// Use your favorite library to attach events. Don't actually do this
// yourself. But if you did do it yourself, this is how it would be done.
if (win.addEventListener) {
body.addEventListener('paste', handlePaste, false);
} else {
body.attachEvent("onpaste", handlePaste);
}
Notice my sample code has attached a function called handlePaste
. We'll get to that next. The paste event is funny: some browsers fire it before the paste, some browsers fire it afterwards. You'll want to normalize that, so that you are always dealing with the pasted content after the paste. To do this, use a timeout method.
function handlePaste() {
window.setTimeout(filterHTML, 50);
}
So, 50 milliseconds after a paste event, the filterHTML function will be called. This is the meat of the job: you need to filter the HTML and remove any undesireable styles or elements. You have a lot to worry about here!
I have personally seen MSWord paste in these elements:
meta
link
style
o:p
(A paragraph in a different namespace)
shapetype
shape
- Comments, like
<!-- comment -->
.
font
- And of course, the
MsoNormal
class.
The filterHTML function should remove these when appropriate. You may also wish to remove other items as you deem necessary. Here is an example filterHTML
that removes the items I have listed above.
// Your favorite JavaScript library probably has these utility functions.
// Feel free to use them. I'm including them here so this example will
// be library-agnostic.
function collectionToArray(col) {
var x, output = [];
for (x = 0; x < col.length; x += 1) {
output[x] = col[x];
}
return output;
}
// Another utility function probably covered by your favorite library.
function trimString(s) {
return s.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
function filterHTML() {
var iframe = your.rich.text.editor.getIframe(),
win = iframe.contentWindow,
doc = win.document,
invalidClass = /(?:^| )msonormal(?:$| )/gi,
cursor, nodes = [];
// This is a depth-first, pre-order search of the document's body.
// While searching, we want to remove invalid elements and comments.
// We also want to remove invalid classNames.
// We also want to remove font elements, but preserve their contents.
nodes = collectionToArray(doc.body.childNodes);
while (nodes.length) {
cursor = nodes.shift();
switch (cursor.nodeName.toLowerCase()) {
// Remove these invalid elements.
case 'meta':
case 'link':
case 'style':
case 'o:p':
case 'shapetype':
case 'shape':
case '#comment':
cursor.parentNode.removeChild(cursor);
break;
// Remove font elements but preserve their contents.
case 'font':
// Make sure we scan these child nodes too!
nodes.unshift.apply(
nodes,
collectionToArray(cursor.childNodes)
);
while (cursor.lastChild) {
if (cursor.nextSibling) {
cursor.parentNode.insertBefore(
cursor.lastChild,
cursor.nextSibling
);
} else {
cursor.parentNode.appendChild(cursor.lastChild);
}
}
break;
default:
if (cursor.nodeType === 1) {
// Remove all inline styles
cursor.removeAttribute('style');
// OR: remove a specific inline style
cursor.style.fontFamily = '';
// Remove invalid class names.
invalidClass.lastIndex = 0;
if (
cursor.className &&
invalidClass.test(cursor.className)
) {
cursor.className = trimString(
cursor.className.replace(invalidClass, '')
);
if (cursor.className === '') {
cursor.removeAttribute('class');
}
}
// Also scan child nodes of this node.
nodes.unshift.apply(
nodes,
collectionToArray(cursor.childNodes)
);
}
}
}
}
You included some sample HTML that you wanted to filter, but you did not include a sample output that you would like to see. If you update your question to show what you want your sample to look like after filtering, I will try to adjust the filterHTML function to match. For the time being, please consider this function as a starting point for devising your own filters.
Note that this code makes no attempt to distinguish pasted content from content that existed prior to the paste. It does not need to do this; the things that it removes are considered invalid wherever they appear.
An alternative solution would be to filter these styles and contents using regular expressions against the innerHTML
of the document's body. I have gone this route, and I advise against it in favor of the solution I present here. The HTML that you will receive by pasting will vary so much that regex-based parsing will quickly run into serious issues.
Edit:
I think I see now: you are trying to remove the inline style attributes themselves, right? If that is so, you can do this during the filterHTML function by including this line:
cursor.removeAttribute('style');
Or, you can target specific inline styles for removal like so:
cursor.style.fontFamily = '';
I've updated the filterHTML function to show where these lines would go.
Good luck and happy coding!