Skip to content

Commit 6b4a44c

Browse files
committed
Use previous grapheme cluster to handle Unicode chars
1 parent 7c99cbd commit 6b4a44c

5 files changed

Lines changed: 51 additions & 45 deletions

File tree

lib/node_modules/@stdlib/string/remove-last/lib/remove_last.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
// MODULES //
2222

2323
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
24+
var prevGraphemeClusterBreak = require( '@stdlib/string/prev-grapheme-cluster-break' );
2425

2526

2627
// MAIN //
@@ -55,7 +56,7 @@ function removeLast( str ) {
5556
if ( str === '' ) {
5657
return '';
5758
}
58-
return str.substring( 0, str.length-1 );
59+
return str.substring( 0, prevGraphemeClusterBreak( str, str.length-1 ) + 1 ); // eslint-disable-line max-len
5960
}
6061

6162

lib/node_modules/@stdlib/string/remove-last/test/test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,24 @@ tape( 'the function removes the last character of a given string', function test
7878

7979
t.end();
8080
});
81+
82+
tape( 'the function removes the last character of a given string (Unicode characters)', function test( t ) {
83+
var out;
84+
85+
out = removeLast( '😀😀😀' );
86+
t.strictEqual( out, '😀😀', 'removes character' );
87+
88+
out = removeLast( '🤖 Robot Army 🤖' );
89+
t.strictEqual( out, '🤖 Robot Army ', 'removes character' );
90+
91+
out = removeLast( 'अनुच्छेद' );
92+
t.strictEqual( out, 'अनुच्छे', 'returns expected value' );
93+
94+
out = removeLast( '六书/六書' );
95+
t.strictEqual( out, '六书/六', 'returns expected value' );
96+
97+
out = removeLast( '🌷' );
98+
t.strictEqual( out, '', 'returns expected value' );
99+
100+
t.end();
101+
});

lib/node_modules/@stdlib/string/reverse/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ out = reverseString( 'Lorem ipsum 𝌆 dolor sit ameͨ͆t.' );
5151

5252
<section class="notes">
5353

54-
## Notes
55-
56-
- While Unicode aware, the function does not currently support extended grapheme clusters.
57-
5854
</section>
5955

6056
<!-- /.notes -->

lib/node_modules/@stdlib/string/reverse/lib/main.js

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,8 @@
2020

2121
// MODULES //
2222

23+
var prevGraphemeClusterBreak = require( '@stdlib/string/prev-grapheme-cluster-break' );
2324
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
24-
var replace = require( '@stdlib/string/replace' );
25-
26-
27-
// VARIABLES //
28-
29-
var RE_COMBINING_MARKS = /([\0-\u02FF\u0370-\u1AAF\u1B00-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uE000-\uFE1F\uFE30-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])([\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]+)/g; // eslint-disable-line no-misleading-character-class
30-
var RE_SURROGATE_PAIR = /([\uD800-\uDBFF])([\uDC00-\uDFFF])/g;
31-
32-
33-
// FUNCTIONS //
34-
35-
/**
36-
* Callback invoked for swapping symbols with their combining marks.
37-
*
38-
* @private
39-
* @param {string} match - match
40-
* @param {string} p1 - first capture group
41-
* @param {string} p2 - second capture group
42-
* @returns {string} updated string
43-
*/
44-
function onCombiningMark( match, p1, p2 ) {
45-
// Reverse combined marks in order for the marks to be in the same order after subsequent reversing...
46-
return reverseString( p2 ) + p1;
47-
}
4825

4926

