Diskussion Code-Formatierung für die C und C++ Foren

Vielleicht blickt ja jemand anderes durch, der uns dieses komische Verhalten erklären kann. :D

Es sieht aktuell so aus, als würde folgende Zeile in der Anwendung funktionieren und das Problem beheben. :)
PHP:
return preg_replace('/(\[CODE(?:=([a-z]+)?)?\])((?:.*\n?)*)/', addcslashes($formatted_code, '\\'), $code_block) ?? $code_block

Ich fürchte, dieses Problem wird aber früher oder später mit anderen Zeichen in ähnlicher Form wieder auftreten.

Erstmal danke für die Hilfe, @german!

Wenn jemand eine bessere Lösung hat, gerne her damit.

Edit:
@Lowl3v3l Ich habe jetzt mal deine gepostete Style Config eingearbeitet. Passt das so?
Oder müssen wir noch irgendwelche Änderungen vornehmen?
 
Zuletzt bearbeitet:
Ich fürchte, dieses Problem wird aber früher oder später mit anderen Zeichen in ähnlicher Form wieder auftreten.
Dann kannst du addcslashes weitere Zeichen hinzufügen. Bei $n (mit n = 0..99) wird dasselbe Problem auftauchen, da es wie \n als Platzhalter für Submatches verwendet wird. BTW das \134 ist die oktale Repräsentation für den Backslash und sollte somit ohne Nebenwirkungen sein. \044 wäre das Dollarzeichen.
Wenn du also '\044\134' übergibst, sollte das alles sein, wenn man den Anmerkungen zu preg_replace dort Glauben schenken darf:
 
Zuletzt bearbeitet:
Ich versteh' die Welt nicht mehr. :cautious:
Also bei dem RegEx ergibt das ganze zumindest halbwegs Sinn. Kurz gesagt, der Punkt macht dir 'nen fetten Strich durch deine Rechnung. Im Prinzip ist .* für die Search-Engine 'ne Einladung für "nimm alles, was bei 3 nicht auf dem Baum ist". D.h. aber auch, dass er ohne Probleme einen leeren String matchen kann. Deswegen würde ich dir den Rat geben, diese Methode, wenn's geht, immer zu vermeiden.
echo '<pre>', preg_replace('/.*/', 'test\0hallo', 'test\0hallo'), '</pre>';
Hier z.B. scheint die Funktion einmal den kompletten String und einmal das leere String-Ende zu matchen. Die Folge: Beim ersten Match wird der komplette String durch replacement ersetzt und durch die Rückreferenzierung '\0' der Match an der Stelle eingesetzt, das führt zu testtest\0hallohallo. Anschließend wird das String-Ende auch nochmal durch replacement ersetzt, '\0' ist hier daher leer, dadurch kommt schlussendlich testtest\0hallohallotesthallo zustande. Die These lässt sich leicht beweisen, indem du als RegEx mal /^.*$/ nimmst, dann bekommst du nämlich nur den ersten Match.
Was das Escaping angeht, bin ich Germans Meinung: Vermutlich braucht man zweifaches Escaping durch die RegEx- und die String-Regeln (total unsinnig eigentlich, aber scheint wohl der Fall zu sein). Wenn du allerdings preg_replace_callback verwendest, scheint das nicht mehr nötig zu sein, da du da mittels des Funktionsarguments die Rückreferenzierung bekommst.
Folgendes Beispiel funktioniert bei mir einwandfrei:
PHP:
const REGEX = '/(\[CODE(?:=(?:[a-z]+)?)?\])((?:.*\n?)*)(\[\/CODE\])/';

function format_code ($code) {
    return $code . "FORMATTED, YAY";
}
echo '<pre>', preg_replace_callback(REGEX, function ($match) {
    return $match[1] . format_code($match[2]) . $match[3];
}, 'Hallo, mein Code: [CODE=c]result[length] = "\0" ; [\/CODE]'), '</pre>';
// Shit, jetzt macht mir das Forum n Strich durch die Rechnung xD
// Natürlich im String das \/Code ohne den Backslash
Ausgabe: Hallo, mein Code: [CODE=c]result[length] = "\0" ; FORMATTED, YAY[/CODE]

