To extract assignments such as title = "Blue = Green", match the key and the complete quoted value together. This Mac walkthrough uses JavaScript in Safari to preserve internal equals signs, quotes and backslashes while reporting malformed lines.
Why does splitting on equals signs lose the value?
Consider this synthetic assignment:
title = "Blue = Green"
The intended key is title; the value, including its original quotes, is "Blue = Green". Only the first equals sign separates the fields. The second belongs to the quoted text.
JavaScript’s split("=") divides at each occurrence without interpreting quotes. Adding a limit, as in split("=", 2), truncates the resulting array; it does not preserve everything after the first separator. This follows the standard’s definition of String.prototype.split.
The right method depends on the input’s grammar. Use this decision aid before changing the text:
| Input condition | Appropriate approach |
|---|---|
| One assignment per line; only separation matters | Locate the first equals sign and keep the entire remainder. |
| One assignment per line; quotes and key syntax must also be checked | Match the complete line with a defined assignment pattern, as below. |
| Several assignments per line or values spanning lines | Use a parser designed for those additional rules. |
For this task, complete-line validation gives a useful extra check: an unmatched quote or unexpected trailing text produces an error instead of an incomplete extraction.
What assignment grammar does this example preserve?
The program below implements a deliberately small text format. These are its authored rules, rather than a claim that it parses every JavaScript assignment:
- Each nonblank line contains one assignment.
- Keys start with an ASCII letter or underscore, followed by ASCII letters, digits or underscores.
- Values use matching straight single quotes or straight double quotes. Empty quoted values are valid.
- Within a value, a backslash pairs with the next character, except a carriage return or newline. Both characters remain unchanged.
- Spaces and tabs may surround the key, separator and value. Spaces and tabs inside quotes remain part of the value.
- Physical line endings may be LF, CRLF or CR. Quoted values cannot cross those line endings.
- Comments, semicolons and unquoted values are outside this grammar.
For example, caption='Small = calm' is valid. So is note="Use \"a=b\" here": the escaped double quotes belong to the value. By contrast, caption='Small = calm" has mismatched delimiters and must be corrected.
The output stores each value as rawValue, including its enclosing quotes. Extraction does not decode escape sequences: the two characters \n stay two characters. This makes the result suitable for rearranging assignment text without changing its written representation.
Duplicate keys also remain separate entries in their original order. That choice avoids silently replacing an earlier value when the same key appears again.
How do you run the transformation in Safari on a Mac?
Safari’s Web Inspector provides a place to evaluate JavaScript. Enable it through Safari → Settings… → Advanced → Show features for web developers. Then choose Develop → Show Web Inspector, or press Option-Command-I. These steps are documented in WebKit’s Enabling Web Inspector.
Select the Console tab and use its input prompt for the complete program below. WebKit documents the prompt as an input field for evaluating JavaScript in its Web Inspector Interface reference. The example operates only on the authored string inside the program.
The sample uses String.raw so its backslashes survive JavaScript string construction. When editing the sample, remember that backticks and ${...} still have template-literal meaning; this input contains neither. See the JavaScript String.raw() documentation before adding those characters.
(() => {
const source = String.raw`title = "Blue = Green"
caption='Small = calm'
note = "Use \"a=b\" here"
marker = "$1 = $$"
empty = ""`;
const assignment = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*("(?:\\[^\r\n]|[^"\\\r\n])*"|'(?:\\[^\r\n]|[^'\\\r\n])*')[ \t]*$/;
function extractAssignments(text) {
const pairs = [];
const errors = [];
const lines = text.split(/\r\n|\n|\r/);
lines.forEach((line, index) => {
if (/^[ \t]*$/.test(line)) return;
const match = assignment.exec(line);
if (!match || match[0].length !== line.length) {
errors.push(`Line ${index + 1}: expected key = quoted value`);
return;
}
pairs.push({ key: match[1], rawValue: match[2] });
});
if (errors.length) throw new Error(errors.join("\n"));
return pairs;
}
const pairs = extractAssignments(source);
const extracted = pairs
.map(({ key, rawValue }) => `key: ${key}\nvalue: ${rawValue}`)
.join("\n\n");
console.log(extracted);
const normalized = source.split(/\r\n|\n|\r/)
.map(line => line.replace(assignment,
(_whole, key, rawValue) => key + " = " + rawValue))
.join("\n");
console.log("Normalized assignments:\n" + normalized);
})();
This is a complete program with sample data and no imports or additional packages. Its surrounding function keeps the declarations local, allowing the whole example to be run again after editing the sample.
The first console message separates the fields:
key: title
value: "Blue = Green"
key: caption
value: 'Small = calm'
key: note
value: "Use \"a=b\" here"
key: marker
value: "$1 = $$"
key: empty
value: ""
The second message provides assignments with consistent spacing:
Normalized assignments:
title = "Blue = Green"
caption = 'Small = calm'
note = "Use \"a=b\" here"
marker = "$1 = $$"
empty = ""
Select and copy whichever text block fits the desktop task. The program displays results through console.log, which WebKit documents in its Console Object API. It does not write a file.
How does the pattern keep quoted equals signs intact?
The pattern has two capturing groups: one for the key and one for the complete quoted value. Its internal (?:...) groups organize alternatives without creating additional captured fields. This use of capturing and noncapturing groups follows the JavaScript groups documentation.
After matching the key, the pattern consumes the assignment separator once. It then chooses either the double-quoted branch or the single-quoted branch. Each branch accepts ordinary characters—including equals signs—or a backslash paired with a following character. An unescaped matching quote closes that branch.
This structure handles escaped quotes without treating them as the end of the value. It also handles paired backslashes: after a pair is consumed together, a following unescaped quote can close the string. Empty values work because the quoted contents may repeat zero times.
The program reads the captures through match[1] and match[2]. A failed exec() call returns null, as specified by ECMAScript’s RegExp.prototype.exec. The additional length comparison requires the match to cover the entire physical line.
Only spaces and tabs count as surrounding formatting. The program never trims the inside of a value. Consequently, label = " Blue = Green " retains both spaces at each end of the quoted contents.
Why use a replacement function when reformatting?
The normalization step returns the captured key, a standardized separator and the captured value. Its replacement callback receives the complete match first, followed by the two captures. JavaScript documents this argument order in String.prototype.replace().
A callback’s returned text is inserted literally, without interpreting replacement tokens such as $1 or $$. That keeps the sample value "$1 = $$" intact. The same documentation explains this distinction between replacement strings and replacement functions.
A fixed replacement template such as "$1 = $2" would also work for this particular pattern: captured contents are inserted without a second substitution pass. The callback becomes especially useful when assembling replacement text dynamically, because it keeps the returned value separate from replacement-template syntax.
Normalization intentionally changes surrounding spacing and converts physical line endings to LF. It preserves blank lines and the exact quoted value text. If the task requires the original spacing and line endings, use the extracted pairs for inspection and keep the original source unchanged.
What should you check before using the result?
The extraction function gathers line-numbered errors and throws before either result is printed. Blank lines containing only spaces or tabs are explicitly ignored. A malformed nonblank line therefore cannot disappear into an apparently successful partial result.
Use these synthetic cases to diagnose unexpected input:
| Assignment text | Expected result |
|---|---|
x="a=b=c" |
Preserve the entire quoted value. |
x='' |
Accept an empty single-quoted value. |
x=" a=b " |
Preserve the spaces inside quotes. |
x="a\"=b" |
Preserve the escaped quote and equals sign. |
x="a=b |
Report the missing closing quote. |
x="a" extra |
Report trailing text. |
x="a" y="b" |
Reject multiple assignments on one line. |
x=a=b |
Reject an unquoted value under this grammar. |
The program’s extraction and normalization logic was executed in Node.js 20.19.2 with the shown sample. Fifteen additional deterministic checks covered quoted equals signs, empty values, escaped quotes, paired backslashes, literal dollar tokens, malformed lines, duplicate keys and blank-line handling. That verifies the authored transformation logic; it is not a claim that the Safari interface was exercised.
If a line fails, first check its delimiters, then look for a final unpaired backslash, trailing text or an unsupported key. Change the grammar deliberately when the task needs more syntax. For the stated one-assignment-per-line format, the result is a reusable pair of fields whose quoted value retains every internal equals sign.
Sources
- Enabling Web Inspector — WebKit.
- Web Inspector Interface — WebKit.
- Console Object API — WebKit.
- ECMAScript® 2027 Language Specification — Text Processing — Ecma International / TC39, living draft.
- String.raw() — MDN Web Docs.
- Groups and backreferences — MDN Web Docs.
- String.prototype.replace() — MDN Web Docs.