Autor Beitrag
Sahroma
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 28.05.15 09:06 
Hallo zusammen,

ich habe ein Programm geschrieben das *.dfm, *.pas und *.txt Dateien durchsucht nach Wörtern die ich in eine TEdit Box eintrage. Funktioniert auch alles ohne Probleme nur habe ich mit den *.dfm Dateien Probleme, erstens weil diese ja Umbrüche haben und Umlaute (ä,ü usw) umwaltet in z.B #230. Ich versuche schon seit mehreren Wochen dies hin zubekommen habe auch schon das Internet durchforstet aber nichts gefunden. Ihr seit jetzt meine letzte Hoffnung. :oops:
Hier mal den Code von der Suche:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
procedure TVolltextsuche.FindMyFiles;
var
  LFiles: TStringDynArray;
  LStrs: TStringList;
  LValue: string;
  i : Integer;
begin
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.txt',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.dfm',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.pas',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
end;

function TVolltextsuche.BuildDirectoryFilterPredicate(ASearchText: string): TDirectory.TFilterPredicate;
begin
  Result :=
    function(const Path: stringconst SearchRec: TSearchRec): Boolean
    begin
      Result := ContentContainsText(PrepareContentForSearch(GetContentFromFile(TPath.Combine(Path, SearchRec.Name))), ASearchText);
    end;
end;

function TVolltextsuche.GetContentFromFile(const AFilename: string): string;
begin
  Result := TFile.ReadAllText(AFilename);
end;

function TVolltextsuche.PrepareContentForSearch(const AContent: string): string;
begin
  Result := AContent;
end;

function TVolltextsuche.ContentContainsText(const AContent: stringconst ASearchText: string): Boolean;
begin
  Result := AContent.Contains(ASearchText);
end;


Habt ihr da Vorschläge wie ich die Umbrüche vor der Suche aus der Datei temporär entferne und das die Suche die codierten Umlaute beachtet? Danke schonmal für eure Hilfe.

Grüße
Sahroma


Zuletzt bearbeitet von Sahroma am Do 28.05.15 15:37, insgesamt 1-mal bearbeitet
Stundenplan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 128
Erhaltene Danke: 32

Win 7
Delphi 7 Pers., C# (VS 2010 Express)
BeitragVerfasst: Do 28.05.15 13:48 
Warum auch immer du Umbrüche entfernen willst - StringReplace sollte treue Dienste leisten: Ersetze einfach alle #13 und alle #10 durch Leerstrings.
Genauso kannst du auch Umlaute behandeln.
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 28.05.15 13:56 
user profile iconStundenplan hat folgendes geschrieben Zum zitierten Posting springen:
Warum auch immer du Umbrüche entfernen willst - StringReplace sollte treue Dienste leisten: Ersetze einfach alle #13 und alle #10 durch Leerstrings.
Genauso kannst du auch Umlaute behandeln.


An StringReplace habe ich auch schon gedacht aber irgendwie krieg ich den Code nicht richtig gebacken. Warum ich Umbrüche entfernen will ist eine lange Geschichte. :) Soweit ich weiß funktioniert StringReplace doch nur mit StringList o.ä. und nicht mit normalen Strings oder StringArrays wie es bei mir der Fall ist oder habe ich da etwas falsch verstanden?

Programmiere mit Delphi noch nicht so lange, entschuldigt also gegebenenfalls dumme Fragen. :lol:
Stundenplan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 128
Erhaltene Danke: 32