Edit: Ich hab der Diskussion hier jetzt nicht komplett gefolgt, aber wenn der language-tag für die Formatierung wichtig sein sollte, kann man natürlich auch sowas machen:
PHP:
const REGEX = '/\[CODE(?:=([a-z]*))?\]((?:.*\n?)*)\[\/CODE\]/';

function format_code ($code, $lan) {
    return $code . 'FORMATTED' . ($lan ? ' in `' . $lan . '`' : ' without language-tag');
}
echo '<pre>', preg_replace_callback(REGEX, function ($match) {
    return '[CODE' . ($match[1] ? '='.$match[1] : '') . ']' . format_code($match[2], $match[1]) . '[\/CODE]';
}, 'Hallo, mein Code: [CODE=c]result[length] = "\0" ; [\/CODE]'), '</pre>';
Auch hier wieder bei den [\/CODE] entsprechend den Backslash löschen (außer natürlich im RegEx); das Forum zeigt den Post nicht richtig an, wegen dem [/CODE].
Btw wäre es meiner Meinung nach sinnvoller, das ganze in den RegEx-Parser für BB-Codes zwischenzuschalten, oder nicht? Einfach, wenn ein BB-Code "CODE" erkannt wird, dann wird der Inhalt entsprechend formatiert. Oder sucht die Foren-Software für jeden möglichen BB-Code separat? Wäre ja mega ineffizient :unsure:
 
Zuletzt bearbeitet:
Dann kannst du addcslashes weitere Zeichen hinzufügen. Bei $n (mit n = 0..99) wird dasselbe Problem auftauchen, da es wie \n als Platzhalter für Submatches verwendet wird. BTW das \134 ist die oktale Repräsentation für den Backslash und sollte somit ohne Nebenwirkungen sein. \044 wäre das Dollarzeichen.
Wenn du also '\044\134' übergibst, sollte das alles sein, wenn man den Anmerkungen zu preg_replace dort Glauben schenken darf:

Wenn das funktionieren würde, wäre das natürlich eine sehr simple Lösung.
Jedenfalls eine hilfreiche Erläuterung. :)

Hier z.B. scheint die Funktion einmal den kompletten String und einmal das leere String-Ende zu matchen. Die Folge: Beim ersten Match wird der komplette String durch replacement ersetzt und durch die Rückreferenzierung '\0' der Match an der Stelle eingesetzt, das führt zu testtest\0hallohallo. Anschließend wird das String-Ende auch nochmal durch replacement ersetzt, '\0' ist hier daher leer, dadurch kommt schlussendlich testtest\0hallohallotesthallo zustande. Die These lässt sich leicht beweisen, indem du als RegEx mal /^.*$/ nimmst, dann bekommst du nämlich nur den ersten Match.

Das erklärt einiges! (y)
Trotzdem ein komisches, undurchsichtiges Verhalten. :D

Was das Escaping angeht, bin ich Germans Meinung: Vermutlich braucht man zweifaches Escaping durch die RegEx- und die String-Regeln (total unsinnig eigentlich, aber scheint wohl der Fall zu sein). Wenn du allerdings preg_replace_callback verwendest, scheint das nicht mehr nötig zu sein, da du da mittels des Funktionsarguments die Rückreferenzierung bekommst.
Folgendes Beispiel funktioniert bei mir einwandfrei:
PHP:
const REGEX = '/(\[CODE(?:=(?:[a-z]+)?)?\])((?:.*\n?)*)(\[\/CODE\])/';

function format_code ($code) {
    return $code . "FORMATTED, YAY";
}
echo '<pre>', preg_replace_callback(REGEX, function ($match) {
    return $match[1] . format_code($match[2]) . $match[3];
}, 'Hallo, mein Code: [CODE=c]result[length] = "\0" ; [\/CODE]'), '</pre>';
// Shit, jetzt macht mir das Forum n Strich durch die Rechnung xD
// Natürlich im String das \/Code ohne den Backslash
Ausgabe: Hallo, mein Code: [CODE=c]result[length] = "\0" ; FORMATTED, YAY[/CODE]

Edit: Ich hab der Diskussion hier jetzt nicht komplett gefolgt, aber wenn der language-tag für die Formatierung wichtig sein sollte, kann man natürlich auch sowas machen:
PHP:
const REGEX = '/\[CODE(?:=([a-z]*))?\]((?:.*\n?)*)\[\/CODE\]/';

