|
Antwort |
Registriert seit: 8. Mär 2005 Ort: Tapfheim 55 Beiträge Delphi 2010 Enterprise |
#1
Hallo Zusammen,
ich brauch über kurz oder lang eine Möglichkeit, EDIFACT Daten zu lesen und zu schreiben. Hierzu habe ich mir etwas GEdanken gemacht und schonmal "losprogrammiert". Als Ergebniss kann ich nun eine Komponente in einer unit anbieten, welche anhand von einer einfachen Beschreibungsdatei EDIFACT Dateien verarbeiten kann und die verarbeiteten Daten auch wieder ausgeben kann. Noch zu implementieren ist 1. eine komplette Beschreibung von Edifact und zweitens die Möglichkeit, bei verschachtelten Daten also Blöcke die mehrfach vorkommen, diese entsprechend zu lesen und auch dann intern abzubilden. Hierzu mach ich mir gerade Gedanken und dachte so vor mich hin "... machst die Komponente öffentlich bei Delphi-PRAXIS ... ggfs. hilft das jemand anderem und ggfs können wir gemeinsam dann weiterentwickeln ..." Das besondere an dem ganzen ist, dass nur die UNA fest definiert wird. Alle weiteren Segmente werden dynamisch anhand der Definitionsdatei zusammengebaut. Hier mal meine Unit:
Code:
Und hier schonmal eine erste Satzbeschreibung (eigentlich könnte pro Zeile ein Segment beschrieben werden, ich habe, damit das ganze besser lesbar ist, einfach jedes Segment mit einem $SEGMENT$ definiert, später werden alle CR+LF entfernt und aus den $SEGMENT$ ein CR gemacht. Leerzeilen entfernen und schon haben wir eine Zeile pro Segmentbeschreibung:
unit mxEDIFACTunit;
interface uses windows, sysutils, Messages, Classes, db; type tmxedi_array_of_string = array of string; tmxedi_una = class(tObject) private fUNA_1, // : Composite-Element fUNA_2, // + Datenelement Trennzeichen fUNA_3, // , Zeichen für Dezimalkomma fUNA_4, // ? Release Zeichen Wennzeichen 1 bis 4 und 6 im text vorkommt muss es mit Releqase Zeichen escaped werden fUNA_5, // _ Reserviert, bleibt leer = Leerzeichen fUNA_6 // ' Segmentendezeichen : char; protected procedure set_una(value:string); function get_una:string; public constructor Create; destructor Destroy; override; published property UNA_1_Composite_Element: char read fUna_1 write funa_1; property UNA_2_Datenelement_TZ: char read funa_2 write funa_2; property UNA_3_Dezimalkomma: char read funa_3 write funa_3; property UNA_4_Release_Zeichen: char read funa_4 write funa_4; property UNA_5_Reserved: char read funa_5 write funa_5; property UNA_6_Segmentendezeichen: char read funa_6 write funa_6; property UNA:string read get_una write set_una; function StartROW(value:string):string; function AddSegment(values: array of string):string; function ExtractElement(var value:string; vtype:string):string; function GetFieldValues(value:string; var v:tmxedi_array_of_string):integer; end; tmxedi_dataset_description_fieldtype = (ftString, ftInteger, ftLargeint, ftBoolean, ftFloat, ftDate, ftTime, ftDateTime, ftTimeStamp); tmxedi_dataset_description_sids = record id:string[4]; feld:string[4]; description:string[64]; typ:tmxedi_dataset_description_fieldtype; mussfeld:boolean; end; tmxedi_dataset_description = record datacount:integer; datasids:array of tmxedi_dataset_description_sids; end; tmxedi_segment = class(tObject) private funa:tmxedi_una; fDataType:string; fdescription: array of tmxedi_dataset_description; fdata:tstrings; function getsidfieldname(const fd:array of tmxedi_dataset_description; vnr,vfeldid:integer):string; protected procedure set_data(value:string); function get_data():string; procedure set_Format(value:string); function get_Format():string; public constructor Create; overload; constructor Create(AOwner:TComponent); overload; destructor Destroy; override; published property UNA_link:tmxedi_una read funa write funa; property DataType:string read fDataType write fDataType; property DataFormat:string read get_Format write set_Format; property DataValue:string read get_data write set_data; end; tmxedi_segment_array = array of tmxedi_segment; tmxEDIFACT = class(tComponent) private fUNA:tmxedi_una; fDataSegments:tmxedi_segment_array; fDataSegmentsFormat:tstrings; protected procedure Notification(AComponent:TComponent; Operation:TOperation); override; procedure set_DataSegmentsFormat(Value:tStrings); function get_DataOutput():string; procedure set_DataInput(value: tStrings); public constructor Create(AOwner:TComponent); override; destructor Destroy; override; published property UNA:tmxedi_una read funa write funa; property DataSegments:tmxedi_segment_array read fDataSegments; property DataSegmentsFormat:tstrings read fDataSegmentsFormat write set_DataSegmentsFormat; property Output:string read get_DataOutput; property Input:tstrings write set_DataInput; end; procedure Register; implementation // ************************************************************************************************************************************* // UNA // ************************************************************************************************************************************* constructor tmxEDI_una.Create; begin inherited Create; // Initialisierung der Parameter self.UNA:=':+,? '+''''; end; destructor tmxedi_una.Destroy; begin inherited destroy; end; function tmxEDI_una.get_una; begin result:=fUNA_1+fUNA_2+fUNA_3+fUNA_4+fUNA_5+fUNA_6; end; procedure tmxEDI_una.set_una(value: string); begin if length(value)=6 then begin fUNA_1:=value[1]; fUNA_2:=value[2]; fUNA_3:=value[3]; fUNA_4:=value[4]; fUNA_5:=value[5]; fUNA_6:=value[6]; end else begin raise Exception.Create('UNA muss 6 Zeichen lang sein!'); end; end; function tmxEDI_una.StartROW(value:string):string; begin result:=value+self.UNA_2_Datenelement_TZ; end; function tmxEDI_una.AddSegment(values: array of string):string; var i:integer; vtmp:string; begin result:=''; for i:=0 to length(values)-1 do begin if result<>'' then result:=result+self.UNA_1_Composite_Element; result:=result+values[i]; end; end; function tmxEDI_una.ExtractElement(var value:string; vtype:string):string; var i:integer; begin result:=''; i:=-1; if vtype='+' then i:=pos(UNA_2_Datenelement_TZ, value); if vtype=':' then i:=pos(UNA_1_Composite_Element, value); if i<>-1 then begin if i=0 then begin i:=pos(UNA_6_Segmentendezeichen, value); if i>0 then begin result:=copy(value,1,i-1); value:=copy(value, i, length(value)); end else begin result:=value; value:=''; end; end else begin result:=copy(value,1,i-1); value:=copy(value,i+1,length(value)); end; end; // konvertierung durchführen , de_escape result:=stringreplace(result, UNA_4_Release_Zeichen+UNA_4_Release_Zeichen, '$$rlz$$', [rfReplaceAll]); // excaped escape-zeichen sichern result:=stringreplace(result, UNA_4_Release_Zeichen, '', [rfReplaceAll]); result:=stringreplace(result, '$$rlz$$', UNA_4_Release_Zeichen, [rfReplaceAll]); end; function tmxEDI_una.GetFieldValues(value:string; var v:tmxedi_array_of_string):integer; var vtmp:string; i:integer; begin setlength(v,0); result:=0; vtmp:=ExtractElement(value, ':'); setlength(v, length(v)+1); v[length(v)-1]:=vtmp; // abcde:dddd:dddd while value<>'' do begin vtmp:=ExtractElement(value, ':'); setlength(v, length(v)+1); v[length(v)-1]:=vtmp; end; result:=length(v); end; // ************************************************************************************************************************************* // UNDATASET // ************************************************************************************************************************************* constructor tmxedi_segment.Create; begin inherited Create; UNA_Link:=nil; raise Exception.Create('MXEDI Segment: Create nur mit aOwner aufrufbar!'); end; constructor tmxedi_segment.Create(AOwner: TComponent); begin inherited Create; UNA_link:=tmxEDIFACT(aOwner).fUNA; // Initialisierung der Parameter // UNG+IFTMIN+0001GPO+0120CTXX+040130:1452+UNG-123+UN+D:03A' self.DataType:='NIL'; fdata:=tstringlist.Create; //self.DataFormat:='NIL'+UNA_link.UNA_2_Datenelement_TZ; // wird per routine alles einrichten end; destructor tmxedi_segment.Destroy; begin UNA_Link:=nil; fdata.Clear; fdata.free; inherited destroy; end; function tmxedi_segment.get_Data():string; var i, idata:integer; vtemp:string; v:array of string; procedure __va(val:string); begin setlength(v, length(v)+1); v[length(v)-1]:=val; end; procedure __AddSegment(); begin result:=result+una_link.AddSegment(v); setlength(v,0); end; begin setlength(v,0); result:=una_link.StartRow(fDataType); for i:=0 to length(fdescription)-1 do begin vtemp:=''; for idata:=0 to fdescription[i].datacount-1 do begin vtemp:=vtemp+fdata.values[getsidfieldname(fdescription,i,idata)]; if idata<(fdescription[i].datacount-1) then begin vtemp:=vtemp+UNA_link.UNA_1_Composite_Element; end; end; if (length(vtemp)=(fdescription[i].datacount-1)) and (vtemp=StringOfChar(UNA_link.UNA_1_Composite_Element,fdescription[i].datacount-1)) then begin vtemp:=''; end; result:=result+vtemp; if i<(length(fdescription)-1) then begin result:=result+UNA_link.UNA_2_Datenelement_TZ; end; __AddSegment(); end; result:=result+una_link.UNA_6_Segmentendezeichen; end; function tmxedi_segment.getsidfieldname(const fd:array of tmxedi_dataset_description; vnr, vfeldid:integer):string; begin result:=fd[vnr].datasids[vfeldid].id+'_'+fd[vnr].datasids[vfeldid].feld+'_'+inttostr(vnr); end; procedure tmxedi_segment.set_Format(value:string); var vtmp:string; v:tmxedi_array_of_string; vfields,i,vc:integer; procedure __setids(var fd:tmxedi_dataset_description; idnr:integer; idtmp:string); begin // S000|0022$description of this data$ if pos('|',idtmp)>0 then begin fd.datasids[idnr].id:=copy(idtmp,1,pos('|',idtmp)-1); fd.datasids[idnr].feld:=copy(idtmp,pos('|',idtmp)+1,length(idtmp)); end else begin fd.datasids[idnr].id:=idtmp; fd.datasids[idnr].feld:=''; end; if pos('$',idtmp)>0 then begin idtmp:=copy(idtmp, pos('$',idtmp)+1, length(idtmp)); if pos('$',idtmp)>0 then idtmp:=copy(idtmp,1,pos('$',idtmp)-1); end else begin idtmp:=''; end; fd.datasids[idnr].description:=idtmp; end; function __getfields():integer; begin vtmp:=UNA_link.ExtractElement(value, '+'); vc:=UNA_link.GetFieldValues(vtmp, v); result:=length(v); end; begin if copy(value,1,length(fDataType))=fDataType then begin // UNB+S000|0022:S000|0033+S010|0020+S012|0080+0055:0099+S090+S080+S081|0041+S081|0042+S081|0043+S081|0044+S081|0046' vtmp:=UNA_link.ExtractElement(value, '+'); // = UNB setlength(fdescription,0); while (value<>'') and (value<>UNA_link.UNA_6_Segmentendezeichen) do begin vfields:=__getfields(); // hier eine bezeichnung der felder ?? setlength(fdescription, length(fdescription)+1); fdescription[length(fdescription)-1].datacount:=vfields; setlength(fdescription[length(fdescription)-1].datasids,vfields); for i:=0 to vfields-1 do begin __setids(fdescription[length(fdescription)-1], i, v[i]); end; end; end else begin raise Exception.Create(format('Description not for: "%s"',[value])); end; end; function tmxedi_segment.get_format():string; begin result:='format'; end; procedure tmxedi_segment.set_Data(value: string); var vtmp:string; idata, vc, tmpcount,i:integer; v:tmxedi_array_of_string; function __getfields():integer; begin vtmp:=UNA_link.ExtractElement(value, '+'); vc:=UNA_link.GetFieldValues(vtmp, v); result:=length(v); end; begin // UNB+UNOC:3+0001GPO+GPO+040130:1452+GPO-001++++++1' vtmp:=UNA_link.ExtractElement(value, '+'); // = UNB for i:=0 to length(fdescription)-1 do begin tmpcount:=__getfields(); if tmpcount=fdescription[i].datacount then begin for idata:=0 to tmpcount-1 do begin fdata.Values[getsidfieldname(fdescription,i, idata)]:=v[idata]; end; end else begin for idata:=0 to fdescription[i].datacount-1 do begin if idata<=tmpcount-1 then begin fdata.Values[getsidfieldname(fdescription,i, idata)]:=v[idata]; end else begin fdata.values[getsidfieldname(fdescription,i, idata)]:=''; end; end; { raise Exception.Create(fDataType+#13+ 'expected count:'+inttostr(fdescription[i].datacount)+#13+ 'found count:'+inttostr(tmpcount)+#13+ 'data:'+vtmp); } end; end; end; // Ende UN-Dataset // ************************************************************************************************************************************* procedure tmxEDIFACT.Notification(AComponent:TComponent; Operation:TOperation); begin inherited Notification(aComponent, Operation); if (operation=opRemove) then begin //if (aComponent = ADSConnection) and (ADSConnection<>nil) then ADSConnection:=nil; //if (aComponent = MxINI) and (MxINI<>nil) then MxINI:=nil; end; end; constructor tmxEDIFACT.Create(AOwner: TComponent); begin inherited Create(AOwner); // Initialisierung der Parameter funa:=tmxedi_una.Create; fDataSegmentsFormat:=tstringlist.Create; end; destructor tmxEDIFACT.Destroy; var i:integer; begin funa.free; funa:=nil; fDataSegmentsFormat.Free; for i:=0 to length(fDataSegments)-1 do begin fDataSegments[i].Free; end; inherited Destroy; end; function tmxEDIFACT.get_DataOutput():string; var i:integer; begin result:=''; for i:=0 to length(fdatasegments)-1 do begin result:=result+fdatasegments[i].DataValue; end; end; procedure tmxEDIFACT.set_DataInput(value: tStrings); var vtmp:tstrings; vtemp, vsegmentid:string; si, i:integer; vplus, vpunkt:string; vSegment:tmxedi_segment; begin vtemp:=value.Text; vtemp:=StringReplace(vtemp, #10, '', [rfReplaceAll, rfIgnoreCase]); vtemp:=StringReplace(vtemp, #13, '', [rfReplaceAll, rfIgnoreCase]); vtemp:=StringReplace(vtemp, fUNA.UNA_6_Segmentendezeichen, #13, [rfReplaceAll, rfIgnoreCase]); vtmp:=tstringlist.create; vtmp.text:=vtemp; vplus:=fUNA.UNA_2_Datenelement_TZ; vpunkt:=fUNA.UNA_1_Composite_Element; for i:=0 to vtmp.Count-1 do begin vtemp:=trim(vtmp[i]); if vtemp<>'' then begin vsegmentid:=copy(vtemp,1,pos(vplus,vtemp)-1); for si:=0 to Length(fdatasegments)-1 do begin if fdatasegments[si].fDataType=vsegmentid then begin // was ist mit mehrfach vorkommenden segmenten ?? fdatasegments[si].DataValue:=vtemp; break; end; end; end; end; vtmp.free; end; procedure tmxEDIFACT.set_DataSegmentsFormat(Value: TStrings); var vtmp:tstrings; vtemp:string; i:integer; vplus, vpunkt:string; vSegment:tmxedi_segment; begin fDataSegmentsFormat.clear; vtemp:=value.Text; vtemp:=StringReplace(vtemp, #10, '', [rfReplaceAll, rfIgnoreCase]); vtemp:=StringReplace(vtemp, #13, '', [rfReplaceAll, rfIgnoreCase]); vtemp:=StringReplace(vtemp, '$SEGMENT$', #13, [rfReplaceAll, rfIgnoreCase]); vtmp:=tstringlist.create; vtmp.text:=vtemp; vplus:=fUNA.UNA_2_Datenelement_TZ; vpunkt:=fUNA.UNA_1_Composite_Element; for i:=0 to vtmp.Count-1 do begin vtemp:=trim(vtmp[i]); if vtemp<>'' then begin vSegment:=tmxedi_segment.Create(self); vsegment.DataType:=copy(vtemp,1,pos(vplus,vtemp)-1); vsegment.DataFormat:=vtemp+UNA.UNA_6_Segmentendezeichen; //vSegment.DataValue:='UNB+UNOC:3+0001GPO+GPO+040130:1452+GPO-001++++++1'+''''; setlength(fdatasegments, length(fdatasegments)+1); fDataSegments[length(fDataSegments)-1]:=vSegment; end; end; vtmp.free; end; // ******************************************************************************************************************************* procedure Register; begin RegisterComponents('MicrotronX', [tmxEDIFACT]); end; end.
Code:
Die Satzbeschreibung einfach in eine Textdatei speichern.
$SEGMENT$
UNB+ S001|0001$Inerchange Header UNOB = UN/ECE Level C$: S001|0002$EDI Syntax Versionsnummer$+ S002|0004$Nachrichten Absender Identification$+ S003|0010$Nachrichten Empfänger Identification$+ S004|0017$Nachricht Datum JJMMTT$: S004|0019$Nachricht Zeit HHMM$+ 0020$ICR interchange control ref, eindeutige Nachrichten ID No$+ S005|0022$Ref bzw. Passwort vom Empfänger$+ S005|0026$Anwendungsreferenz, z.B. IFTMIN$+ S005|0029$Verarbeitungspriorität$+ S005|0031$Bestätigungsanforderung$+ S005|0032$Austauschvereinbarungskennung$+ S005|0035$Testkennzeichen:1 ansonsten leer$ $SEGMENT$ UNH+ 0062$Laufende Nummer der Nachricht$+ S009|0065$Nachricht Identifikation$: S009|0052$Message Type Version Number "D" Entwurfs-Version$: S009|0054$Message Type Release Number "96B" Ausgabe 1996B$: S009|0051$Controlling Agenc "AB" oder "UN" Codeliste E0051$: S009|0057$Anwendungscode der Organisation$+ 0068$Nachrichten Nummer$ $SEGMENT$ UNZ+ 0046$Anzahl Nachrichten$+ 0020$ICR Nummer$ Musterdaten zum Importieren und Testen:
Code:
Genutzt werden kann das wie in folgendem Beispiel ersichtlich ist:
UNA:+.? 'UNB+UNOC:3+9001891+5866+080623:1708+08062317084704'UNH+1+CUSDEC:D:96B:UN:DEXPDA+9001891-08062317084704'UNZ+1+08062317084704'
Code:
Ich hoffe ich finde hier den einen oder anderen, der Interesse an der weiterentwicklung hat ...
procedure TForm3.Button1Click(Sender: TObject);
var vdatei:tstrings; mxedi:tmxEDIFACT; begin mxedi:=tmxEDIFACT.create(self); vdatei:=tstringlist.create; // Satzbeschreibung wird eingelesen und alle Objekte werden erzeugt vdatei.LoadFromFile('Segmentbeschreibungs-Datei.txt'); mxedi.DataSegmentsFormat:=vdatei; // Musterdatei wird eingelesen und anhand der Satzbeschreibung verarbeitet, // ggfs fehlende Segmente oder Felder in den Musterdaten werden "übersprungen" sodass die Ausgabe diese korrigiert vdatei.loadfromfile('musterdaten.txt'); mxedi.Input:=vdatei; // Jetzt die Ausgabe durch die Komponente anhand der Satzbeschreibung erzeugen lassen und in memo1 ausgeben Memo1.Lines.clear; memo1.lines.add(mxedi.Output); // Objekte freigeben vdatei.free; mxedi.free; end;
Yusuf Zorlu
yusuf.zorlu@microtronx.com Meine Arbeit ist so geheim ... ich habe selbst keine Ahnung was ich überhaupt mache! Geändert von TBx (23. Nov 2017 um 18:16 Uhr) |
Zitat |
Registriert seit: 2. Mär 2004 5.508 Beiträge Delphi 5 Professional |
#2
Hmmm, also wenn ich mit EDIFACT nochmal anfangen würden, dann würde ich mir einen Konverter EDIFACT<->XML schreiben und im Programm nur auf das DOM zugreifen.
Auf diese Idee sind schon andere gekommen und es gibt sogar einen Standard dafür, wie man von EDIFACT -> XML (und zurück) kommt. http://en.wikipedia.org/wiki/XML/EDIFACT Blöd nur, das die ISO TS 20625 richtig viel Geld kostet. Vorallem wenn man EDIFACT lesen möchte wäre eine schön aufbereitete Baumstruktur (DOM) sehr nützlich. Man kann dann auch die Annehmlichkeiten wie XPath und XQuery nutzen.
Andreas
Geändert von shmia (25. Sep 2012 um 18:08 Uhr) |
Zitat |
Registriert seit: 8. Mär 2005 Ort: Tapfheim 55 Beiträge Delphi 2010 Enterprise |
#3
Hmmm, also wenn ich mit EDIFACT nochmal anfangen würden, dann würde ich mir einen Konverter EDIFACT<->XML schreiben und im Programm nur auf das DOM zugreifen.
Auf diese Idee sind schon andere gekommen und es gibt sogar einen Standard dafür, wie man von EDIFACT -> XML (und zurück) kommt. http://en.wikipedia.org/wiki/XML/EDIFACT Blöd nur, das die ISO TS 20625 richtig viel Geld kostet. Vorallem wenn man EDIFACT lesen möchte wäre eine schön aufbereitete Baumstruktur (DOM) sehr nützlich. Man kann dann auch die Annehmlichkeiten wie XPath und XQuery nutzen. Zur Zeit überlege ich, wie ich in den aktuellen Stand am sinnvollsten die Baumstruktur einbringe ... natürlich so dynamisch wie nur möglich ... Sobald meine Gedanken geordnet und in die richtige Richtung gehen werde ich hier einfach immer den neuen Source posten ...
Yusuf Zorlu
yusuf.zorlu@microtronx.com Meine Arbeit ist so geheim ... ich habe selbst keine Ahnung was ich überhaupt mache! |
Zitat |
Registriert seit: 9. Jul 2010 Ort: Köln 667 Beiträge Delphi 2010 Professional |
#4
Übrigens beinhaltete die Jedi JCL bis einschließlich Version 2.1 Units für die Bearbeitung von EDIFACT-Dokumenten.
|
Zitat |
Registriert seit: 8. Mär 2005 Ort: Tapfheim 55 Beiträge Delphi 2010 Enterprise |
#5
Übrigens beinhaltete die Jedi JCL bis einschließlich Version 2.1 Units für die Bearbeitung von EDIFACT-Dokumenten.
Yusuf Zorlu
yusuf.zorlu@microtronx.com Meine Arbeit ist so geheim ... ich habe selbst keine Ahnung was ich überhaupt mache! |
Zitat |
Registriert seit: 21. Nov 2014 1 Beiträge |
#6
bei mir steht gerade das gleiche Problem.
Schon was neues gefunden? |
Zitat |
Registriert seit: 8. Mär 2005 Ort: Tapfheim 55 Beiträge Delphi 2010 Enterprise |
#7
bei mir steht gerade das gleiche Problem.
Schon was neues gefunden? wir haben uns einen eigenen dynamischen Konverter gebaut, mit dem wir jegliche Edifact Dateien lesen und schreiben können. Als Basis haben wir die Komponenten von nSoftware verwendet. Hat uns alles zwar etwas mehr Geld gekostet aber durch unsere eigene Komponente verfügen wir nun über uneingeschränkte Lese und Schreibmöglichkeiten ... ohne das ganze auf die gewünschte Version z.B. 95b oder 01B anzupassen. Macht alles die Komponente selber ... Mehr Infos zur Basis-Komponente hier: https://www.nsoftware.com/ibiz/edifact/
Yusuf Zorlu
yusuf.zorlu@microtronx.com Meine Arbeit ist so geheim ... ich habe selbst keine Ahnung was ich überhaupt mache! |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |