Tworzę prosty wysiwyg editor do tworzenia plików pomocy, bazujący na gotowych szablonach podobnych do tych z MSDN. Mam w nim opcję do wklejania sformatowanego kodu z kolorowaniem składni włącznie.
Pierwszy do pracy wchodzi tokenizer dzielący tekst na tokeny typu string, komentaż, keyword.
Następnie tokeny zmieniam w kod html, używając tagu SPAN z różnymi klasami by różnie pokolorować tekst. Ale, wklejając np. tabulator - browser zmienia go w spację, co nie jest mile widziane nawet w kontenerze PRE.
Więc wymyśliłem na to sposób - dodatkowy SPAN z innerText="ttt" do wklejenia trzech tabulatorów... oraz podobnie ze znakiem nowej linii.
Tekst "int main()\n{\n\t" zostanie sformatowany do postaci
<SPAN class=t>int</SPAN> main(){<SPAN>n</SPAN><SPAN>t</SPAN>
I w takiej postaci wklejam kod do browsera. Następnie pobieram kolekcję elementów SPAN z elementu PRE (gdzie wkleiłem html), i za pomocą IHTMLDOMNode podmieniam SPAN z literką 't' lub 'n' na tabulator lub znak nowej linii. Działa to wyśmienicie o ile wklejany tekst jest w rozmiarze mikro.
Problemem jest kod o rozmiarze prawie 100KB w którym jest 13k takich elementów do podmiany. Formatowanie kodu razem z wklejeniem trwa ułamek sekundy, ale naprawa SPAN'ów trwa aż 18 sekund. Zdaję sobie sprawę że każda moja zmiana jest dodawana do undo-list, co jednak nie usprawiedliwia faktu że jeden SPAN zamienia się w textNode w czasie ponad 1ms.
Wiem również że aby cofnąć wklejony kod trzeba kliknąć Undo 13k razy ;)
I teraz pytanie: Jak zrobić by proces wklejania był szybki, oraz by bufor Undo nie wzbogacił się o więcej jak np. 1, 2, 8 pozycji?
Rozwiązanie eliminujące tylko czas to załadowanie do browsera nowego dokumentu z już doklejonym kodem, jednak wtedy undo-list jest wyczyszczone.
Oto kod podmieniający specjalne tagi SPAN na tekst:
void ReplaceSpanWithTab(IHTMLElement *element)
{
// this will replace all
// <SPAN>t</SPAN> with '\t' character(s)
// <SPAN>n</SPAN> with '\n' character(s)
// <SPAN>s</SPAN> with space character(s)
WCHAR *pwszTabs;
int cch;
IHTMLElementCollection *all;
if (!element->get_all(&all) && all)
{
int count;
all->get_length(&count);
for (int a=count-1; a>=0; a--)
{
IHTMLSpanElement *span;
if (!CollectionGetItem(all, a, IID_IHTMLSpanElement, &span)) // <SPAN>
{
BSTR bstrClass = NULL;
IHTMLElement *spanelement;
if (!span->QueryInterface(IID_IHTMLElement, &spanelement))
{
// pomijaj SPAN CLASS=
if (!spanelement->get_className(&bstrClass) && bstrClass)
{
SysFreeString(bstrClass);
}
spanelement->Release();
}
IHTMLDOMNode *spanNode;
if (!bstrClass && !span->QueryInterface(IID_IHTMLDOMNode, &spanNode))
{
// SPAN bez atrybutu CLASS
IHTMLDOMNode *childNode;
if (!spanNode->get_firstChild(&childNode) && childNode)
{
IHTMLDOMTextNode *tn;
if (!childNode->QueryInterface(IID_IHTMLDOMTextNode, &tn))
{
BOOL fSuccess = FALSE;
BSTR bstrData;
if (!tn->get_data(&bstrData) && bstrData)
{
if (!wcscmp(bstrData, L"n")) // podmień SPAN na \n
{
fSuccess = !tn->put_data(L"\r\n");
}
else
{
if (*(WCHAR*)bstrData == 't') // podmień SPAN na \t
{
cch = wcslen(bstrData);
pwszTabs = new WCHAR[cch+1];
if (pwszTabs)
{
wmemset(pwszTabs, '\t', cch);
pwszTabs[cch] = 0;
fSuccess = !tn->put_data(pwszTabs);
delete pwszTabs;
}
}
else if (*(WCHAR*)bstrData == 's') // podmień SPAN na spacje
{
cch = wcslen(bstrData);
pwszTabs = new WCHAR[cch+1];
if (pwszTabs)
{
wmemset(pwszTabs, ' ', cch);
pwszTabs[cch] = 0;
fSuccess = !tn->put_data(pwszTabs);
delete pwszTabs;
}
}
}
SysFreeString(bstrData);
}
// ostatecznie usuń tagi z elementu SPAN
if (fSuccess) spanNode->replaceNode(childNode, NULL);
tn->Release();
}
childNode->Release();
}
spanNode->Release();
}
span->Release();
}
}
all->Release();
}
}