Win 7
Delphi 7 Pers., C# (VS 2010 Express)
BeitragVerfasst: Do 28.05.15 14:02 
Doch, StringReplace nimmt einzelne Strings und verarbeitet diese. ;-)
Bsp.: result := StringReplace(AContent, #13'', [rfReplaceAll]);
Damit ersetzt du in AContent den Char #13 durch einen Leerstring; rfReplaceAll bestimmt, dass alle Vorkommnisse ausgetauscht werden.
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 28.05.15 14:13 
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
LFiles := TDirectory.GetFiles(ebPfad.Text,'*.dfm',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        LValue := StringReplace(LValue,'''+''', [rfReplaceAll]);
        LValue := StringReplace(LValue,'''''', [rfReplaceAll]);
        LValue := StringReplace(LValue,#13#10'', [rfReplaceAll]);
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;


Habe es mal so eingefügt aber wenn ich dann den Compiler starte, kommt die Meldung: "Der linken Seite kann nichts zugewiesen werden". Stelle ich mich da jetzt nur doof an?
SMO
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 120
Erhaltene Danke: 18


D2005 Personal
BeitragVerfasst: Do 28.05.15 15:08 
For-Schleifenvariablen sind in der Tat Read-Only.

Entweder so:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var i: Integer;

for i := Low(LFiles) to High(LFiles) do
begin
  LValue := LFiles[i];
  LValue := StringReplace(LValue,'''+''', [rfReplaceAll]);
  // usw.
end;


oder so:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var temp: string;

for LValue in LFiles do
begin
  temp := LValue;
  temp := StringReplace(temp,'''+''', [rfReplaceAll]);
  // usw. mit temp
end;
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 28.05.15 15:24 
Danke für die Infos. Habe die zweite Möglichkeit von SMO ausprobiert aber es will immer noch nicht so wie es sein soll. Er ignoriert den StringReplace komplett.
Hier mal den kompletten Code vom Programm mit allen Buttons usw. vielleicht findet ihr da ein Fehler was das verhindert. Ich bin mit meinem Latein komplett am Ende. :(

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
unit Volltext;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Types, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, Vcl.FileCtrl, System.IOUtils,
  System.Masks, Winapi.ShellAPI, Vcl.Buttons;

type
  TVolltextsuche = class(TForm)
    lbErgebnis: TListBox;
    btnStart: TButton;
    btnLöschen: TButton;
    ebSuche: TEdit;
    btnSucheN: TButton;
    FormPanel: TPanel;
    lblSucheT: TLabel;
    lblSucheP: TLabel;
    ebPfad: TEdit;
    procedure btnSucheNClick(Sender: TObject);
    procedure btnLöschenClick(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure lbErgebnisClick(Sender: TObject);
  private
    { Private-Deklarationen }
    procedure FindMyFiles;
    // Lädt den Inhalt aus der Datei in einen String
    function GetContentFromFile(const AFilename: string): string;
    // Überarbeitet den Content für die einfachere Suche nach dem Text
    function PrepareContentForSearch(const AContent: string): string;
    // Durchsucht den Content nach dem Suchtext
    function ContentContainsText(const AContent, ASearchText: string): Boolean;
    // Erzeugt den Such-Filter
    function BuildDirectoryFilterPredicate(ASearchText: string): TDirectory.TFilterPredicate;
  public
    { Public-Deklarationen }
  end;

var
  Volltextsuche: TVolltextsuche;
  OverrideCbBAdr: Boolean = False;
implementation

{$R *.dfm}
procedure TVolltextsuche.FindMyFiles;
var
  LFiles: TStringDynArray;
  LStrs: TStringList;
  LValue: string;
  dfmstring : string;
begin
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.txt',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.dfm',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        dfmstring := LValue;
        dfmstring := StringReplace(dfmstring,'''+''', [rfReplaceAll]);
        dfmstring := StringReplace(dfmstring,'''''', [rfReplaceAll]);
        dfmstring := StringReplace(dfmstring,#13#10'', [rfReplaceAll]);
        lbErgebnis.Items.Add(dfmstring);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.pas',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
end;

function TVolltextsuche.BuildDirectoryFilterPredicate(ASearchText: string): TDirectory.TFilterPredicate;
begin
  Result :=
    function(const Path: stringconst SearchRec: TSearchRec): Boolean
    begin
      Result := ContentContainsText(PrepareContentForSearch(GetContentFromFile(TPath.Combine(Path, SearchRec.Name))), ASearchText);
    end;
end;

function TVolltextsuche.GetContentFromFile(const AFilename: string): string;
begin
  Result := TFile.ReadAllText(AFilename);
end;

procedure TVolltextsuche.lbErgebnisClick(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to lbErgebnis.Items.Count -1 do
    begin
      if lbErgebnis.Selected[i] then
      ShellExecute(Handle, 'open', PChar('notepad'), PChar(lbErgebnis.Items.Strings[i]), nil, SW_SHOW);
    end;
end;

function TVolltextsuche.PrepareContentForSearch(const AContent: string): string;
begin
  Result := AContent;
end;

function TVolltextsuche.ContentContainsText(const AContent: stringconst ASearchText: string): Boolean;
begin
  Result := AContent.Contains(ASearchText);
end;

procedure TVolltextsuche.btnLöschenClick(Sender: TObject);
begin
lbErgebnis.Clear;
end;

procedure TVolltextsuche.btnStartClick(Sender: TObject);
begin
FindMyFiles;
end;

procedure TVolltextsuche.btnSucheNClick(Sender: TObject);
var
  Directory: string;
begin
  Directory := 'C:\';
    if SelectDirectory('Bitte ein Verzeichnis wählen. ','',Directory) then
      begin
        if not OverrideCbBAdr then
          begin
            OverrideCbBAdr := True;
            ebPfad.Text:= '';
          end;
            ebPfad.Text := Directory;
      end;
end;

end.
Stundenplan
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 128
Erhaltene Danke: 32

Win 7
Delphi 7 Pers., C# (VS 2010 Express)
BeitragVerfasst: Do 28.05.15 16:00 
Momentan wendest du die Replaces ja auch auf die Dateinamen an; ich nehme an, der Replace-Code gehört in PrepareContentForSearch?

Für diesen Beitrag haben gedankt: Sahroma
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Fr 29.05.15 07:44 
user profile iconStundenplan hat folgendes geschrieben Zum zitierten Posting springen:
Momentan wendest du die Replaces ja auch auf die Dateinamen an; ich nehme an, der Replace-Code gehört in PrepareContentForSearch?


Oh jetzt wo du es sagst, stimmt. :oops:
So kann das natürlich nicht funktionieren. Danke dir vielmals. :idea:
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 11.06.15 08:57 
Programm läuft nun ohne Probleme. Nun bin ich dabei noch ein paar Zusatzfunktionen einzufügen. Bisher habe ich eine EditBox wo mir die Anzahl der gefundenen Treffer anzeigt. Würde aber gerne noch eine zusätzlich Editbox einbauen wo mir die Anzahl ALLER Ergebnisse ausgibt (also wie viele Dateien er insgesamt durchsucht hat). Bekomme das aber irgendwie nicht gebacken. Könnt ihr mir da weiterhelfen?

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
procedure TVolltextsuche.FindMyFiles;
var
  LFiles: TStringDynArray;
  LValue: string;
  Treffer: Integer;
begin
  pb1.Min := 0;
  pb1.Max := 80;
  pb1.Step := 20;
  Treffer := 0;
  pb1.StepIt;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.txt',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  pb1.StepIt;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.dfm',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  pb1.StepIt;
  LFiles := TDirectory.GetFiles(ebPfad.Text,'*.pas',TSearchOption.soAllDirectories,BuildDirectoryFilterPredicate(ebSuche.Text));
  lbErgebnis.Items.BeginUpdate;
  try
    for LValue in LFiles do
      begin
        lbErgebnis.Items.Add(LValue);
      end;
  finally
    lbErgebnis.Items.EndUpdate;
  end;
  pb1.StepIt;
  ebGTreffer.Text := IntToStr(lbErgebnis.Items.Count);
end;


ebGTreffer = Editboxname für gefundene Treffer. Eine zusätzliche Editbox soll wie bereits beschrieben noch ALLE Ergebnisse während der Suche ausgeben.
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Do 11.06.15 09:47 
- Nachträglich durch die Entwickler-Ecke gelöscht -
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 11.06.15 10:03 
user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Hallo Sahroma,

im ersten Beitrag stand doch:

Zitat:
ich habe ein Programm geschrieben das *.dfm, *.pas und *.txt Dateien durchsucht nach Wörtern die ich in eine TEdit Box eintrage [...]

und jetzt:

Zitat:
Würde aber gerne noch eine zusätzlich Editbox einbauen wo mir die Anzahl ALLER Ergebnisse ausgibt (also wie viele Dateien er insgesamt durchsucht hat) [...]

Möchtest du nun die Anzahl aller gefundenen Wörter ermitteln oder die Anzahl aller .dfm / .pas / .txt Dateien ermitteln?
Für beide Fälle müsstest du nur eine Variable definieren die bei 0 anfängt und sich um 1 erhöht, sobald ein Treffer vorhanden ist bzw. eine Datei durchsucht wurde.


Ich möchte die Anzahl ALLER .dfm / .pas / .txt Dateien wissen (nicht nur die wo mit dem Eintrag in der Editbox übereinstimmen). Sorry da habe ich mich falsch ausgedrückt. Das mit dem Wert immer um 1 erhöhen habe ich mir auch schon gedacht nur sind alle Versuche die ich damit gemacht habe fehlgeschlagen. Denke einfach das ich mich da wieder dumm anstelle und es ganz einfach ist. :oops:
baumina
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 305
Erhaltene Danke: 61

Win 7
Delphi 10.2 Tokyo Enterprise
BeitragVerfasst: Do 11.06.15 10:15 
GetFiles ohne Editfeld-Suchkriterien aufrufen und gefundene Anzahl ausgeben.

Für diesen Beitrag haben gedankt: Sahroma
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Do 11.06.15 12:08 
- Nachträglich durch die Entwickler-Ecke gelöscht -

Für diesen Beitrag haben gedankt: Sahroma
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Do 11.06.15 15:06 
Danke euch beiden. Habe bauminas Variante genommen und funktioniert genau so wie ich es mir gedacht habe.
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Fr 12.06.15 14:03 
Ich weiß ich gehe euch mit meinen dummen Fragen auf die Nerven, aber leider arbeite ich mit Delphi noch nicht so lange.
Mein Suchergebnis gebe ich ja in einer ListBox aus, nun habe ich die Anweisung das ich es in ein TListView ausgebe soll. Habe damit noch überhaupt keine Erfahrung. Habe bisher 4 Spalten in die ListView Box eingefügt (Name der Datei, Ordner wo die Datei liegt, Größe der Datei und Typ (pas oder dfm oder txt). Ich habe aber absolut keine Ahnung wie ich das Suchergebnis so zerteile das die Inhalte der jeweiligen Spalten auch stimme? Bisher bekam ich nur den kompletten Pfad in der Namens Spalte ausgegeben.
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 12.06.15 20:52 
- Nachträglich durch die Entwickler-Ecke gelöscht -

Für diesen Beitrag haben gedankt: Sahroma
Sahroma Threadstarter
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 66
Erhaltene Danke: 1


Delphi 10 Seattle, Delphi 10.1 Berlin
BeitragVerfasst: Fr 19.06.15 14:44 
Und wieder ist ein Problem aufgetreten bzw. ich stehe auf dem Schlauch. Habe nun ein MemoFeld im Formular drin mit einer Checkbox. Wenn die Checkbox markiert ist soll bei der Suche die im Memofeld eingetragen Ordner von der Suche ausgeschlossen werden. Ich sitze hier wie in Anfänger der das erst mal programmiert. Mir fällt einfach nicht ein wie ich das umsetze. Bitte helft mir auf die Sprünge. :nixweiss:
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Fr 19.06.15 17:22 
- Nachträglich durch die Entwickler-Ecke gelöscht -