Back to index

lightning-sunbird  0.9+nobinonly
Classes | Public Member Functions | Static Public Member Functions | Private Types | Private Member Functions | Private Attributes
mozInlineSpellWordUtil Class Reference

This class extracts text from the DOM and builds it into a single string. More...

#include <mozInlineSpellWordUtil.h>

Collaboration diagram for mozInlineSpellWordUtil:
Collaboration graph
[legend]

List of all members.

Classes

struct  DOMTextMapping
struct  NodeOffset
struct  RealWord

Public Member Functions

 mozInlineSpellWordUtil ()
nsresult Init (nsWeakPtr aWeakEditor)
nsresult SetEnd (nsIDOMNode *aEndNode, PRInt32 aEndOffset)
nsresult SetPosition (nsIDOMNode *aNode, PRInt32 aOffset)
nsresult GetRangeForWord (nsIDOMNode *aWordNode, PRInt32 aWordOffset, nsIDOMRange **aRange)
nsresult GetNextWord (nsAString &aText, nsIDOMRange **aRange, PRBool *aSkipChecking)
nsIDOMDocumentRangeGetDocumentRange () const
nsIDocumentGetDocument () const
nsIDOMNodeGetRootNode ()

Static Public Member Functions

static void NormalizeWord (nsSubstring &aWord)

Private Types

enum  DOMMapHint { HINT_BEGIN, HINT_END }

Private Member Functions

void InvalidateWords ()
void EnsureWords ()
PRInt32 MapDOMPositionToSoftTextOffset (NodeOffset aNodeOffset)
NodeOffset MapSoftTextOffsetToDOMPosition (PRInt32 aSoftTextOffset, DOMMapHint aHint)
PRInt32 FindRealWordContaining (PRInt32 aSoftTextOffset, DOMMapHint aHint, PRBool aSearchForward)
void BuildSoftText ()
void BuildRealWords ()
void SplitDOMWord (PRInt32 aStart, PRInt32 aEnd)
nsresult MakeRange (NodeOffset aBegin, NodeOffset aEnd, nsIDOMRange **aRange)
nsresult MakeRangeForWord (const RealWord &aWord, nsIDOMRange **aRange)

Private Attributes

nsCOMPtr< nsIDOMDocumentRangemDOMDocumentRange
nsCOMPtr< nsIDocumentmDocument
nsCOMPtr< nsIDOMViewCSSmCSSView
nsIDOMNodemRootNode
NodeOffset mSoftBegin
NodeOffset mSoftEnd
nsString mSoftText
nsTArray< DOMTextMappingmSoftTextDOMMapping
nsTArray< RealWordmRealWords
PRInt32 mNextWordIndex
PRPackedBool mSoftTextValid

Detailed Description

This class extracts text from the DOM and builds it into a single string.

The string includes whitespace breaks whereever non-inline elements begin and end. This string is broken into "real words", following somewhat complex rules; for example substrings that look like URLs or email addresses are treated as single words, but otherwise many kinds of punctuation are treated as word separators. GetNextWord provides a way to iterate over these "real words".

The basic operation is:

  1. Call Init with the weak pointer to the editor that you're using.
    1. Call SetEnd to set where you want to stop spellchecking. We'll stop at the word boundary after that. If SetEnd is not called, we'll stop at the end of the document's root element.
    2. Call SetPosition to initialize the current position inside the previously given range.
    3. Call GetNextWord over and over until it returns false.

Definition at line 71 of file mozInlineSpellWordUtil.h.


Member Enumeration Documentation

Enumerator:
HINT_BEGIN 
HINT_END 

Definition at line 174 of file mozInlineSpellWordUtil.h.


Constructor & Destructor Documentation


Member Function Documentation

Definition at line 639 of file mozInlineSpellWordUtil.cpp.