5027
// MAIN //
@@ -57,45 +34,46 @@ function onCombiningMark( match, p1, p2 ) {
5734
* @returns {string} reversed string
5835
*
5936
* @example
60-
* var out = reverseString( 'last man standing' );
37+
* var out = reverse( 'last man standing' );
6138
* // returns 'gnidnats nam tsal'
6239
*
6340
* @example
64-
* var out = reverseString( 'presidential election' );
41+
* var out = reverse( 'presidential election' );
6542
* // returns 'noitcele laitnediserp'
6643
*
6744
* @example
68-
* var out = reverseString( 'javaScript' );
45+
* var out = reverse( 'javaScript' );
6946
* // returns 'tpircSavaj'
7047
*
7148
* @example
72-
* var out = reverseString( 'Hidden Treasures' );
49+
* var out = reverse( 'Hidden Treasures' );
7350
* // returns 'serusaerT neddiH'
7451
*/
75-
function reverseString( str ) {
52+
function reverse( str ) {
7653
var out;
54+
var brk;
55+
var idx;
7756
var i;
7857
if ( !isString( str ) ) {
7958
throw new TypeError( 'invalid argument. First argument must be a string primitive. Value: `' + str + '`.' );
8059
}
8160
if ( str === '' ) {
8261
return '';
8362
}
84-
// Swap symbols with their combing marks:
85-
str = replace( str, RE_COMBINING_MARKS, onCombiningMark );
86-
87-
// Swap high and low surrogates so that low surrogates appear first:
88-
str = replace( str, RE_SURROGATE_PAIR, '$2$1' );
8963

90-
// Reverse the string code units...
9164
out = [];
92-
for ( i = str.length-1; i >= 0; i-- ) {
93-
out.push( str.charAt( i ) );
65+
idx = str.length - 1;
66+
while ( idx >= 0 ) {
67+
brk = prevGraphemeClusterBreak( str, idx );
68+
for ( i=brk+1; i<=idx; i++ ) {
69+
out.push( str.charAt( i ) );
70+
}
71+
idx = brk;
9472
}
9573
return out.join( '' );
9674
}
9775

9876

9977
// EXPORTS //
10078

101-
module.exports = reverseString;
79+
module.exports = reverse;

lib/node_modules/@stdlib/string/reverse/test/test.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,12 @@ tape( 'the function reverses a string', function test( t ) {
8282
'foo\uD834bar\uDF06baz', // unpaired surrogates
8383
'foo\uD834\uDF06\u0303bar', // astral symbol (surrogate pair) followed by a single combining mark
8484
'foo\uD834\uDF06\u0303\u035C\u035D\u035Ebar', // astral symbol (surrogate pair) followed by multiple combining marks
85-
'H\u0339\u0319\u0326\u032E\u0349\u0329\u0317\u0317\u0367\u0307\u030F\u030A\u033EE\u0368\u0346\u0352\u0306\u036E\u0303\u034F\u0337\u032E\u0323\u032B\u0324\u0323 \u0335\u031E\u0339\u033B\u0300\u0309\u0313\u036C\u0351\u0361\u0345C\u036F\u0302\u0350\u034F\u0328\u031B\u0354\u0326\u031F\u0348\u033BO\u031C\u034E\u034D\u0359\u035A\u032C\u031D\u0323\u033D\u036E\u0350\u0357\u0300\u0364\u030D\u0300\u0362M\u0334\u0321\u0332\u032D\u034D\u0347\u033C\u031F\u032F\u0326\u0309\u0312\u0360\u1E1A\u031B\u0319\u031E\u032A\u0317\u0365\u0364\u0369\u033E\u0351\u0314\u0350\u0345\u1E6E\u0334\u0337\u0337\u0317\u033C\u034D\u033F\u033F\u0313\u033D\u0350H\u0319\u0319\u0314\u0304\u035C'
85+
'H\u0339\u0319\u0326\u032E\u0349\u0329\u0317\u0317\u0367\u0307\u030F\u030A\u033EE\u0368\u0346\u0352\u0306\u036E\u0303\u034F\u0337\u032E\u0323\u032B\u0324\u0323 \u0335\u031E\u0339\u033B\u0300\u0309\u0313\u036C\u0351\u0361\u0345C\u036F\u0302\u0350\u034F\u0328\u031B\u0354\u0326\u031F\u0348\u033BO\u031C\u034E\u034D\u0359\u035A\u032C\u031D\u0323\u033D\u036E\u0350\u0357\u0300\u0364\u030D\u0300\u0362M\u0334\u0321\u0332\u032D\u034D\u0347\u033C\u031F\u032F\u0326\u0309\u0312\u0360\u1E1A\u031B\u0319\u031E\u032A\u0317\u0365\u0364\u0369\u033E\u0351\u0314\u0350\u0345\u1E6E\u0334\u0337\u0337\u0317\u033C\u034D\u033F\u033F\u0313\u033D\u0350H\u0319\u0319\u0314\u0304\u035C',
86+
'六书/六書',
87+
'กิิก้้ก็็ก็็กิิก้้ก็็กิิก้้กิิก้้ก็็ก็็กิิก้้ก็็กิิก้้',
88+
'\u{1112}\u{1161}\u{11AB}',
89+
'\u{D55C}',
90+
'เ❄︎நி'
8691
];
8792

8893
expected = [
@@ -97,7 +102,12 @@ tape( 'the function reverses a string', function test( t ) {
97102
'zab\uDF06rab\uD834oof',
98103
'rab\uD834\uDF06\u0303oof',
99104
'rab\uD834\uDF06\u0303\u035C\u035D\u035Eoof',
100-
'H\u0319\u0319\u0314\u0304\u035C\u1E6E\u0334\u0337\u0337\u0317\u033C\u034D\u033F\u033F\u0313\u033D\u0350\u1E1A\u031B\u0319\u031E\u032A\u0317\u0365\u0364\u0369\u033E\u0351\u0314\u0350\u0345M\u0334\u0321\u0332\u032D\u034D\u0347\u033C\u031F\u032F\u0326\u0309\u0312\u0360O\u031C\u034E\u034D\u0359\u035A\u032C\u031D\u0323\u033D\u036E\u0350\u0357\u0300\u0364\u030D\u0300\u0362C\u036F\u0302\u0350\u034F\u0328\u031B\u0354\u0326\u031F\u0348\u033B \u0335\u031E\u0339\u033B\u0300\u0309\u0313\u036C\u0351\u0361\u0345E\u0368\u0346\u0352\u0306\u036E\u0303\u034F\u0337\u032E\u0323\u032B\u0324\u0323H\u0339\u0319\u0326\u032E\u0349\u0329\u0317\u0317\u0367\u0307\u030F\u030A\u033E'
105+
'H\u0319\u0319\u0314\u0304\u035C\u1E6E\u0334\u0337\u0337\u0317\u033C\u034D\u033F\u033F\u0313\u033D\u0350\u1E1A\u031B\u0319\u031E\u032A\u0317\u0365\u0364\u0369\u033E\u0351\u0314\u0350\u0345M\u0334\u0321\u0332\u032D\u034D\u0347\u033C\u031F\u032F\u0326\u0309\u0312\u0360O\u031C\u034E\u034D\u0359\u035A\u032C\u031D\u0323\u033D\u036E\u0350\u0357\u0300\u0364\u030D\u0300\u0362C\u036F\u0302\u0350\u034F\u0328\u031B\u0354\u0326\u031F\u0348\u033B \u0335\u031E\u0339\u033B\u0300\u0309\u0313\u036C\u0351\u0361\u0345E\u0368\u0346\u0352\u0306\u036E\u0303\u034F\u0337\u032E\u0323\u032B\u0324\u0323H\u0339\u0319\u0326\u032E\u0349\u0329\u0317\u0317\u0367\u0307\u030F\u030A\u033E',
106+
'書六/书六',
107+
'ก้้กิิก็็ก้้กิิก็็ก็็ก้้กิิก้้กิิก็็ก้้กิิก็็ก็็ก้้กิิ',
108+
'\u{1112}\u{1161}\u{11AB}',
109+
'\u{D55C}',
110+
'நி❄︎เ'
101111

102112
];
103113
for ( i = 0; i < values.length; i++ ) {

0 commit comments

Comments
 (0)