function format_code ($code, $lan) {
    return $code . 'FORMATTED' . ($lan ? ' in `' . $lan . '`' : ' without language-tag');
}
echo '<pre>', preg_replace_callback(REGEX, function ($match) {
    return '[CODE' . ($match[1] ? '='.$match[1] : '') . ']' . format_code($match[2], $match[1]) . '[\/CODE]';
}, 'Hallo, mein Code: [CODE=c]result[length] = "\0" ; [\/CODE]'), '</pre>';
Auch hier wieder bei den [\/CODE] entsprechend den Backslash löschen (außer natürlich im RegEx); das Forum zeigt den Post nicht richtig an, wegen dem [/CODE].

Deine Herangehensweise mit preg_replace_callback werde ich mir auf jeden Fall mal näher anschauen.
Dadurch könnten wir den Prozess eventuell deutlich vereinfachen und gleichzeitig unser Escaping-Problem umgehen.
Super Idee! :)

Btw wäre es meiner Meinung nach sinnvoller, das ganze in den RegEx-Parser für BB-Codes zwischenzuschalten, oder nicht? Einfach, wenn ein BB-Code "CODE" erkannt wird, dann wird der Inhalt entsprechend formatiert. Oder sucht die Foren-Software für jeden möglichen BB-Code separat? Wäre ja mega ineffizient :unsure:

Wie ich das genau in die Forensoftware einbaue, habe ich noch nicht abschließend geklärt.
Ich blick da auch noch nicht ganz durch.
Bisher hatte ich mich grob an einem Beispiel in der XenForo-Dokumentation orientiert, wo gezeigt wird, wie man in den Save-Prozess eines Forenbeitrags eingreifen kann.
Sprich: Ich greife den kompletten Beitragstext ab, sende den an meine API und überschreibe den Beitragstext mit dem Text inkl. dem neu formatierten Code.
Das ist eventuell nicht die effizienteste Lösung, dafür aber erstmal relativ einfach zu realisieren.
 
wie man in den Save-Prozess eines Forenbeitrags eingreifen kann.
Sprich: Ich greife den kompletten Beitragstext ab, sende den an meine API und überschreibe den Beitragstext mit dem Text inkl. dem neu formatierten Code.
Achso, dann ist es ja zu verkraften. Dachte schon, der würde das wie beim LaTeX-Plugin jedes Mal bei der Anzeige neu ausführen, aber so ergibt das natürlich viel mehr Sinn.
 
So, dann will ich euch mal auf den neusten Stand bringen.

Ich habe mittlerweile sowohl unsere Backend-Anwendung zum Formatieren des Codes als auch das zugehörige Add-on, das letztlich per cURL den Beitragstext mit einem Code-Block rüberschickt und schließlich durch die formatierte Version austauscht, erfolgreich fertiggestellt und in Betrieb genommen.

Ich hoffe sehr, dass die Anwendung performant genug ist, sodass beim Speichern nicht eine allzu lange "Warteschleife" entsteht, wenn der Beitrag Code enthält.
Ansonsten bin ich gespannt, ob sonst irgendwelche Probleme entstehen oder ihr noch Feedback habt, wie man das Ganze verbessern könnte.
Der Code usw. ist jedenfalls öffentlich auf GitHub einsehbar.

Add-on: https://github.com/dev-community-de/dev-community-code-formatter-xenforo-addon
Backend: https://github.com/dev-community-de/code-formatter

Neben kleineren Optimierungen werden wir uns nun wahrscheinlich der Erweiterung durch weitere Sprachen widmen können.
@dominik hatte ja schon mit Go begonnen, wobei wir da leider noch das Problem haben, das ich noch nicht herausgefunden habe, wie man das Syntax-Highlighting sowie die Auswahl von Go-Code-Blocks im Forum ermöglichen kann.


Btw: Wir hatten ziemlich am Anfang ja mal die Problematik der Transportverschlüsselung, also die Ansteuerung der Anwendung per HTTPS, sodass der Code sowie die Beitragstexte nicht mitzulesen sind, angesprochen.
Das hat sich jetzt insofern erledigt, da bei den verwendeten Cloud-Dienst Google Cloud Run automatisch und kostenfrei ein Nginx-Proxy inkl. TLS-Zertifikat eingerichtet wird, worüber Anfragen entgegengenommen und dann an die Anwendung intern weitergeleitet werden.
Wir müssen uns also nicht mehr um eine entsprechende Konfiguration des Docker-Containers kümmern.
 
