/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.model.richtext; import org.waveprotocol.wave.model.document.util.ElementStyleView; /** * Firefox specific implementation of the RichTextTokenizer. This is necessary * to remove whitespace and newlines from elements. Also, when pasting into * an element that does not have focus, it is necessary to explicitly call * focus on the element rather than just moving the selection. * * TODO(user): Rather than traversing the DOM prior to processing, * we can override processTextNode from the parent and do the modification then. * Only reason I haven't done this yet is because of the leftSibling logic. * */ public class RichTextTokenizerImplFirefox<N, E extends N, T extends N> extends RichTextTokenizerImpl<N, E, T> { public RichTextTokenizerImplFirefox(ElementStyleView<N, E, T> doc) { super(doc); } /** * When pasting HTML from and to FF, the extra whitespace and newlines * are being kept around, causing subsequent edits to leave to catastrophic * DOM mutations (such as deleting entire paragraph blocks when deleting * DOM nodes). I am not really sure why that is happening, but this * code is trying to prevent it. Ideally there will be a better solution * down the road. */ @Override protected void processTextNodeInner(T textNode, N leftSibling) { // Keep the first space in a sequence of spaces. This is quite lame and we // shouldn't have to do this, but at least it's somewhat consistent with // other browsers. String data = document.getData(textNode).replace('\n', ' '); // Special logic: If this text node borders an Element, we can allow trailing // whitespace (e.g., "Click <a>here</a>"). N rightSibling = document.getNextSibling(textNode); data = trimWhitespace(data, leftSibling == null || document.asText(leftSibling) != null, rightSibling == null || document.asText(rightSibling) != null); boolean remove = false; if (!data.isEmpty()) { StringBuilder b = new StringBuilder(data.length()); boolean wasSpace = false; for (int i = 0; i < data.length(); ++i) { char ch = data.charAt(i); if (ch == ' ') { if (wasSpace) { continue; } wasSpace = true; } else { wasSpace = false; } b.append(ch); } data = b.toString(); } // If this is an empty or single space text node, just remove it. if (data.isEmpty() || (data.length() == 1 && data.charAt(0) == ' ')) { // Ignored. } else { addTextToken(data); } } /** * Implementation of trim that handles either leading or trailing separately. * * @param str the string to trim. * @param trimLeading True if the leading spaces should be trimmed. * @param trimTrailing True if the trailing spaces should be trimmed. */ private static String trimWhitespace(String str, boolean trimLeading, boolean trimTrailing) { if (str.isEmpty()) { return str; } int first = 0; if (trimLeading) { for (; first < str.length(); ++first) { if (str.charAt(first) != ' ') { break; } } } int last = str.length(); if (trimTrailing) { for (last = last - 1; last >= first; --last) { if (str.charAt(last) != ' ') { last = last + 1; break; } } } if (first >= last) { return ""; } return str.substring(first, last); } }