Repository: nodeca/js-yaml (js)

Issue:
Support encoding emojis without encoding as surrogate pairs
Currently, if an emoji is in a YAML string, and run through `safeDump`, it will be converted into escaped surrogate pairs (i.e. `thiskey: "😀"` is dumped as `thiskey: "\uD83D\uDE00"`). This is caused by [lib/js-yaml/dumper.js#L468](https://github.com/nodeca/js-yaml/blob/bd32506/lib/js-yaml/dumper.js#L468), as using JS `charCodeAt` returns surrogate pairs by default (see https://mathiasbynens.be/notes/javascript-unicode). Would you be willing to at least make this configurable, so that we could choose whether to convert into surrogate pairs or write the emojis directly? If you want a PR, I can help with that as well, just wanted to see what your thoughts were.

Pull request description:
Combine surrogate pairs into one escape sequence when dumping.
When an astral character is encountered while dumping to a double-quoted string, save it as an 32-bit escaped Unicode sequence instead of as a surrogate pair (`\U0001F600` instead of `\uD83D\uDE00`).

Closes #368.

Patch:
diff --git a/README.md b/README.md
index cf4831a..cf73ebd 100644
--- a/README.md
+++ b/README.md
@@ -182,7 +182,7 @@ options:
 - `noRefs` _(default: `false`)_ - if `true`, don't convert duplicate objects into references
 - `noCompatMode` _(default: `false`)_ - if `true` don't try to be compatible with older
   yaml versions. Currently: don't quote "yes", "no" and so on, as required for YAML 1.1
-- `condenseFlow` _(default: `false`)_ - if `true` flow sequences will be condensed, omitting the space between `key: value` or `a, b`. Eg. `'[a,b]'` or `{a:{b:c}}`. Can be useful when using yaml for pretty URL query params as spaces are %-encoded.
+- `condenseFlow` _(default: `false`)_ - if `true` flow sequences will be condensed, omitting the space between `a, b`. Eg. `'[a,b]'`, and omitting the space between `key: value` and quoting the key. Eg. `'{"a":b}'` Can be useful when using yaml for pretty URL query params as spaces are %-encoded.
 
 The following table show availlable styles (e.g. "canonical",
 "binary"...) available for each tag (.e.g. !!null, !!int ...). Yaml
diff --git a/lib/js-yaml/dumper.js b/lib/js-yaml/dumper.js
index 6e60bbd..025b185 100644
--- a/lib/js-yaml/dumper.js
+++ b/lib/js-yaml/dumper.js
@@ -461,11 +461,21 @@ function foldLine(line, width) {
 // Escapes a double-quoted string.
 function escapeString(string) {
   var result = '';
-  var char;
+  var char, nextChar;
   var escapeSeq;
 
   for (var i = 0; i < string.length; i++) {
     char = string.charCodeAt(i);
+    // Check for surrogate pairs (reference Unicode 3.0 section "3.7 Surrogates").
+    if (char >= 0xD800 && char <= 0xDBFF/* high surrogate */) {
+      nextChar = string.charCodeAt(i + 1);
+      if (nextChar >= 0xDC00 && nextChar <= 0xDFFF/* low surrogate */) {
+        // Combine the surrogate pair and store it escaped.
+        result += encodeHex((char - 0xD800) * 0x400 + nextChar - 0xDC00 + 0x10000);
+        // Advance index one extra since we already used that char here.
+        i++; continue;
+      }
+    }
     escapeSeq = ESCAPE_SEQUENCES[char];
     result += !escapeSeq && isPrintable(char)
       ? string[i]
@@ -531,7 +541,7 @@ function writeFlowMapping(state, level, object) {
       pairBuffer;
 
   for (index = 0, length = objectKeyList.length; index < length; index += 1) {
-    pairBuffer = '';
+    pairBuffer = state.condenseFlow ? '"' : '';
 
     if (index !== 0) pairBuffer += ', ';
 
@@ -544,7 +554,7 @@ function writeFlowMapping(state, level, object) {
 
     if (state.dump.length > 1024) pairBuffer += '? ';
 
-    pairBuffer += state.dump + ':' + (state.condenseFlow ? '' : ' ');
+    pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' ');
 
     if (!writeNode(state, level, objectValue, false, false)) {
       continue; // Skip this pair because of invalid value.


Test patch:
diff --git a/test/issues/0346.js b/test/issues/0346.js
index 7b3ebed..ccd75d5 100644
--- a/test/issues/0346.js
+++ b/test/issues/0346.js
@@ -5,15 +5,21 @@ var yaml = require('../../');
 
 
 test('should not emit spaces in arrays in flow mode between entries using condenseFlow: true', function () {
+  var array = [ 'a', 'b' ];
+  var dumpedArray = yaml.dump(array, { flowLevel: 0, indent: 0, condenseFlow: true });
   assert.equal(
-    yaml.dump([ 'a', 'b' ], { flowLevel: 0, indent: 0, condenseFlow: true }),
+    dumpedArray,
     '[a,b]\n'
   );
+  assert.deepEqual(yaml.load(dumpedArray), array);
 });
 
-test('should not emit spaces between key: value in objects in flow sequence using condenseFlow: true', function () {
+test('should not emit spaces between key: value and quote keys using condenseFlow: true', function () {
+  var object = { a: { b: 'c' } };
+  var objectDump = yaml.dump(object, { flowLevel: 0, indent: 0, condenseFlow: true });
   assert.equal(
-    yaml.dump({ a: { b: 'c' } }, { flowLevel: 0, indent: 0, condenseFlow: true }),
-    '{a:{b:c}}\n'
+    objectDump,
+    '{"a":{"b":c}}\n'
   );
+  assert.deepEqual(yaml.load(objectDump), object);
 });