Kurztest bestanden :)
 
Ich mag die Längenbegrenzung nicht :(
Sollte man in mehrzeiligen Kommentaren mal in einer Zeile zu lang sein, werden alle Kommentarzeilen komplett neu zusammengebaut. Und zwar ohne Rücksicht darauf, wo man vorher absichtlich eine neue Zeile angefangen hatte. Die vorhergehende wird "aufgefüllt". Bekommt man zwar mit den üblichen // clang-format off und // clang-format on ein- und ausgeschaltet, stört aber. Und auch sonst wird Code umgebrochen, wo das im Normalfall noch nicht sinnvoll ist. Ich meine, Code im Smartphone vernünftig lesen zu wollen, ist von vorn herein grenzwertig. Ob man darauf nun tatsächlich Rücksicht nehmen sollte ... ?

Beispiel:
 
Ja, kann verstehen, dass das vielleicht stört.
Gibt es da ähnliche Meinungen von anderen?
Sollen wir die Längenbegrenzung also komplett entfernen?
Mir ist das letztendlich relativ gleich.
 
Ich würde da vielleicht erstmal ein größeres Limit versuchen, bevor man es ganz abstellt. Ich bin altmodisch, ich find Umbrüche irgendwie wichtig für die Lesbarkeit^^
 
Hmm, von mir aus. Also im Moment sind wir bei 80. Da waren wir uns eigentlich schon einig, dass das Quatsch ist. Mein 10" Netbook kommt problemlos auf ~125 Zeichen ohne scrollen zu müssen. Aber ein 10" Display ist immer noch jenseits von gut und böse. Wenn ich mich richtig erinnere hatten wir was von um die 150 Zeichen gesagt, oder?
 
Dann funktioniert das noch nicht. Bei 80 ist Schluss.
Live Test:
C:
// 456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
War:
Code:
// 456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789

Siehe auch den oben verlinkten Beitrag ...

// EDIT: Entferne mal die Single Quotes um die 180. Kann sein dass das sonst als String gewertet wird, somit invalid ist, und ein Fallback zu Default verursacht.
 
Zuletzt bearbeitet:
So, sollte jetzt ordnungsgemäß funktionieren.
Die .clang-format-Datei war schlicht im falschen Verzeichnis. :D

Habe zudem deine Einstellung ReflowComments: false übernommen.
 
Super! Hab gerade meinen Beitrag noch mal editiert, um zu sehen was passiert. Alles OK, auch ohne die Formatierung an bestimmten Stellen ausknipsen zu müssen. Passt so (y)
 
Ich dachte mir: "Ach.. clang kann ja auch andere Sprachen" (bei Version 7.x unter Anderem Java, JavaScript und ObjectiveC). Also deine Repo geklont, Container gebaut und ausprobiert.

Habe ein paar Probleme damit

Dateinamenserweiterungen
Clang benutzt die Dateierweiterung, um festzustellen, welcher Parser angewendet werden muss (wie viele andere Code-Formatierer auch). Weil die Sprachcodes der Codeblöcke im Backend als Dateierweiterung gesetzt werden, muss man clang-format bei Abweichungen vom Dateinamenstandard natürlich ein bisschen nachhelfen. Zum Beispiel heißt der Sprachcode für js hier javascript und für m objectivec usw. Das sollte auch so bleiben. Allerdings muss man dann dem Formatter sagen, womit er es zu tun hat.

Lösung A
Die Lösung bestünde darin, in PHP die Sprachcodes auf Standarderweiterungen zu mappen und dann per --assume-filename zu übergeben. Zum Beispiel so, wenn man das pro child-Klasse verwalten will (Achtung, nicht getestet) :
CodeFormatter:
private $lang;
protected function getLang(): string { return $this->lang; }

private function __construct(string $lang) { $this->lang = $lang; }

protected static function getCodeFormatterForLanguage(string $lang) : ?self {
    foreach (self::$code_formatters as $code_formatter) {
        if (self::supportsLanguage($code_formatter, $lang)) {
            return new $code_formatter($lang);
        }
    }
    return null;
}

ClangCodeFormatter extends CodeFormatter:
protected static $supported_languages = ['c','cpp','javascript','objectivec'];
protected static $extensions = ['javascript' => "js", 'objectivec' => "m"] ;

protected function getShellCommand(string $file) : string {
    $options = '';
    if(array_key_exists($this->getLang(), self::$extensions)) {
        $options = '--assume-filename=\'name.' . self::$extensions[$this->getLang()] . '\'';
    }
    return 'clang-format -style=file ' . $options . ' -i ' . $file;
}

Wenn diese Struktur vorliegt, könnte man zusätzlich pro Sprache eine eigene Formatierungsdatei anlegen (zum Beispiel js-clang-format.json) und ihren Inhalt direkt weitergeben als Parameter -style=<INHALT>.


Lösung B
Man kann ohne die obigen Codeänderungen die Erweiterungen in der .clang-format-Datei eintragen (getestet) :
.clang-format - gemeinsamer Block:
BasedOnStyle: LLVM
ColumnLimit: 180
ReflowComments: false

RawStringFormats:
  - Language: Cpp
    Delimiters:
      - c
      - C
      - cc
      - CC
      - cpp
      - Cpp
      - CPP
      - 'c++'
      - 'C++'

  - Language: Java
    Delimiters:
      - java
      - Java
      - JAVA

  - Language: JavaScript
    Delimiters:
      - js
      - JS
      - javascript
      - JavaScript
      - JAVASCRIPT

  - Language: ObjC
    Delimiters:
      - objectivec
      - Objectivec
      - ObjectiveC
      - OBJECTIVEC
      - m
      - M
      - mm
      - Mm
      - MM
      - boomshakalaka

Problem
Ich würde natürlich Lösung B bevorzugen, weil .clang-format die Standarddatei ist und es insgesamt weniger Codeänderungen erfordert. Aber ich kriege es nicht hin, clang-format mit obiger Struktur dazu zu bringen, sprachabhängige Formatierungen durchzuführen.

Habe es auch noch so probiert (am Beispiel von ObjectiveC) :
.clang-format - mit mehreren 'docs':
---
Language: Cpp
BasedOnStyle: LLVM
ColumnLimit: 180
ReflowComments: false
RawStringFormats:
  - Language: Cpp
    Delimiters:
      - c
      - C
      - cc
      - CC
      - cpp
      - Cpp
      - CPP
      - 'c++'
      - 'C++'

---
Language: ObjC
BasedOnStyle: LLVM
ColumnLimit: 50
MaxEmptyLinesToKeep: 0
RawStringFormats:
  - Language: ObjC
    Delimiters:
      - objectivec
      - Objectivec
      - ObjectiveC
      - OBJECTIVEC
      - m
      - M
      - mm
      - Mm
      - MM
      - boomshakalaka

---
Language: Java
RawStringFormats:
  - Language: Java
    Delimiters:
      - java
      - Java
      - JAVA

---
Language: JavaScript
BasedOnStyle: LLVM
ColumnLimit: 80
ReflowComments: false
MaxEmptyLinesToKeep: 10
RawStringFormats:
  - Language: JavaScript
    Delimiters:
      - js
      - JS
      - javascript
      - JavaScript
      - JAVASCRIPT

Ich möchte in diesem Beispiel also alle leeren Zeilen entfernen (die anderen Einstellungen und Sprachen ignorieren, die hatte ich auch noch getestet, wurde alles nicht von clang beachtet).

Ergebnis von POST (als Beispiel habe ich hier die beliebte Programmiersprache Boomshakalaka gewählt) :
POST-Ergebnis


Code:
[CODE=boomshakalaka]#import "RWTScaryBugData.h"



@implementation     RWTScaryBugData



@synthesize title =_title;

@synthesize rating =_rating;



- (id)initWithTitle:(NSString*)title rating:(float)rating {  if ((self = [super init])) {

    self.title = title;    self.rating = rating;

  }
                                                                                                              

                                                        

  return self;
}


@end
Code:
#import "RWTScaryBugData.h"



@implementation     RWTScaryBugData



@synthesize title =_title;

@synthesize rating =_rating;



- (id)initWithTitle:(NSString*)title rating:(float)rating {  if ((self = [super init])) {

    self.title = title;    self.rating = rating;

  }
                                                                                                              

                                                        

  return self;
}


@end
[/CODE]

Das zeigt mir, dass auf diese Weise offensichtlich Dateinamenserweiterungen übergeben werden können.. weil der Code formatiert wird. Aber er wird nicht mit sprachspezifischen Einstellungen formatiert.


Sonstiges
Ich hatte auch noch 2 weitere Formatter zum Laufen gebracht, für zusätzliche Sprachen..das poste ich aber glaube ich lieber in einem extra Thread, weil das sonst am Thema vorbeigeht.. ich verlinke das dann hier:

 
Zuletzt bearbeitet:
Vielen Dank für deine Überlegungen und Lösungsansätze, @Mat!

Das Problem mit den Dateinamenserweiterungen musste natürlich früher oder später auftauchen. Das war irgendwie klar.

Ich persönlich hatte schon mal etwas ähnliches wie Lösung A in Betracht gezogen, dies aber bisher aufgrund der, bei den bisher unterstützten Sprachen, noch nicht aufgetauchten Problematik nicht in die Tat umgesetzt.

Ich würde tatsächlich sagen, dass ich eher auf eine PHP-gestützte Lösung mit einem Mapping oder einem Austausch der Sprachcodes zu den eigentlichen File Extensions setzen würde.
Der Grund hierfür ist schlicht und ergreifend, dass dieser Ansatz vollkommen unabhängig von dem jeweiligen Formatter funktioniert.
Sollten wir also beispielsweise mal nicht mehr auf clang-format setzen wollen, müssten wir sonst wieder Änderungen vornehmen.
Das wäre bei einer Lösung in Richtung A nicht der Fall.

Genauer gesagt würde ich allerdings noch viel früher ansetzen und schon in der Klasse CodeFormatterApp eingreifen, wenn die Code-Language aus dem jeweiligen Code-Block extrahiert wird. -> https://github.com/dev-community-de/code-formatter/blob/master/app/CodeFormatterApp.php#L122

Dort ist schon jetzt ein Fallback für nicht definierte Sprachen hinterlegt, wobei die Code-Language dann einfach auf txt gesetzt wird, was ja die File Extension für eine Textdatei darstellt.

Durch ein einfaches Mapping nach deinem Vorbild könnten wir an dieser Stelle sicherstellen, dass die Sprache auch der File Extension entspricht, sodass der Code Formatter weiß, mit welcher Sprache er es zu tun hat.
 
Genauer gesagt würde ich allerdings noch viel früher ansetzen und schon in der Klasse CodeFormatterApp eingreifen, wenn die Code-Language aus dem jeweiligen Code-Block extrahiert wird. -> https://github.com/dev-community-de/code-formatter/blob/master/app/CodeFormatterApp.php#L122

Das hatte ich mir auch überlegt, aber dann gedacht, es wäre vielleicht schöner, wenn jeder Formatter seine eigenen Zuständigkeiten speichert. Allerdings ist deine Lösung sinnvoller, weil die Formatter-Klassen ja nicht 1:1 Sprachen abbilden, sondern eben nur das Formatting Tool.. die Dateinamenserweiterungen dagegen sind unabhängig davon.. da hatte ich nicht dran gedacht.

Aber ja.. wenn man das so macht, dann könnte man von der einheitlichen Config-Datei weg und würde die Styles für clang direkt aus sprachspezifischen Dateien laden. Der nimmt die Parameter als einzeiliges JSON entgegen:

-style='{key1: value1, key2: value2, ...}'

Siehe auch:

Wenn das nicht vorher jemand macht, dann würde ich so wie bei Prettier da auch so ab Juli reinschauen und das testen.
 
Aber ja.. wenn man das so macht, dann könnte man von der einheitlichen Config-Datei weg und würde die Styles für clang direkt aus sprachspezifischen Dateien laden. Der nimmt die Parameter als einzeiliges JSON entgegen:

-style='{key1: value1, key2: value2, ...}'

Siehe auch:

Wenn das nicht vorher jemand macht, dann würde ich so wie bei Prettier da auch so ab Juli reinschauen und das testen.

Jo, das wäre dann ggf. der nächste Schritt, um die Einstellungen spezifischer ausrichten zu können. :)
Auch hier: Vielleicht schau ich da im Voraus schon mal rein. Dann geb ich dir aber Bescheid.
 
Zurück
Oben Unten