Oto moja propozycja (dość konkretna ;] ) rozwiązania problemu. Może najpierw wyjaśnię.
Funkcję można podzielić na 3 fazy:
1) [14-41] Wyodrębnienie poszczególnych elementów łańcucha (tekst, znaczniki), wrzucenie ich na stos
2) [43-59] Ustalenie typu każdego elementu stosu (np. rodzaj znacznika)
3) [61-101] Formatowanie każdego elementu w zależności od typu elementu
const
NL = #13#10; // Znak nowej linii, zależnie od systemu
TAB = #32#32; // Pojedyńcze wcięcie
function formatXML(s: AnsiString): AnsiString;
var
text: AnsiString;
stack: array of AnsiString;
stackt: array of ShortInt;
posa, posb: Integer;
i, j, tabs: Integer;
begin
// Pozbycie się znaków nowej linii, obcięcie białych znaków
s := Trim(StringReplace(s, NL, '', [rfReplaceAll]));
while Length(s) > 0 do
begin
posa := pos('<', s);
posb := pos('>', s);
if posa*posb = 0 then // Nieprawidłowa ilość klamer
begin
Result := Trim(s); // Zwrócenie początkowego łańcucha
Exit;
end;
if posa > 1 then // Jeżeli znacznik jest poprzedzony łańcuchem
begin
text := Copy(s, 1, posa-1);
if Trim(text) <> '' then // Jeżeli tym łańcuchem nie są same białe znaki
begin
SetLength(stack, Length(stack)+1);
stack[High(stack)] := text; // Wrzucenie łańcucha na stos
end;
end;
SetLength(stack, Length(stack)+1);
stack[High(stack)] := Copy(s, posa, posb-posa+1); // Wrzucenie znacznika na stos
Delete(s, 1, posb); // Usunięcie znacznka i poprzedzającego go tekstu z łańcucha początkowego
end;
SetLength(stackt, Length(stack));
for i := 0 to Length(stack)-1 do
begin
// Ustalamy typ danych znajdujących się na stosie:
if (stack[i][1] = '<') and (stack[i][Length(stack[i])] = '>') then // Znacznik
if stack[i][2] = '/' then // Znacznik zamykający
stackt[i] := 1
else if stack[i][Length(stack[i])-1] = '/' then // Znacznik otwierający i zamykający
stackt[i] := 2
{ tutaj musisz obsłużyc inne rodzaje znacznikow (komentarz, nagłówek) }
else
stackt[i] := 0 // Znacznik otwierający lub nierozpoznany
else
stackt[i] := 3; // Tekst lub inny, nierozpoznany typ
end;
tabs := 0; // Początkowe wcięcie, domyślnie = 0
for i := 0 to Length(stackt)-1 do
begin
// Różne działania w zależności od typu danego elementu stosu
case stackt[i] of
0 : begin // Znacznik otwierający
for j := 1 to tabs do
s := Concat(s, TAB); // Dodanie wcięcia
s := Concat(s, stack[i]); // Dodanie znacznika
if i+1 < Length(stackt) then
if (stackt[i+1] = 0) or (stackt[i+1] = 2) then // Jeżeli następny element to znacznik otwierający
begin
s := Concat(s, NL); // Dodanie znaku nowej linii
Inc(tabs); // Powiększenie wcięcia
end;
end;
1 : begin // Znacznik zamykający
if (i-1 >= 0) and (stackt[i-1] <> 3) then // Jeżeli poprzedni element jest znacznikiem
for j := 1 to tabs do
s := Concat(s, TAB); // Dodanie wcięcia
s := Concat(s, stack[i], NL); // Dodanie znacznika i znaku nowej linii
if i+1 < Length(stackt) then
if stackt[i+1] = 1 then // Jeżeli następny element jest znacznikiem zamykającym
Dec(tabs);
end;
2 : begin // Znacznik otwierający i zamykający
if (i-1 >= 0) and (stackt[i-1] <> 3) then // Jeżeli poprzedni element jest znacznikiem
for j := 1 to tabs do
s := Concat(s, TAB); // Dodanie wcięcia
s := Concat(s, stack[i], NL); // Dodanie znacznika i znaku nowej linii
if i+1 < Length(stackt) then
if stackt[i+1] = 1 then // Jeżeli następny element jest znacznikiem zamykającym
Dec(tabs); // Zmnejszenie wcięcia
end;
3 : s := Concat(s, stack[i]); // Tekst
end;
end;
Result := s;
end;
Musisz dopisać obsługę innych przypadków (komentarz, nagłówek), ale zamysł chyba jest dobry :)
BTW: Nie miałem czasu optymalizować kodu...