{
  // This is pretty simple. We just have to walk mSoftText, tokenizing it
  // into "real words".
  // We do an outer traversal of words delimited by IsDOMWordSeparator, calling
  // SplitDOMWord on each of those DOM words
  PRInt32 wordStart = -1;
  mRealWords.Clear();
  for (PRInt32 i = 0; i < PRInt32(mSoftText.Length()); ++i) {
    if (IsDOMWordSeparator(mSoftText.CharAt(i))) {
      if (wordStart >= 0) {
        SplitDOMWord(wordStart, i);
        wordStart = -1;
      }
    } else {
      if (wordStart < 0) {
        wordStart = i;
      }
    }
  }
  if (wordStart >= 0) {
    SplitDOMWord(wordStart, mSoftText.Length());
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 554 of file mozInlineSpellWordUtil.cpp.

{
  // First we have to work backwards from mSoftStart to find a text node
  // containing a DOM word separator, a non-inline-element
  // boundary, or the hard start node. That's where we'll start building the
  // soft string from.
  nsIDOMNode* node = mSoftBegin.mNode;
  PRInt32 firstOffsetInNode = 0;
  PRInt32 checkBeforeOffset = mSoftBegin.mOffset;
  while (node) {
    if (ContainsDOMWordSeparator(node, checkBeforeOffset, &firstOffsetInNode))
      break;
    checkBeforeOffset = PR_INT32_MAX;
    if (IsBreakElement(mCSSView, node)) {
      // Since FindPrevNode follows tree *preorder*, we're about to traverse
      // up out of 'node'. Since node induces breaks (e.g., it's a block),
      // don't bother trying to look outside it, just stop now.
      break;
    }
    node = FindPrevNode(node, mRootNode);
  }

  // Now build up the string moving forward through the DOM until we reach
  // the soft end and *then* see a DOM word separator, a non-inline-element
  // boundary, or the hard end node.
  mSoftText.Truncate();
  mSoftTextDOMMapping.Clear();
  PRBool seenSoftEnd = PR_FALSE;
  // Leave this outside the loop so large heap string allocations can be reused
  // across iterations
  nsAutoString str;
  while (node) {
    if (node == mSoftEnd.mNode) {
      seenSoftEnd = PR_TRUE;
    }

    PRBool exit = PR_FALSE;
    if (IsTextNode(node)) {
      GetNodeText(node, str);
      PRInt32 lastOffsetInNode = str.Length();

      if (seenSoftEnd) {
        // check whether we can stop after this
        for (PRInt32 i = node == mSoftEnd.mNode ? mSoftEnd.mOffset : 0;
             i < PRInt32(str.Length()); ++i) {
          if (IsDOMWordSeparator(str.CharAt(i))) {
            exit = PR_TRUE;
            // stop at the first separator after the soft end point
            lastOffsetInNode = i;
            break;
          }
        }
      }
      
      if (firstOffsetInNode < lastOffsetInNode) {
        PRInt32 len = lastOffsetInNode - firstOffsetInNode;
        mSoftTextDOMMapping.AppendElement(
          DOMTextMapping(NodeOffset(node, firstOffsetInNode), mSoftText.Length(), len));
        mSoftText.Append(Substring(str, firstOffsetInNode, len));
      }
      
      firstOffsetInNode = 0;
    }

    if (exit)
      break;

    CheckLeavingBreakElementClosure closure = { mCSSView, PR_FALSE };
    node = FindNextNode(node, mRootNode, CheckLeavingBreakElement, &closure);
    if (closure.mLeftBreakElement || (node && IsBreakElement(mCSSView, node))) {
      // We left, or are entering, a break element (e.g., block). Maybe we can
      // stop now.
      if (seenSoftEnd)
        break;
      // Record the break
      mSoftText.Append(' ');
    }
  }
  
#ifdef DEBUG_SPELLCHECK
  printf("Got DOM string: %s\n", NS_ConvertUTF16toUTF8(mSoftText).get());
#endif
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 269 of file mozInlineSpellWordUtil.cpp.

Here is the call graph for this function:

Here is the caller graph for this function:

PRInt32 mozInlineSpellWordUtil::FindRealWordContaining ( PRInt32  aSoftTextOffset,
DOMMapHint  aHint,
PRBool  aSearchForward 
) [private]

Definition at line 737 of file mozInlineSpellWordUtil.cpp.

{
  NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
  if (!mSoftTextValid)
    return -1;

  // The invariant is that the range start..end includes the last word,
  // if any, such that mSoftTextOffset <= aSoftTextOffset
  PRInt32 start = 0;
  PRInt32 end = mRealWords.Length();
  while (end - start >= 2) {
    PRInt32 mid = (start + end)/2;
    const RealWord& word = mRealWords[mid];
    if (word.mSoftTextOffset > aSoftTextOffset) {
      end = mid;
    } else {
      start = mid;
    }
  }
  
  if (start >= end)
    return -1;

  // 'start' is now the last word, if any, such that
  // mSoftTextOffset <= aSoftTextOffset.
  // If we're doing HINT_END, then we may want to return the end of the
  // the previous word instead of the start of this word
  if (aHint == HINT_END && start > 0) {
    const RealWord& word = mRealWords[start - 1];
    if (word.mSoftTextOffset + word.mLength == aSoftTextOffset)
      return start - 1;
  }
  
  // We allow ourselves to return the end of this word even if we're
  // doing HINT_START. This will only happen if there is no word which this
  // point is the start of. I'm not 100% sure this is OK...
  const RealWord& word = mRealWords[start];
  PRInt32 offset = aSoftTextOffset - word.mSoftTextOffset;
  if (offset >= 0 && offset <= word.mLength)
    return start;

  if (aSearchForward) {
    if (mRealWords[0].mSoftTextOffset > aSoftTextOffset) {
      // All words have mSoftTextOffset > aSoftTextOffset
      return 0;
    }
    // 'start' is the last word such that mSoftTextOffset <= aSoftTextOffset.
    // Word start+1, if it exists, will be the first with
    // mSoftTextOffset > aSoftTextOffset.
    if (start + 1 < PRInt32(mRealWords.Length()))
      return start + 1;
  }

  return -1;
}

Here is the caller graph for this function:

Definition at line 119 of file mozInlineSpellWordUtil.h.

{ return mDocument; }

Definition at line 118 of file mozInlineSpellWordUtil.h.

{ return mDOMDocumentRange; }
nsresult mozInlineSpellWordUtil::GetNextWord ( nsAString &  aText,
nsIDOMRange **  aRange,
PRBool aSkipChecking 
)

Definition at line 337 of file mozInlineSpellWordUtil.cpp.

{
#ifdef DEBUG_SPELLCHECK
  printf("GetNextWord called; mNextWordIndex=%d\n", mNextWordIndex);
#endif

  if (mNextWordIndex < 0 ||
      mNextWordIndex >= PRInt32(mRealWords.Length())) {
    mNextWordIndex = -1;
    *aRange = nsnull;
    *aSkipChecking = PR_TRUE;
    return NS_OK;
  }
  
  const RealWord& word = mRealWords[mNextWordIndex];
  nsresult rv = MakeRangeForWord(word, aRange);
  NS_ENSURE_SUCCESS(rv, rv);
  ++mNextWordIndex;
  *aSkipChecking = !word.mCheckableWord;
  ::NormalizeWord(mSoftText, word.mSoftTextOffset, word.mLength, aText);

#ifdef DEBUG_SPELLCHECK
  printf("GetNextWord returning: %s (skip=%d)\n",
         NS_ConvertUTF16toUTF8(aText).get(), *aSkipChecking);
#endif
  
  return NS_OK;
}

Here is the call graph for this function:

Here is the caller graph for this function:

nsresult mozInlineSpellWordUtil::GetRangeForWord ( nsIDOMNode aWordNode,
PRInt32  aWordOffset,
nsIDOMRange **  aRange 
)

Definition at line 289 of file mozInlineSpellWordUtil.cpp.

{
  // Set our soft end and start
  NodeOffset pt = NodeOffset(aWordNode, aWordOffset);
  
  InvalidateWords();
  mSoftBegin = mSoftEnd = pt;
  EnsureWords();
  
  PRInt32 offset = MapDOMPositionToSoftTextOffset(pt);
  if (offset < 0)
    return MakeRange(pt, pt, aRange);
  PRInt32 wordIndex = FindRealWordContaining(offset, HINT_BEGIN, PR_FALSE);
  if (wordIndex < 0)
    return MakeRange(pt, pt, aRange);
  return MakeRangeForWord(mRealWords[wordIndex], aRange);
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 120 of file mozInlineSpellWordUtil.h.

{ return mRootNode; }

Definition at line 86 of file mozInlineSpellWordUtil.cpp.

{
  nsresult rv;

  // getting the editor can fail commonly because the editor was detached, so
  // don't assert
  nsCOMPtr<nsIEditor> editor = do_QueryReferent(aWeakEditor, &rv);
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsIDOMDocument> domDoc;
  rv = editor->GetDocument(getter_AddRefs(domDoc));
  NS_ENSURE_SUCCESS(rv, rv);

  mDocument = do_QueryInterface(domDoc, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  mDOMDocumentRange = do_QueryInterface(domDoc, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  // view
  nsCOMPtr<nsIDOMDocumentView> docView = do_QueryInterface(domDoc, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIDOMAbstractView> abstractView;
  rv = docView->GetDefaultView(getter_AddRefs(abstractView));
  NS_ENSURE_SUCCESS(rv, rv);
  mCSSView = do_QueryInterface(abstractView, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  // Find the root node for the editor. For contenteditable we'll need something
  // cleverer here.
  nsCOMPtr<nsIDOMElement> rootElt;
  rv = editor->GetRootElement(getter_AddRefs(rootElt));
  NS_ENSURE_SUCCESS(rv, rv);
  
  mRootNode = rootElt;
  NS_ASSERTION(mRootNode, "GetRootElement returned null *and* claimed to suceed!");
  return NS_OK;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 164 of file mozInlineSpellWordUtil.h.

Here is the caller graph for this function:

nsresult mozInlineSpellWordUtil::MakeRange ( NodeOffset  aBegin,
NodeOffset  aEnd,
nsIDOMRange **  aRange 
) [private]

Definition at line 372 of file mozInlineSpellWordUtil.cpp.

{
  if (! mDOMDocumentRange)
    return NS_ERROR_NOT_INITIALIZED;

  nsresult rv = mDOMDocumentRange->CreateRange(aRange);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = (*aRange)->SetStart(aBegin.mNode, aBegin.mOffset);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = (*aRange)->SetEnd(aEnd.mNode, aEnd.mOffset);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

Here is the caller graph for this function:

Definition at line 279 of file mozInlineSpellWordUtil.cpp.

{
  NodeOffset begin = MapSoftTextOffsetToDOMPosition(aWord.mSoftTextOffset, HINT_BEGIN);
  NodeOffset end = MapSoftTextOffsetToDOMPosition(aWord.EndOffset(), HINT_END);
  return MakeRange(begin, end, aRange);
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 667 of file mozInlineSpellWordUtil.cpp.

{
  if (!mSoftTextValid) {
    NS_ERROR("Soft text must be valid if we're to map into it");
    return -1;
  }
  
  for (PRInt32 i = 0; i < PRInt32(mSoftTextDOMMapping.Length()); ++i) {
    const DOMTextMapping& map = mSoftTextDOMMapping[i];
    if (map.mNodeOffset.mNode == aNodeOffset.mNode) {
      // Allow offsets at either end of the string, in particular, allow the
      // offset that's at the end of the contributed string
      PRInt32 offsetInContributedString =
        aNodeOffset.mOffset - map.mNodeOffset.mOffset;
      if (offsetInContributedString >= 0 &&
          offsetInContributedString <= map.mLength)
        return map.mSoftTextOffset + offsetInContributedString;
      return -1;
    }
  }
  return -1;
}

Here is the caller graph for this function:

Definition at line 691 of file mozInlineSpellWordUtil.cpp.

{
  NS_ASSERTION(mSoftTextValid, "Soft text must be valid if we're to map out of it");
  if (!mSoftTextValid)
    return NodeOffset(nsnull, -1);
  
  // The invariant is that the range start..end includes the last mapping,
  // if any, such that mSoftTextOffset <= aSoftTextOffset
  PRInt32 start = 0;
  PRInt32 end = mSoftTextDOMMapping.Length();
  while (end - start >= 2) {
    PRInt32 mid = (start + end)/2;
    const DOMTextMapping& map = mSoftTextDOMMapping[mid];
    if (map.mSoftTextOffset > aSoftTextOffset) {
      end = mid;
    } else {
      start = mid;
    }
  }
  
  if (start >= end)
    return NodeOffset(nsnull, -1);

  // 'start' is now the last mapping, if any, such that
  // mSoftTextOffset <= aSoftTextOffset.
  // If we're doing HINT_END, then we may want to return the end of the
  // the previous mapping instead of the start of this mapping
  if (aHint == HINT_END && start > 0) {
    const DOMTextMapping& map = mSoftTextDOMMapping[start - 1];
    if (map.mSoftTextOffset + map.mLength == aSoftTextOffset)
      return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + map.mLength);
  }
  
  // We allow ourselves to return the end of this mapping even if we're
  // doing HINT_START. This will only happen if there is no mapping which this
  // point is the start of. I'm not 100% sure this is OK...
  const DOMTextMapping& map = mSoftTextDOMMapping[start];
  PRInt32 offset = aSoftTextOffset - map.mSoftTextOffset;
  if (offset >= 0 && offset <= map.mLength)
    return NodeOffset(map.mNodeOffset.mNode, map.mNodeOffset.mOffset + offset);
    
  return NodeOffset(nsnull, -1);
}

Here is the caller graph for this function:

Definition at line 546 of file mozInlineSpellWordUtil.cpp.

{
  nsAutoString result;
  ::NormalizeWord(aWord, 0, aWord.Length(), result);
  aWord = result;
}

Here is the caller graph for this function:

nsresult mozInlineSpellWordUtil::SetEnd ( nsIDOMNode aEndNode,
PRInt32  aEndOffset 
)

Definition at line 230 of file mozInlineSpellWordUtil.cpp.

{
  NS_PRECONDITION(aEndNode, "Null end node?");

  NS_ASSERTION(mRootNode, "Not initialized");

  InvalidateWords();

  if (!IsTextNode(aEndNode)) {
    // End at the start of the first text node after aEndNode/aEndOffset.
    aEndNode = FindNextTextNode(aEndNode, aEndOffset, mRootNode);
    aEndOffset = 0;
  }
  mSoftEnd = NodeOffset(aEndNode, aEndOffset);
  return NS_OK;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 248 of file mozInlineSpellWordUtil.cpp.

{
  InvalidateWords();

  if (!IsTextNode(aNode)) {
    // Start at the start of the first text node after aNode/aOffset.
    aNode = FindNextTextNode(aNode, aOffset, mRootNode);
    aOffset = 0;
  }
  mSoftBegin = NodeOffset(aNode, aOffset);

  EnsureWords();
  
  PRInt32 textOffset = MapDOMPositionToSoftTextOffset(mSoftBegin);
  if (textOffset < 0)
    return NS_OK;
  mNextWordIndex = FindRealWordContaining(textOffset, HINT_END, PR_TRUE);
  return NS_OK;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void mozInlineSpellWordUtil::SplitDOMWord ( PRInt32  aStart,
PRInt32  aEnd 
) [private]

Definition at line 1013 of file mozInlineSpellWordUtil.cpp.

{
  WordSplitState state(this, mSoftText, aStart, aEnd - aStart);
  state.mCurCharClass = state.ClassifyCharacter(0, PR_TRUE);

  while (state.mCurCharClass != CHAR_CLASS_END_OF_INPUT) {
    state.AdvanceThroughSeparators();
    if (state.mCurCharClass == CHAR_CLASS_END_OF_INPUT)
      break;

    PRInt32 specialWordLength = state.FindSpecialWord();
    if (specialWordLength > 0) {
      mRealWords.AppendElement(
        RealWord(aStart + state.mDOMWordOffset, specialWordLength, PR_FALSE));

      // skip the special word
      state.mDOMWordOffset += specialWordLength;
      if (state.mDOMWordOffset + aStart >= aEnd)
        state.mCurCharClass = CHAR_CLASS_END_OF_INPUT;
      else
        state.mCurCharClass = state.ClassifyCharacter(state.mDOMWordOffset, PR_TRUE);
      continue;
    }

    // save the beginning of the word
    PRInt32 wordOffset = state.mDOMWordOffset;

    // find the end of the word
    state.AdvanceThroughWord();
    PRInt32 wordLen = state.mDOMWordOffset - wordOffset;
    mRealWords.AppendElement(
      RealWord(aStart + wordOffset, wordLen,
               !state.ShouldSkipWord(wordOffset, wordLen)));
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:


Member Data Documentation

Definition at line 127 of file mozInlineSpellWordUtil.h.

Definition at line 126 of file mozInlineSpellWordUtil.h.

Definition at line 125 of file mozInlineSpellWordUtil.h.

Definition at line 160 of file mozInlineSpellWordUtil.h.

Definition at line 159 of file mozInlineSpellWordUtil.h.

Definition at line 130 of file mozInlineSpellWordUtil.h.

Definition at line 131 of file mozInlineSpellWordUtil.h.

Definition at line 132 of file mozInlineSpellWordUtil.h.

Definition at line 135 of file mozInlineSpellWordUtil.h.

Definition at line 147 of file mozInlineSpellWordUtil.h.

Definition at line 162 of file mozInlineSpellWordUtil.h.


The documentation for this class was generated from the following files: