gecko-dev/embedding/components/find/src/nsFind.cpp

1078 lines
30 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the guts of the find algorithm.
*
* The Initial Developer of the Original Code is Akkana Peck.
*
* Portions created by the Initial Developer are Copyright (C) 2___
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Akkana Peck <akkana@netscape.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
//#define DEBUG_FIND 1
#include "nsFind.h"
#include "nsContentCID.h"
#include "nsIEnumerator.h"
#include "nsITextContent.h"
#include "nsIDOMNode.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocumentRange.h"
#include "nsIDOMDocumentTraversal.h"
#include "nsISelection.h"
#include "nsISelectionController.h"
#include "nsIPresShell.h"
#include "nsTextFragment.h"
#include "nsString.h"
#include "nsIAtom.h"
#include "nsParserCIID.h"
#include "nsIServiceManagerUtils.h"
#include "nsUnicharUtils.h"
#include "nsIDOMElement.h"
#include "nsIDOMHTMLElement.h"
#include "nsIDOMHTMLDocument.h"
static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID);
// Sure would be nice if we could just get these from somewhere else!
PRInt32 nsFind::sInstanceCount = 0;
nsIAtom* nsFind::sScriptAtom = nsnull;
NS_IMPL_ISUPPORTS1(nsFind, nsIFind)
nsFind::nsFind()
: mFindBackward(PR_FALSE)
, mCaseSensitive(PR_FALSE)
, mWrapFind(PR_FALSE)
, mEntireWord(PR_FALSE)
, mWrappedOnce(PR_FALSE)
, mIterOffset(0)
, mNodeQ(0)
, mLengthInQ(0)
{
NS_INIT_ISUPPORTS();
// Initialize the atoms if they aren't already:
if (sInstanceCount <= 0)
{
sScriptAtom = NS_NewAtom("script");
}
++sInstanceCount;
}
nsFind::~nsFind()
{
ClearQ();
if (sInstanceCount <= 1)
NS_IF_RELEASE(sScriptAtom);
--sInstanceCount;
}
NS_IMETHODIMP
nsFind::SetDomDoc(nsIDOMDocument* aDomDoc, nsIPresShell* aPresShell)
{
#ifdef DEBUG_FIND
printf("Setting dom document ...\n");
#endif
mDoc = aDomDoc;
mSelCon = do_QueryInterface(aPresShell);
mRange = nsnull;
mIterator = nsnull;
SetRangeAroundDocument();
return NS_OK;
}
// Adapted from nsTextServicesDocument::GetDocumentContentRootNode
nsresult
nsFind::GetRootNode(nsIDOMNode **aNode)
{
nsresult rv;
NS_ENSURE_ARG_POINTER(aNode);
*aNode = 0;
if (!mDoc)
return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDoc);
if (htmlDoc)
{
// For HTML documents, the content root node is the body.
nsCOMPtr<nsIDOMHTMLElement> bodyElement;
rv = htmlDoc->GetBody(getter_AddRefs(bodyElement));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(bodyElement);
return bodyElement->QueryInterface(NS_GET_IID(nsIDOMNode),
(void **)aNode);
}
// For non-HTML documents, the content root node will be the doc element.
nsCOMPtr<nsIDOMElement> docElement;
rv = mDoc->GetDocumentElement(getter_AddRefs(docElement));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(docElement);
return docElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void **)aNode);
}
void nsFind::SetRangeAroundDocument()
{
#ifdef DEBUG_FIND
printf("nsFind::SetRangeAroundDocument()\n");
#endif
// Zero everything in case we have problems:
mIterOffset = 0;
mIterNode = nsnull;
mWrappedOnce = PR_FALSE;
mRange = nsnull;
nsCOMPtr<nsIDOMNode> bodyNode;
nsresult rv = GetRootNode(getter_AddRefs(bodyNode));
nsCOMPtr<nsIContent> bodyContent (do_QueryInterface(bodyNode));
#ifdef DEBUG_FIND
if (!bodyContent)
printf("Document has no root content\n");
#endif
if (NS_FAILED(rv) || !bodyContent)
return;
PRInt32 childCount;
rv = bodyContent->ChildCount(childCount);
if (NS_FAILED(rv)) return;
#ifdef DEBUG_FIND
printf("Body has %d children\n", childCount);
#endif
if (!mRange)
mRange = do_CreateInstance(kRangeCID);
mRange->SetStart(bodyNode, 0);
mRange->SetEnd(bodyNode, childCount);
}
// Set the range to go from the end of the current selection to the
// end of the document (forward), or beginning to beginning (reverse).
// or around the whole document if there's no selection.
nsresult nsFind::SetRangeToSelection()
{
#ifdef DEBUG_FIND
printf("nsFind::SetRangeToSelection()\n");
#endif
if (!mSelCon) return NS_ERROR_NOT_INITIALIZED;
if (!mRange)
mRange = do_CreateInstance(kRangeCID);
nsCOMPtr<nsISelection> selection;
mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(selection));
if (!selection) return NS_ERROR_BASE;
// There is a selection.
PRInt32 count = -1;
nsresult rv = selection->GetRangeCount(&count);
if (count < 1) return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDOMNode> node;
if (mFindBackward)
{
nsCOMPtr<nsIDOMRange> range;
// This isn't quite right, since the selection's ranges aren't
// necessarily in order; but hopefully they usually will be.
selection->GetRangeAt(count-1, getter_AddRefs(range));
range->GetStartContainer(getter_AddRefs(node));
if (!node) return NS_ERROR_UNEXPECTED;
PRInt32 offset;
range->GetStartOffset(&offset);
// Need bodyNode, for the start of the document
nsCOMPtr<nsIDOMNode> bodyNode;
rv = GetRootNode(getter_AddRefs(bodyNode));
NS_ENSURE_SUCCESS(rv, rv);
// Set mRange to go from the beginning of the doc to this node
mRange->SetStart(bodyNode, 0);
mRange->SetEnd(node, offset);
}
else // forward
{
nsCOMPtr<nsIDOMRange> range;
selection->GetRangeAt(0, getter_AddRefs(range));
range->GetEndContainer(getter_AddRefs(node));
if (!node) return NS_ERROR_UNEXPECTED;
PRInt32 offset;
range->GetEndOffset(&offset);
// Need bodyNode, for the start of the document
nsCOMPtr<nsIDOMNode> bodyNode;
rv = GetRootNode(getter_AddRefs(bodyNode));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> bodyContent (do_QueryInterface(bodyNode));
NS_ENSURE_ARG_POINTER(bodyContent);
PRInt32 childCount;
rv = bodyContent->ChildCount(childCount);
// Set mRange to go from end node to end of doc
mRange->SetStart(node, offset);
mRange->SetEnd(bodyNode, childCount);
}
return NS_OK;
}
nsresult nsFind::InitIterator()
{
nsresult rv;
if (!mIterator)
{
rv = nsComponentManager::CreateInstance(kCContentIteratorCID,
nsnull,
NS_GET_IID(nsIContentIterator),
getter_AddRefs(mIterator));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG_POINTER(mIterator);
}
// Initialize the range if it isn't already
if (!mRange)
{
rv = SetRangeToSelection();
if (NS_FAILED(rv))
SetRangeAroundDocument();
}
mIterator->Init(mRange);
if (mFindBackward) {
// Use post-order in the reverse case,
// so we get parents before children,
// in case we want to precent descending into a node.
mIterator->MakePost();
mIterator->Last();
} else {
// Use pre-order in the forward case.
// Pre order is currently broken (will skip nodes!), so don't use it.
//mIterator->MakePre();
mIterator->First();
}
return NS_OK;
}
nsresult
nsFind::SetRange(nsIDOMRange* aRange)
{
mRange = aRange;
mIterOffset = 0;
mIterNode = nsnull;
mWrappedOnce = PR_FALSE;
return NS_OK;
}
/* attribute boolean findBackward; */
NS_IMETHODIMP
nsFind::GetFindBackwards(PRBool *aFindBackward)
{
if (!aFindBackward)
return NS_ERROR_NULL_POINTER;
*aFindBackward = mFindBackward;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetFindBackwards(PRBool aFindBackward)
{
// If we're changing directions, reset wrapping as well:
if (aFindBackward != mFindBackward)
mWrappedOnce = PR_FALSE;
mFindBackward = aFindBackward;
return NS_OK;
}
/* attribute boolean caseSensitive; */
NS_IMETHODIMP
nsFind::GetCaseSensitive(PRBool *aCaseSensitive)
{
if (!aCaseSensitive)
return NS_ERROR_NULL_POINTER;
*aCaseSensitive = mCaseSensitive;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetCaseSensitive(PRBool aCaseSensitive)
{
mCaseSensitive = aCaseSensitive;
return NS_OK;
}
/* attribute boolean wrapFind; */
NS_IMETHODIMP
nsFind::GetWrapFind(PRBool *aWrapFind)
{
if (!aWrapFind)
return NS_ERROR_NULL_POINTER;
*aWrapFind = mWrapFind;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetWrapFind(PRBool aWrapFind)
{
mWrapFind = aWrapFind;
return NS_OK;
}
/* attribute boolean entireWord; */
NS_IMETHODIMP
nsFind::GetEntireWord(PRBool *aEntireWord)
{
if (!aEntireWord)
return NS_ERROR_NULL_POINTER;
*aEntireWord = mEntireWord;
return NS_OK;
}
NS_IMETHODIMP
nsFind::SetEntireWord(PRBool aEntireWord)
{
mEntireWord = aEntireWord;
return NS_OK;
}
/* readonly attribute boolean replaceEnabled; */
NS_IMETHODIMP
nsFind::GetReplaceEnabled(PRBool *aReplaceEnabled)
{
if (!aReplaceEnabled)
return NS_ERROR_NULL_POINTER;
*aReplaceEnabled = PR_FALSE;
nsresult result = NS_OK;
return result;
}
#ifdef DEBUG_FIND
static void DumpNode(nsIDOMNode* aNode)
{
if (!aNode)
{
printf(">>>> Node: NULL\n");
return;
}
nsAutoString nodeName;
aNode->GetNodeName(nodeName);
nsCOMPtr<nsITextContent> textContent (do_QueryInterface(aNode));
if (textContent)
{
nsAutoString newText;
textContent->CopyText(newText);
printf(">>>> Text node: '%s'\n",
NS_LossyConvertUCS2toASCII(newText).get());
}
else
printf(">>>> Node: %s\n", NS_LossyConvertUCS2toASCII(nodeName).get());
}
#endif
//
// Here begins the find code.
// A ten-thousand-foot view of how it works:
// Find needs to be able to compare across inline (but not block) nodes,
// e.g. find for "abc" should match a<b>b</b>c.
// So after we've searched a node, we're not done with it;
// in the case of a partial match we may need to go back and
// look at the node's string again, so we save the last few
// nodes visited in a deque (a double-ended queue).
// When we're comparing, we look through the queue first, then
// start pulling new nodes from the tree, queueing them as necessary.
//
// Text nodes store their text in an nsTextFragment, which is
// effectively a union of a one-byte string or a two-byte string.
// Single and double strings are intermixed in the dom.
// We don't have string classes which can deal with intermixed strings,
// so all the handling is done explicitly here.
//
// Ongoing questions:
// 1. We remember the node and offset from the last find.
// This is wrong, and will break when we replace. We should either:
// a) Remember a range (and let range gravity manage it);
// b) Always search forward or backward from the current selection.
// A poll on IRC seemed to indicate it was okay to use the selection.
//
// 2. Rather than having NextNode know how to wrap, it might be better
// if FindInQ only searched once, then we could handle wrapping here.
// Arguable.
//
// 3. Some people want to be able to search in text controls,
// not just text nodes. It should be possible.
// This would be especially useful for webmail users
// (e.g. if the caret is in a text control, search only there).
//
/* boolean Find (in wstring aFindText); */
NS_IMETHODIMP
nsFind::Find(const PRUnichar *aPatText, PRBool *aDidFind)
{
#ifdef DEBUG_FIND
printf("============== nsFind::Find(%s)\n",
NS_LossyConvertUCS2toASCII(aPatText).get());
#endif
if (!aPatText || !aDidFind)
return NS_ERROR_NULL_POINTER;
*aDidFind = PR_FALSE;
if (!mDoc)
return NS_ERROR_NOT_INITIALIZED;
nsAutoString patStr(aPatText);
if (!mCaseSensitive)
ToLowerCase(patStr);
aPatText = patStr.get();
PRInt32 patLen = patStr.Length();
nsCOMPtr<nsIDOMRange> range;
PRBool found = FindInQ(aPatText, patLen-1, getter_AddRefs(range));
if (found)
{
#ifdef DEBUG_FIND
printf("Matched!\n");
#endif
*aDidFind = PR_TRUE;
// Set the selection:
// XXX The intent for the future is that nsFind will not set selection;
// instead, it will return a range, and nsWebBrowserFind will be a
// wrapper that sets the selection and scrolls.
SetSelectionAndScroll(range);
}
// Clear queue and range; we want to be stateless.
// Caller should reset our range before calling us again
// (or else we'll reset it according to the selection).
ClearQ();
mRange = 0;
mIterator = 0;
mLastBlockParent = 0;
return NS_OK;
}
// Add this node to the deque, popping the top node off if needed.
void
nsFind::AddIterNode(PRInt32 aPatLen)
{
#ifdef DEBUG_FIND
printf("AddIterNode\n");
#endif
nsCOMPtr<nsITextContent> textContent (do_QueryInterface(mIterNode));
if (!textContent) return;
// If it's only whitespace, don't waste our time.
// This is probably worthwhile even though it has to loop over the string,
// since it may save us several loops in future iterations.
PRBool onlyWhite = PR_FALSE;
nsresult rv = textContent->IsOnlyWhitespace(&onlyWhite);
if (onlyWhite) return;
PRInt32 newLen;
rv = textContent->GetTextLength(&newLen);
if (NS_FAILED(rv)) return;
// Now look through the queue and see if we can pop the oldest node off:
// We know mLengthInQ and aPatLen, can get length of the oldest item:
while (mNodeQ.GetSize() > 0)
{
nsITextContent* oldestItem =
mFindBackward ? NS_STATIC_CAST(nsITextContent*, mNodeQ.PeekFront())
: NS_STATIC_CAST(nsITextContent*, mNodeQ.Peek());
NS_ASSERTION(oldestItem, "Queue nonzero but can't peek!");
if (!oldestItem)
break;
PRInt32 oldestLength;
rv = oldestItem->GetTextLength(&oldestLength);
if (NS_SUCCEEDED(rv)) // If we can't get text length, pop it anyway
{
if (mLengthInQ - oldestLength - mIterOffset < aPatLen)
break;
// still long enough, we're going to pop one:
mLengthInQ -= oldestLength;
}
// Pop-and-release whether or not it had a length:
nsITextContent* tc = (mFindBackward
? NS_STATIC_CAST(nsITextContent*, mNodeQ.Pop())
: NS_STATIC_CAST(nsITextContent*, mNodeQ.PopFront())
);
NS_IF_RELEASE(tc);
// If we popped, then mIterOffset no longer matters:
mIterOffset = 0;
#ifdef DEBUG_FIND
printf("Popping something of length %d, queue now %d in %d nodes\n",
oldestLength, mLengthInQ, mNodeQ.GetSize());
#endif
}
// Push the new node onto the queue:
// nsDeque doesn't addref, so we have to do it explicitly:
mLengthInQ += newLen;
#ifdef DEBUG_FIND
printf("Adding %d chars to make %d\n", newLen, mLengthInQ);
#endif
// Windows compiler won't let us addref the results of a nsCOMPtr::get
nsITextContent* rawtxt = textContent;
NS_ADDREF(rawtxt);
if (mFindBackward)
mNodeQ.PushFront(rawtxt);
else
mNodeQ.Push(rawtxt);
}
// Unfortunately, we can't use Erase(), because that doesn't release
// the nodes in the queue. So we have to pop and release each one.
void nsFind::ClearQ()
{
#ifdef DEBUG_FIND
printf("Clearing Deque\n");
#endif
nsITextContent* tc;
while ((tc = NS_STATIC_CAST(nsITextContent*, mNodeQ.PopFront())))
NS_RELEASE(tc);
mLengthInQ = 0;
// If we have no queue, then mIterOffset is no longer useful:
mIterOffset = 0;
}
void
nsFind::SetSelectionAndScroll(nsIDOMRange* aRange)
{
// foundStrLen is the true # chars in the text nodes in the dom tree,
// so we can use it when searching forward/back.
nsCOMPtr<nsISelection> selection;
if (!mSelCon) return;
mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(selection));
if (!selection) return;
selection->RemoveAllRanges();
selection->AddRange(aRange);
// Scroll if necessary to make the selection visible:
mSelCon->ScrollSelectionIntoView
(nsISelectionController::SELECTION_NORMAL,
nsISelectionController::SELECTION_FOCUS_REGION);
}
nsresult
nsFind::NextNode()
{
nsresult rv;
nsCOMPtr<nsIContent> content;
nsCOMPtr<nsITextContent> tc;
if (!mIterator)
{
rv = InitIterator();
NS_ENSURE_SUCCESS(rv, rv);
rv = mIterator->CurrentNode(getter_AddRefs(content));
#ifdef DEBUG_FIND
nsCOMPtr<nsIDOMNode> dnode (do_QueryInterface(content));
printf(":::::::::::::::::::::::::: Got the first node "); DumpNode(dnode);
#endif
tc = do_QueryInterface(content);
if (tc)
{
mIterNode = do_QueryInterface(content);
return NS_OK;
}
}
while (1)
{
if (mFindBackward)
rv = mIterator->Prev();
else
rv = mIterator->Next();
if (NS_FAILED(rv)) break;
rv = mIterator->CurrentNode(getter_AddRefs(content));
#ifdef DEBUG_FIND
nsCOMPtr<nsIDOMNode> dnode (do_QueryInterface(content));
printf(":::::::::::::::::::::::::: Got another node "); DumpNode(dnode);
#endif
// nsIContentIterator.h says Next() will return error at end,
// but it doesn't really, so we have to check:
if (NS_FAILED(rv) || !content)
break;
tc = do_QueryInterface(content);
if (tc)
break;
#ifdef DEBUG_FIND
dnode = do_QueryInterface(content);
printf("Not a text node: "); DumpNode(dnode);
#endif
// If we ever cross a block node, flush the queue --
// we don't match patterns extending across block boundaries.
//if (IsBlockNode(content))
// ClearQ();
// Now see if we need to skip this node --
// e.g. is it part of a script or other invisible node?
// Note that we don't ask for CSS information;
// a node can be invisible due to CSS, and we'd still find it.
while (SkipNode(content))
{
nsCOMPtr<nsIDOMNode> node (do_QueryInterface(content));
if (node)
{
nsCOMPtr<nsIDOMNode> sib;
if (mFindBackward)
node->GetPreviousSibling(getter_AddRefs(sib));
else
node->GetNextSibling(getter_AddRefs(sib));
content = do_QueryInterface(sib);
if (content)
mIterator->PositionAt(content);
else {
// Can a nsIDOMNode ever not be an nsIContent?
// If so, should we loop until we find one?
NS_ASSERTION(content, "Find: Node is not content\n");
}
}
}
}
if (content)
mIterNode = do_QueryInterface(content);
else
mIterNode = nsnull;
// See if we need to wrap around
if (!mIterNode && mWrapFind && !mWrappedOnce)
{
#ifdef DEBUG_FIND
printf("Wrapping ...\n");
#endif
mWrappedOnce = 1;
mIterator = nsnull;
return NextNode(); // tail recurse
}
#ifdef DEBUG_FIND
printf("Iterator gave: "); DumpNode(mIterNode);
#endif
return NS_OK;
}
PRBool nsFind::IsBlockNode(nsIContent* aContent)
{
nsCOMPtr<nsIAtom> atom;
aContent->GetTag(*getter_AddRefs(atom));
if (!mParserService) {
nsresult rv;
mParserService = do_GetService(kParserServiceCID, &rv);
if (NS_FAILED(rv) || !mParserService) return PR_FALSE;
}
PRInt32 id;
mParserService->HTMLAtomTagToId(atom, &id);
PRBool isBlock = PR_FALSE;
mParserService->IsBlock(id, isBlock);
return isBlock;
}
PRBool nsFind::IsTextNode(nsIDOMNode* aNode)
{
nsCOMPtr<nsITextContent> content (do_QueryInterface(aNode));
if (content) return PR_TRUE;
else return PR_FALSE;
}
PRBool nsFind::SkipNode(nsIContent* aContent)
{
// Skipping depends on the iterator using the right order.
// Currently, post order (used by reverse find) works,
// but pre order has problems. So turn off skipping for forward find.
if (!mFindBackward)
return PR_FALSE;
nsCOMPtr<nsIAtom> atom;
aContent->GetTag(*getter_AddRefs(atom));
if (!atom)
return PR_TRUE;
nsIAtom *atomPtr = atom.get();
return (sScriptAtom == atomPtr);
}
nsresult nsFind::GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent)
{
while (aNode)
{
nsCOMPtr<nsIDOMNode> parent;
nsresult rv = aNode->GetParentNode(getter_AddRefs(parent));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> content (do_QueryInterface(parent));
if (content && IsBlockNode(content))
{
*aParent = parent;
NS_ADDREF(*aParent);
return NS_OK;
}
aNode = parent;
}
return NS_ERROR_FAILURE;
}
#define NBSP_CHARCODE ((PRUnichar)160)
#define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE)
//
// FindInQ:
// If we have nodes in the queue, search them first.
// Then take nodes out of the tree with NextNode,
// until null (NextNode will return 0 at the end of our range).
// Save intermediates in the deque.
//
PRBool nsFind::FindInQ(const PRUnichar* aPatStr, PRInt32 aPatLen,
nsIDOMRange** aRangeRet)
{
#ifdef DEBUG_FIND
printf("FindInQ for '%s'%s\n",
NS_LossyConvertUCS2toASCII(aPatStr).get(),
mFindBackward ? " (backward)" : "");
#endif
// The offset only matters for the first node in the queue,
// so make a local copy so that we can zero it when we loop:
PRInt32 offset = mIterOffset;
// current offset into the pattern -- reset to beginning/end:
PRInt32 pindex = (mFindBackward ? aPatLen : 0);
// Current offset into the fragment
PRInt32 findex = 0;
// Place in the fragment to re-start the match:
PRInt32 restart = 0;
// Direction to move pindex and ptr*
int incr = (mFindBackward ? -1 : 1);
// Loop over nodes in our queue:
nsDequeIterator iter (mFindBackward ? mNodeQ.End() : mNodeQ.Begin());
nsDequeIterator iterEnd (mFindBackward ? mNodeQ.Begin() : mNodeQ.End());
nsITextContent* tc;
const nsTextFragment *frag = nsnull;
PRBool fromQ = PR_TRUE;
// Pointers into the current fragment:
const PRUnichar *t2b = nsnull;
const char *t1b = nsnull;
// Keep track of when we're in whitespace:
// (only matters when we're matching)
PRBool inWhitespace = PR_FALSE;
// Place to save the range start point in case we find a match:
nsITextContent* matchAnchorTC = nsnull;
PRInt32 matchAnchorOffset = 0;
//while ((tc = NS_STATIC_CAST(nsITextContent*, iter.GetCurrent())) != nsnull)
while (1)
{
#ifdef DEBUG_FIND
printf("Loop ...\n");
#endif
// If this is our first time on a new node, reset the pointers:
if (!frag)
{
#ifdef DEBUG_FIND
printf("New node -- Resetting pointers. Deque size is %d\n",
mNodeQ.GetSize());
#endif
// Get the current node, either from the deque or the tree.
if (fromQ && (iter != iterEnd))
tc = NS_STATIC_CAST(nsITextContent*, iter.GetCurrent());
else
tc = nsnull;
if (tc == nsnull) // no more deque
{
#ifdef DEBUG_FIND
printf("No more deque, looking in tree\n");
#endif
fromQ = PR_FALSE;
NextNode();
if (!mIterNode) // Out of nodes
{
// Reset the iterator, so this nsFind will be usable if
// the user wants to search again (from beginning/end).
mIterator = nsnull;
return PR_FALSE;
}
// We have a new text content. If its block parent is different
// from the block parent of the last text content, then we
// need to clear the queue since we don't want to find
// across block boundaries.
nsCOMPtr<nsIDOMNode> blockParent;
GetBlockParent(mIterNode, getter_AddRefs(blockParent));
if (blockParent && (blockParent != mLastBlockParent))
{
ClearQ();
mLastBlockParent = blockParent;
}
// Push mIterNode onto the queue:
mIterNode->QueryInterface(nsITextContent::GetIID(), (void**)&tc);
AddIterNode(aPatLen);
}
if (tc == nsnull) // Out of nodes
return PR_FALSE;
#ifdef DEBUG_FIND
else printf("Got something from the queue\n");
#endif
nsresult rv = tc->GetText(&frag);
if (NS_FAILED(rv)) continue;
if (offset > 0)
restart = offset;
else if (mFindBackward)
restart = frag->GetLength()-1;
else
restart = 0;
findex = restart;
#ifdef DEBUG_FIND
printf("Starting from offset %d\n", findex);
#endif
if (frag->Is2b())
{
t2b = frag->Get2b();
t1b = nsnull;
#ifdef DEBUG_FIND
printf("2 byte, '%s'\n", NS_LossyConvertUCS2toASCII(t2b).get());
#endif
}
else
{
t1b = frag->Get1b();
t2b = nsnull;
#ifdef DEBUG_FIND
// t1b isn't null terminated, so we may print garbage after the string.
// If we want to be accurate, would be better to make a new nsCString.
printf("1 byte, '%s'\n", t1b);
#endif
}
}
else // still on the old node
{
// Still on the old node. Advance the pointers,
// then see if we need to pull a new node from the queue.
findex += incr;
#ifdef DEBUG_FIND
printf("Same node -- (%d, %d)\n", pindex, findex);
#endif
if (mFindBackward ? (findex < 0) : (findex >= frag->GetLength()))
{
#ifdef DEBUG_FIND
printf("Pulling a new node: restart=%d, frag len=%d\n",
restart, frag->GetLength());
#endif
// Done with this node. Pull a new one.
// If restart is the first/last char,
// then it's safe to pop this because we aren't using the
// contents of the node any more.
mLengthInQ -= frag->GetLength();
if (mFindBackward) {
--iter;
if (restart <= 0)
mNodeQ.Pop();
} else {
++iter;
if (restart >= frag->GetLength())
mNodeQ.PopFront();
}
frag = nsnull;
// Offset can only apply to the first node:
offset = -1;
continue;
}
}
// The two characters we'll be comparing:
PRUnichar c = (t2b ? t2b[findex] : (PRUnichar)(t1b[findex]));
PRUnichar patc = aPatStr[pindex];
// Is it time to go back to non-whitespace mode?
if (inWhitespace)
{
if (!IsSpace(c))
inWhitespace = PR_FALSE;
}
// Have we just hit a space?
else if (IsSpace(patc))
{
inWhitespace = PR_TRUE;
// go forward to the last whitespace in the pat str
while ((mFindBackward ? pindex > 0 : pindex < aPatLen-1)
&& IsSpace(aPatStr[pindex+incr]))
pindex += incr;
}
// convert to lower case if necessary
if (!mCaseSensitive && !inWhitespace && IsUpperCase(c))
c = ToLowerCase(c);
// Compare
if ((inWhitespace && IsSpace(c)) || (patc == c))
{
// Are we done?
if (mFindBackward ? pindex <= 0 : pindex >= aPatLen)
// Matched the whole string!
{
#ifdef DEBUG_FIND
printf("Found a match!\n");
#endif
// Make the range:
nsCOMPtr<nsIDOMRange> range;
nsCOMPtr<nsIDOMDocumentRange> docr (do_QueryInterface(mDoc));
if (docr && aRangeRet)
{
docr->CreateRange(getter_AddRefs(range));
PRInt32 matchStartOffset, matchEndOffset;
if (range)
{
nsCOMPtr<nsIDOMNode> startParent;
nsCOMPtr<nsIDOMNode> endParent;
if (mFindBackward)
{
startParent = do_QueryInterface(tc);
endParent = do_QueryInterface(matchAnchorTC);
matchStartOffset = findex;
matchEndOffset = matchAnchorOffset;
}
else
{
startParent = do_QueryInterface(matchAnchorTC);
endParent = do_QueryInterface(tc);
matchStartOffset = matchAnchorOffset;
matchEndOffset = findex+1;
}
if (startParent && endParent)
{
range->SetStart(startParent, matchStartOffset);
range->SetEnd(endParent, matchEndOffset);
*aRangeRet = range.get();
NS_ADDREF(*aRangeRet);
}
}
}
// Pop all but the last node off the queue
ClearQ();
NS_ADDREF(tc);
mNodeQ.Push(tc);
mLengthInQ = frag->GetLength();
// Reset the offset to the other end of the found string:
mIterOffset = findex + (mFindBackward ? 1 : 0);
#ifdef DEBUG_FIND
printf("mIterOffset = %d, mIterNode = ", mIterOffset);
DumpNode(mIterNode);
#endif
return PR_TRUE;
}
// Not done, but still matching.
#ifdef DEBUG_FIND
printf("YES! %c == %c (%d of %d)\n", inWhitespace ? ' ' : patc, c,
pindex, aPatLen);
#endif
// Save the range anchors if we haven't already:
if (!matchAnchorTC) {
matchAnchorTC = tc;
matchAnchorOffset = findex + (mFindBackward ? 1 : 0);
}
// Advance and loop around for the next characters.
pindex += incr;
//findex += incr;
continue;
}
#ifdef DEBUG_FIND
printf("NOT: %c == %c\n", inWhitespace ? ' ' : patc, c);
#endif
// If we didn't match, go back to the beginning of patStr,
// and set findex back to the next char after
// we started the current match.
matchAnchorTC = nsnull;
pindex = (mFindBackward ? aPatLen : 0);
inWhitespace = PR_FALSE;
//findex = restart; // +incr will be added when we continue
}
// Out of nodes, and didn't match.
return PR_FALSE;
}