Wklejenie sformatowanego kodu do browsera

0

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();
	}
}
0

o ile dobrze zrozumialem, podczas wklejania, najpierw zamieniasz \t na span-t-\span zrby edytor nie zgubil tabulacji a potem to cofasz? po co..?

druga rzecz - czemu na span-t-span, a nie np. na 4x   ?

trzecia - musisz koniecznie operowac na drzewie dom ? nie mozesz wykonac podmiany poza nim, w tekscie? skoro to Twoj edytor, nie musisz przeciez robic tego przez pelny close/open dokumentu .. czy chodzi Ci ze kontrolka edycyjna nie jest Twoja, sama zajmuje sie undo/redo i w momencie hurtowej podmiany danych zapomina ona historie zmian?

0

Moj edytor działa na silniku IE, owa kontrolka dhtmledit wydawała się ciekawa więc użyłem jej zamiast samego mshtml. Undo i redo jest gdzieś dobrze zakopane w mshtml, nie mam pojęcia jak je czasowo wyłączyć

Kto chce, może odpalić poniższy kod by zrozumieć jak działa wklejanie tabów:

<pre id=pre contenteditable style="border: solid 1px black; background: #eeeeee; height:10em;white-space: pre;">wklej tu coś</pre>
<script> pre.innerText="\t\tblah\n\nblah";</script>
  1. Aktualnie muszę tak to robić, bo jakkolwiek nie dodam tekstu z tabulatorem, zostanie on (tab) podmieniony na zwykły odstęp. Możliwości jest wiele:
    IHTMLTxtRange::pasteHTML
    IHTMLTxtRange::put_text
    IHTMLElement::put_innerText
    IDHTMLEdit::ExecCommand(DECMD_PASTE)
    IOleCommandTarget::Exec(IDM_PASTE).

  2. Tabulator zajmuje mniej miejsca i szybciej się go wpisuje. Sprawa gustu, oraz prościej jest wstawić tab, niż obliczać ilość spacji względem aktualnej kolumny. Jeżeli kod do wklejenia jest wcinany spacjami, to nie wymuszę formatowania tabulatorem, przecież osoba obsługująca oczekuje tego od programu.

  3. Jak na razie tylko DOM umożliwia dodanie tekstu bez modyfikacji. Mam accelerator który wykrywa moment wklejania, wciśnięcia entera i tabulatora czyniąc odpowiednie formatowanie zależnie od tego w czym aktualnie jest usytuowany caret i jakie opcje w menu są zaznaczone. Jeżeli np. wcisnę tab w elemencie PRE, to prawdziwy tab zostanie doklejony w miejscu kursora. Podobnie z enterem - zamiast domyślnego dodania DIV lub P czy też BR, prawdziwe \r\n zostaje doklejone jeżeli w menu jest zaptaszona taka opcja.

Formatując kompletnie nowy dokument ze sformatowanym tekstem i wpisując go do edytora tracę możliwość cofnięcia wcześniejszych zmian, a powyższy sposób z nadmiarowymi SPANami powoduje ogromne napęcznienie bufora Undo.

Problem rozwiązany. Usunąłem nadmiarowe SPANy dla \t i \n, wyszukałem dwóch najwyższych nie używanych znaków w sformatowanym kodzie html i podmieniłem wszystkie \t na pierwszy, a wszystkie \n na drugi znaleziony znak. Tak zmodyfikowany html wklejam do nowego DIV który tworzę na aktualnej pozycji kursora

//IHTMLTxtRange zwracane jest z document.selection.createRange
IHTMLTxtRange::pasteHTML(L"<DIV id=to_be_replaced></DIV>");

poczym oddaję pałeczkę do rekursywnej funkcji która za pomocą domnodes wyszukuje owych dwóch specjalnych znaków w node.nodeValue i przywraca wszystkie \t i \n w DIV.childnodes.
Na koniec usuwam DIV z opcją fDeep=false. Całość trwa poniżej sekundy, co nie ma porównania z poprzednim czasem 18s.

Undo buffer rośnie o tylko jedną pozycję dzięki

IHTMLDocument2::QueryInterface(IID_IMarkupServices);
IMarkupServices::BeginUndoUnit(L"wklejenie");
// wklejenie i przywrócenie tabów
IMarkupServices::EndUndoUnit();

1 użytkowników online, w tym zalogowanych: 0, gości: 1