Um verschachtelten Text in Klammern in einen Array-Baum umzuwandeln, durchlaufen Sie ihn von links nach rechts. Erstellen Sie für jede öffnende Klammer ein Array und kehren Sie bei jeder schließenden Klammer zu dessen übergeordnetem Array zurück. Diese Anleitung enthält einen vollständigen JavaScript-Parser, den Sie lokal in Safari auf einem Mac ausführen können.
Hier geht es darum, eine kleine, selbst verfasste Gliederung aus Bezeichnungen in verschachtelte Arrays umzuwandeln und dabei ihre Reihenfolge, Anführungszeichen und leeren Gruppen beizubehalten. Alle Beispiele verwenden künstliche Zeichenfolgen. Der Parser umfasst ausdrückliche Grammatikregeln und Prüfungen, damit Sie feststellen können, ob die resultierende Struktur Ihrer Absicht entspricht.
Festlegen, was Klammern und Bezeichnungen in Anführungszeichen bedeuten
Bevor Sie etwas umwandeln, unterscheiden Sie strukturelle Klammern von Klammern, die zu einer Bezeichnung gehören. In diesem Beispiel erzeugen die äußeren Klammern eine Gruppe, während die Klammern innerhalb der doppelt in Anführungszeichen gesetzten Bezeichnung Text bleiben:
(desk ("blue (round)" "say \"hello\"") () cards)
Die beabsichtigte Array-Struktur lautet:
[
[
"desk",
["blue (round)", "say \"hello\""],
[],
"cards"
]
]
Das zusätzliche äußere Array ist beabsichtigt: Es stellt das gesamte Dokument dar. Jede Klammergruppe wird zu einem Array darin. So bleibt der Rückgabetyp gleich, unabhängig davon, ob die Eingabe eine Gruppe, mehrere Gruppen oder Bezeichnungen ohne umschließende Klammern enthält.
Die folgenden Regeln definieren das Eingabeformat dieses Artikels. Es handelt sich um Designentscheidungen für diesen Parser und nicht um ein integriertes JavaScript-Format für Text in Klammern.
| Eingabemerkmal | Bedeutung im resultierenden Baum |
|---|---|
( und ) außerhalb doppelter Anführungszeichen |
Eine verschachtelte Array-Struktur beginnen und beenden |
| Leerraum außerhalb doppelter Anführungszeichen | Bezeichnungen trennen; Trennzeichen nicht beibehalten |
Ein nicht in Anführungszeichen gesetztes Token wie cards |
Eine Zeichenfolge erzeugen |
Eine doppelt in Anführungszeichen gesetzte Zeichenfolge wie "blue tile" |
Eine Zeichenfolge einschließlich ihrer internen Leerzeichen erzeugen |
() |
Ein leeres Array erzeugen |
"" |
Eine leere Zeichenfolge erzeugen |
12, true oder null außerhalb von Anführungszeichen |
Die Schreibweise als Zeichenfolge beibehalten |
| Kommas und einfache Anführungszeichen | Als gewöhnliche Zeichen in einem nicht in Anführungszeichen gesetzten Token behandeln |
Klammern trennen ebenfalls Tokens, daher wird a(b)c zu ["a", ["b"], "c"]. Zwei benachbarte Bezeichnungen benötigen Leerraum: "a"b wird zurückgewiesen. Wenn eine Bezeichnung Leerzeichen, doppelte Anführungszeichen oder wörtliche Klammern enthält, verwenden Sie die Form mit doppelten Anführungszeichen.
Tokens in doppelten Anführungszeichen folgen der JSON-Zeichenfolgensyntax. Der Parser übergibt jedes vollständige Token in Anführungszeichen an JSON.parse(), das JSON-Text akzeptiert und bei ungültiger Syntax einen SyntaxError auslöst. Dadurch wird die Escape-Prüfung an den dokumentierten Parser delegiert. ECMAScript-Spezifikation: JSON.parse
Einen lokalen JavaScript-Arbeitsbereich in Safari öffnen
Der Web Inspector von Safari stellt die JavaScript-Eingabeaufforderung bereit, die für diese Desktop-Umwandlung benötigt wird. Sie müssen weder ein Paket installieren noch ein separates Projekt anlegen.
- Wählen Sie in Safari
Safari→Einstellungen…→Erweitert. - Aktivieren Sie
Funktionen für Webentwickler anzeigen. - Öffnen Sie einen Tab und geben Sie
about:blankin das Adressfeld ein. - Wählen Sie
Entwickler→Web-Inspector einblendenoder drücken SieOption–Command–I. - Wählen Sie den Tab
Konsoleund verwenden Sie dessen Eingabeaufforderung.
WebKit dokumentiert die Entwicklereinstellung und die Tastenkombination für den Inspector unter Web Inspector aktivieren. Die Referenz Web-Inspector-Oberfläche erklärt, dass die Eingabeaufforderung der Konsole JavaScript ausführt und die Ergebnisse anzeigt.
Fügen Sie das gesamte folgende Programm in die Eingabeaufforderung ein und führen Sie es aus. Es enthält den Parser, ein selbst verfasstes Beispiel, sechs Prüfungen gültiger Eingaben und sieben Prüfungen fehlerhafter Eingaben. Importe sind nicht erforderlich. Das Programm wurde erfolgreich mit Node.js 20.19.2 ausgeführt; die Schritte für die Safari-Oberfläche sind dokumentiert, wurden für diesen Artikel aber nicht unabhängig ausprobiert.
Den vollständigen Parser für Text in Klammern ausführen
Der Parser verwaltet einen Stapel offener Arrays. Eine öffnende Klammer erstellt ein untergeordnetes Array und legt es auf den Stapel. Eine schließende Klammer entfernt das aktuelle Array vom Stapel. Bezeichnungen werden an das jeweils aktuell offene Array angehängt.
function parenthesizedToTree(text) {
if (typeof text !== "string") {
throw new TypeError("Expected a string");
}
const root = [];
const stack = [{ items: root, start: -1 }];
const separator = c => /\s|[()]/u.test(c);
let i = 0;
function fail(message, at = i) {
throw new SyntaxError(message + " at UTF-16 offset " + at);
}
while (i < text.length) {
const c = text[i];
const current = stack[stack.length - 1].items;
if (/\s/u.test(c)) {
i++;
} else if (c === "(") {
const child = [];
current.push(child);
stack.push({ items: child, start: i });
i++;
} else if (c === ")") {
if (stack.length === 1) fail("Unexpected closing parenthesis");
stack.pop();
i++;
} else if (c === '"') {
const start = i++;
let closed = false;
while (i < text.length) {
if (text[i] === "\\") {
i += 2;
} else if (text[i] === '"') {
i++;
closed = true;
break;
} else {
i++;
}
}
if (!closed) fail("Unclosed quoted string", start);
let value;
try {
value = JSON.parse(text.slice(start, i));
} catch {
fail("Invalid JSON string", start);
}
if (i < text.length && !separator(text[i])) {
fail("Expected whitespace or a parenthesis");
}
current.push(value);
} else {
const start = i;
while (i < text.length && !separator(text[i])) {
if (text[i] === '"') fail("Quote inside an unquoted token");
i++;
}
current.push(text.slice(start, i));
}
}
if (stack.length !== 1) {
fail("Unclosed opening parenthesis", stack[stack.length - 1].start);
}
return root;
}
const input = String.raw`(desk ("blue (round)" "say \"hello\"") () cards)`;
console.log(JSON.stringify(parenthesizedToTree(input), null, 2));
const validCases = [
["", []],
["()", [[]]],
["a(b)c", ["a", ["b"], "c"]],
['("a b" "")', [["a b", ""]]],
["(12 true)", [["12", "true"]]],
[String.raw`("a\\b" "\u0061" "$&")`, [["a\\b", "a", "$&"]]]
];
for (const [source, expected] of validCases) {
const actual = parenthesizedToTree(source);
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error("Unexpected tree for " + source);
}
}
const invalidCases = [
"(a", "a)", '("a)', String.raw`("\q")`,
'"a"b', 'a"b"', '("line\nbreak")'
];
for (const source of invalidCases) {
let rejected = false;
try {
parenthesizedToTree(source);
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
rejected = true;
}
if (!rejected) throw new Error("Expected rejection for " + source);
}
console.log("All 13 checks passed.");
Das erste ausgegebene Ergebnis ist die zuvor gezeigte verschachtelte Struktur, über mehrere Zeilen formatiert. Die abschließende Meldung sollte All 13 checks passed. lauten. Die Funktion gibt ein echtes JavaScript-Array zurück; JSON.stringify() wandelt diesen Wert in ausgebbaren Text um, und das dritte Argument legt die Einrückung fest. ECMAScript-Spezifikation: JSON.stringify
Um eine andere künstliche Gliederung umzuwandeln, ändern Sie nur das input zugewiesene Beispiel und führen Sie das Programm erneut aus. Lassen Sie die Prüfungen unverändert, damit sie weiterhin das beabsichtigte Verhalten des Parsers überprüfen. Kopieren Sie den ausgegebenen Array-Text in Ihr Arbeitsdokument, wenn Sie eine Textdarstellung benötigen.
Anführungszeichen und Escape-Semantik beibehalten
Zu berücksichtigen sind zwei Ebenen: der JavaScript-Quelltext, der das Beispiel enthält, und der zu analysierende Text in Klammern. Das Beispiel verwendet ein mit String.raw gekennzeichnetes Template-Literal, damit seine Backslashes den Parser erreichen, ohne auf der JavaScript-Quelltextebene durch die übliche Escape-Verarbeitung von Zeichenfolgen verändert zu werden. Der dokumentierte Vorgang liest die rohen Segmente des Template-Literals. ECMAScript-Spezifikation: String.raw
Lassen Sie die Backtick-Begrenzer um das bereitgestellte Beispiel stehen. Bei diesem einfachen Bearbeitungsablauf sollten Sie Beispieltext ohne Backticks oder den Template-Interpolationsöffner ${ verfassen, da diese in der JavaScript-Template-Syntax eine Bedeutung haben.
Innerhalb eines doppelt in Anführungszeichen gesetzten Eingabe-Tokens steht \" für ein wörtliches doppeltes Anführungszeichen, \\ für einen Backslash und \u0061 für a. Dabei handelt es sich um dekodierte Zeichenfolgenwerte: Der Baum behält die Bedeutung der Bezeichnung bei, während die ursprüngliche Schreibweise der Escape-Sequenz und die umgebenden Anführungszeichen verworfen werden. Außerhalb doppelter Anführungszeichen behandelt der benutzerdefinierte Parser Backslashes als gewöhnliche Token-Zeichen.
Beispielsweise bestätigt die letzte Prüfung gültiger Eingaben, dass die Eingabe-Tokens "a\\b", "\u0061" und "$&" zu einer Bezeichnung mit einem Backslash, der Bezeichnung a und der wörtlichen Bezeichnung $& werden. Wenn Sie die ursprünglichen Leerzeichen und die Schreibweise der Escape-Sequenzen exakt reproduzieren müssen, reicht ein Array dekodierter Zeichenfolgen nicht aus; die Darstellung müsste zusätzlich den Quelltext oder Token-Positionen enthalten.
Warum eine pauschale Ersetzung das Ergebnis verändert
Jede öffnende Klammer durch eine eckige Klammer und jede schließende Klammer durch eine andere eckige Klammer zu ersetzen, setzt diese Grammatik nicht um. Dabei würden auch die Klammern in "blue (round)" verändert, und nicht in Anführungszeichen gesetzte Bezeichnungen sowie fehlende Elementtrennzeichen blieben ungelöst. Diese Schlussfolgerung ergibt sich direkt aus dem selbst verfassten Beispiel.
Bei Ersetzungen gibt es noch ein relevantes Detail: Ein Ersetzungsargument in Form einer Zeichenfolge interpretiert Sondersequenzen wie $&, das den gefundenen Text einfügt. Eine Ersetzungsfunktion gibt ihren zurückgegebenen Text ohne diesen Ersetzungsschritt aus. ECMAScript-Spezifikation: replaceAll und Ersetzungssubstitution
Dieses unabhängige Beispiel veranschaulicht den Unterschied:
const label = "tile";
console.log(label.replaceAll("tile", "$&")); // tile
console.log(label.replaceAll("tile", () => "$&")); // $&
Beide Ergebnisse wurden während der Vorbereitung überprüft. Der Baumanalysator vermeidet die Ersetzungssemantik vollständig: Er hängt dekodierte Zeichenfolgen direkt an Arrays an. Wenn Sie später eine Umbenennung von Bezeichnungen hinzufügen, führen Sie Ersetzungen an den analysierten Bezeichnungen durch und entscheiden Sie ausdrücklich, ob der Ersetzungstext interpretiert oder wörtlich eingefügt werden soll.
Unerwartete Ausgaben diagnostizieren, bevor Sie den Baum weiterverwenden
Die enthaltenen Prüfungen dienen als kompakte Entscheidungshilfe. Sie decken leere Eingaben, leere Gruppen, benachbarte Gruppen und Bezeichnungen, Leerzeichen in Anführungszeichen, reine Zeichenfolgenwerte, Escape-Sequenzen und fehlerhafte Syntax ab. Verwenden Sie die folgenden Symptome, um eine Abweichung zwischen Ihrer beabsichtigten Gliederung und der festgelegten Grammatik zu finden.
| Symptom | Erklärung und nächste Maßnahme |
|---|---|
| Das Ergebnis enthält ein zusätzliches äußeres Array | Die Wurzel stellt das Dokument dar. Behalten Sie sie bei, sofern der nächste Schritt nicht ausdrücklich genau eine Gruppe auf oberster Ebene erfordert. |
| Eine Wortgruppe wird zu mehreren Zeichenfolgen | Leerzeichen trennen nicht in Anführungszeichen gesetzte Bezeichnungen. Setzen Sie die Wortgruppe in doppelte Anführungszeichen. |
| Ein Komma bleibt in einer Bezeichnung | Kommas sind hier gewöhnliche Zeichen. Verwenden Sie Leerzeichen zwischen getrennten Bezeichnungen. |
| Klammern werden unerwartet zu verschachtelten Arrays | Setzen Sie wörtliche Klammern innerhalb einer doppelt in Anführungszeichen gesetzten Bezeichnung. |
Unexpected closing parenthesis |
Einer schließenden Klammer fehlt die passende öffnende Klammer. Prüfen Sie die angegebene Position. |
Unclosed opening parenthesis |
Am Ende ist noch eine Gruppe offen. Die Position kennzeichnet die innerste nicht geschlossene öffnende Klammer. |
Invalid JSON string |
Prüfen Sie das Token in Anführungszeichen auf eine ungültige Escape-Sequenz oder ein nicht maskiertes Steuerzeichen. |
| Ein Trennzeichenfehler im Zusammenhang mit Anführungszeichen | Trennen Sie benachbarte Bezeichnungen durch Leerraum oder schließen Sie die gesamte gewünschte Bezeichnung in eine Zeichenfolge in Anführungszeichen ein. |
Die Offsets in dieser Implementierung beginnen bei null und zählen UTF-16-Codeeinheiten, entsprechend der JavaScript-Zeichenfolgenindizierung. Sie sind weder Zeilennummern noch eine Anzahl sichtbarer Zeichen. Bei Validierungsfehlern in Tokens in Anführungszeichen bezeichnet die angegebene Position den Anfang des Tokens; prüfen Sie daher die gesamte Bezeichnung in Anführungszeichen.
Fehlerhafte Eingaben lösen eine Ausnahme aus, bevor die Funktion etwas zurückgibt. Obwohl Arrays beim Durchlaufen intern zusammengesetzt werden, erhalten Aufrufer keinen unvollständigen Baum. Der Parser verwendet einen expliziten Stapel statt rekursiver Funktionsaufrufe; das Beispiel legt jedoch weder eine maximal unterstützte Dokumentgröße noch eine maximale Verschachtelungstiefe fest.
Für eine kleine Desktop-Gliederung besteht der praktische Ablauf darin, sich auf die Grammatik zu einigen, das unveränderte Beispiel und die Prüfungen auszuführen und anschließend den selbst verfassten Text einzusetzen. Prüfen Sie Anführungszeichen, leere Gruppen und die Dokumentwurzel, bevor Sie das Ergebnis weiterverwenden; diese drei Details bestimmen, ob der Array-Baum die beabsichtigte Struktur beibehält.
Quellen
- Web Inspector aktivieren — WebKit.
- Web-Inspector-Oberfläche — WebKit.
- ECMAScript®-Sprachspezifikation 2027: Strukturierte Daten — Ecma International / TC39, laufender Entwurf.
- ECMAScript®-Sprachspezifikation 2027: Textverarbeitung — Ecma International / TC39, laufender Entwurf.