Formatowanie kodu XML bez użycia dodatkowych bibliotek

0

Ha! wieki w tym dziale nie pisałem :)

Potrzeba mi funkcji formatującej kod XML z postaci:

<a><b><c/><d>costam</d></b></a>

albo nawet jakiejś mieszanej typu:

<a><b>
    <c/><d>costam</d>
     </b>
</a>

do postaci:

<a>
  <b>
    <c/>
    <d>costam</d>
  </b>
</a>

czyli z poprawnymi wcięciami.
Napisałem coś na kolanie, ale przy pewnych wariantach się myli i przy długich kodach wcięcia rosną ale nie maleją. Nie podaję mojego podejścia, bo samo założenie jest złe i nie jest to dobry kod do poprawek.

Może ktoś ma coś podobnego na warsztacie i zechce się podzielić :)
Baaardzo bym chciał nie używać dodatkowych bibliotek, parserów etc do tego - jedna, niezależna funkcja string -> string.

PS
Przy okazji wyszedł wałek Coyota/GeSHi - poprawne tagi typu <b> [bold] nie są kolorowane :)</b>

0

Może zabawa ze StringList. Zrobić coś na wzór explode, jeden znacznik = jeden item, potem w pętli szukać odpowiednich par (poczynając od środka, lub licząc te same znaczniki i na ich podstawie określić który jest dopełnieniem którego). Zamieszczenie kodu rónież nie będzie złym pomysłem. Zobaczymy co juz masz, jakie błędy zrobiłeś i czego mamy unikać.

//zabrałem się od d**y strony więc nie chcę pokazywać złego podejścia - M

0

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...

0

ja bym proponował rzut oka na Jedi Code Format - darmowy, w pełni sprawny i OS dla o. pascala

0

Dzięki Patyk, będę testował jak Delphi zarejestruje, bo dziś oddałem kompa, na którym dotychczas pracowałem.

Misiekd, ale ja to potrzebuję w moim programie - a nie do formatowania zewnętrznych kodów.

0
Marooned napisał(a)

Misiekd, ale ja to potrzebuję w moim programie - a nie do formatowania zewnętrznych kodów.

ale to masz ze źródłami więc albo zobacz jak oni to robią albo po prostu zaadoptuj go pod siebie

0

Does JCF just work on Delphi code?
Yes, it is designed with only Delphi in mind.

a składnia XML "ciut" odbiega od składni Delphi

funkcja Patyka wydaje się śmigać bez zarzutu - dzięki wielkie!

0

Patyk: Może (jeśli Ci się chce ;) ) zamieść tę funkcję wraz z jakimś trochę dogłębniejszym opisem w Gotowcach albo czymś - jest to kawał dobrej roboty i szkoda, żeby się zagubił w otchłaniach forum ;)

0

Jeżeli znajdę czas to dopiszę inne przypadki znaczników oraz uodpornię funkcję i postaram się wrzucić :)

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