jenkins-bot has submitted this change and it was merged.

Change subject: Use grapheme clusters in unicodeJS.TextString
......................................................................


Use grapheme clusters in unicodeJS.TextString

unicodejs.js:
* add splitClusters(text) and splitCharacters(text) methods

unicodejs.textstring.js:
* change internal representation from a char string to a list of grapheme
  clusters

unicodejs.wordbreak.js:
* change getGroup to work on the first character of a grapheme cluster

ve.js:
* Use new unicodejs.splitClusters function

Bug: 48975
Change-Id: I202b98199d2780534d1e02519b72579ba796f08f
---
M .docs/categories.json
M VisualEditor.php
M modules/unicodejs/AUTHORS.txt
M modules/unicodejs/unicodejs.js
M modules/unicodejs/unicodejs.textstring.js
M modules/unicodejs/unicodejs.wordbreak.js
M modules/unicodejs/unicodejs.wordbreak.test.js
M modules/ve/ve.js
8 files changed, 125 insertions(+), 32 deletions(-)

Approvals:
  Esanders: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/.docs/categories.json b/.docs/categories.json
index bd252a3..664ace6 100644
--- a/.docs/categories.json
+++ b/.docs/categories.json
@@ -169,7 +169,7 @@
                        {
                                "name": "UnicodeJS",
                                "classes": [
-                                       "unicodeJS.TextString", 
"unicodeJS.wordbreak"
+                                       "unicodeJS", "unicodeJS.TextString", 
"unicodeJS.wordbreak"
                                ]
                        }
                ]
diff --git a/VisualEditor.php b/VisualEditor.php
index 8e931d3..52833c6 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -196,6 +196,9 @@
                'debugScripts' => array(
                        've/ve.debug.js',
                ),
+               'dependencies' => array(
+                       'unicodejs.wordbreak',
+               ),
        ),
        'ext.visualEditor.mediawiki' => $wgVisualEditorResourceTemplate + array(
                'scripts' => array(
diff --git a/modules/unicodejs/AUTHORS.txt b/modules/unicodejs/AUTHORS.txt
index b3cede6..bfe0429 100644
--- a/modules/unicodejs/AUTHORS.txt
+++ b/modules/unicodejs/AUTHORS.txt
@@ -1,7 +1,6 @@
 Principal Authors (major contributors, alphabetically)
 
 Ed Sanders <[email protected]>
+David Chan <[email protected]>
 
 Patch Contributors (minor contributors, alphabetically)
-
-David Chan <[email protected]>
\ No newline at end of file
diff --git a/modules/unicodejs/unicodejs.js b/modules/unicodejs/unicodejs.js
index c22af9e..f4b68d1 100644
--- a/modules/unicodejs/unicodejs.js
+++ b/modules/unicodejs/unicodejs.js
@@ -5,4 +5,39 @@
  * @license The MIT License (MIT); see LICENSE.txt
  */
 
-window.unicodeJS = {};
+( function () {
+       var unicodeJS;
+
+       /**
+        * Namespace for all UnicodeJS classes, static methods and static 
properties.
+        * @class
+        * @singleton
+        */
+       unicodeJS = {};
+
+       /**
+        * Split a string into grapheme clusters.
+        *
+        * @param {string} text Text to split
+        * @returns {string[]} Array of clusters
+        */
+       unicodeJS.splitClusters = function ( text ) {
+               return text.split( /(?![\uDC00-\uDFFF\u0300-\u036F])/g );
+               // kludge: for now, just don't split UTF surrogate pairs or 
combining accents
+               // TODO: implement Grapheme boundary rules
+       };
+
+       /**
+        * Split a string into Unicode characters, keeping surrogates paired.
+        *
+        * @param {string} text Text to split
+        * @returns {string[]} Array of characters
+        */
+       unicodeJS.splitCharacters = function ( text ) {
+               return text.split( /(?![\uDC00-\uDFFF])/g );
+               // TODO: think through handling of invalid UTF-16
+       };
+
+       // Expose
+       window.unicodeJS = unicodeJS;
+}() );
diff --git a/modules/unicodejs/unicodejs.textstring.js 
b/modules/unicodejs/unicodejs.textstring.js
index b8ddf8e..f9bf2aa 100644
--- a/modules/unicodejs/unicodejs.textstring.js
+++ b/modules/unicodejs/unicodejs.textstring.js
@@ -16,21 +16,51 @@
  * @param {string} text Text
  */
 unicodeJS.TextString = function UnicodeJSTextString( text ) {
-       this.text = text;
+       this.clusters = unicodeJS.splitClusters( text );
 };
 
 /* Methods */
 
 /**
- * Read character at specified position
+ * Read grapheme cluster at specified position
  *
  * @method
  * @param {number} position Position to read from
- * @returns {string|null} Character, or null if out of bounds
+ * @returns {string|null} Grapheme cluster, or null if out of bounds
  */
 unicodeJS.TextString.prototype.read = function ( position ) {
-       if ( position < 0 || position >= this.text.length ) {
-               return null;
-       }
-       return this.text.charAt( position );
+       var clusterAt = this.clusters[position];
+       return clusterAt !== undefined ? clusterAt : null;
+};
+
+/**
+ * Return number of grapheme clusters in the text string
+ *
+ * @method
+ * @returns {number} Number of grapheme clusters
+ */
+unicodeJS.TextString.prototype.getLength = function () {
+       return this.clusters.length;
+};
+
+/**
+ * Return a sub-TextString
+ *
+ * @param {number} start Start offset
+ * @param {number} end End offset
+ * @returns {unicodeJS.TextString} New TextString object containing substring
+ */
+unicodeJS.TextString.prototype.substring = function ( start, end ) {
+       var textString = new unicodeJS.TextString( '' );
+       textString.clusters = this.clusters.slice( start, end );
+       return textString;
+};
+
+/**
+ * Get as a plain string
+ *
+ * @returns {string} Plain javascript string
+ */
+unicodeJS.TextString.prototype.getString = function () {
+       return this.clusters.join( '' );
 };
diff --git a/modules/unicodejs/unicodejs.wordbreak.js 
b/modules/unicodejs/unicodejs.wordbreak.js
index 910038d..28bd4df 100644
--- a/modules/unicodejs/unicodejs.wordbreak.js
+++ b/modules/unicodejs/unicodejs.wordbreak.js
@@ -19,19 +19,39 @@
 
        // build regexes
        for ( group in groups ) {
-               patterns[group] = new RegExp( '[' + groups[group] + ']' );
+               patterns[group] = new RegExp( '[' + groups[group] + ']' ); // 
TODO: handle surrogates
        }
 
-       function getGroup( chr ) {
-               // chr is always converted to a string by RegExp#test
+       /**
+        * Return the wordbreak property value for the cluster
+        *
+        * This is a slight con, because Unicode wordbreak property values are 
defined
+        * per character, not per cluster, whereas we're already working with a 
string
+        * split into clusters.
+        *
+        * We are making a working assumption that we can implement the Unicode
+        * word boundary specification by taking the property value of the 
*first*
+        * character of the cluster. In particular, this implements WB4 for us, 
because
+        * non-initial Extend or Format characters disapper.
+        *
+        * See http://www.unicode.org/reports/tr29/#Word_Boundaries
+        *
+        * @private
+        * @param {string} cluster The grapheme cluster
+        * @returns {string} The unicode wordbreak property value
+        */
+       function getGroup( cluster ) {
+               var character;
+               // cluster is always converted to a string by RegExp#test
                // e.g. null -> 'null' and would match /[a-z]/
                // so return null for any non-string value
-               if ( typeof chr !== 'string' ) {
+               if ( typeof cluster !== 'string' ) {
                        return null;
                }
+               character = unicodeJS.splitCharacters( cluster )[0];
                var group;
                for ( group in patterns ) {
-                       if ( patterns[group].test( chr ) ) {
+                       if ( patterns[group].test( character ) ) {
                                return group;
                        }
                }
diff --git a/modules/unicodejs/unicodejs.wordbreak.test.js 
b/modules/unicodejs/unicodejs.wordbreak.test.js
index ca33b26..9966685 100644
--- a/modules/unicodejs/unicodejs.wordbreak.test.js
+++ b/modules/unicodejs/unicodejs.wordbreak.test.js
@@ -20,8 +20,15 @@
                        // 30 - 40
                        " a_b_3_ナ_ " +
                        // 40 - 50
-                       "汉字/漢字 c\u0300\u0327k" +
+                       "汉字/漢字 c\u0300\u0327k  " +
                        // 50 - 60
+                       "\ud800\udf08" + // U+10308 OLD ITALIC LETTER THE
+                       "\ud800\udf08\u0302" + // U+10308 OLD ITALIC LETTER THE 
+ combining circumflex
+                       "\ud800\udf0a" + // U+1030A OLD ITALIC LETTER KA
+                       " pad " +
+                       "\ud800\udf0a" + // U+1030A OLD ITALIC LETTER KA
+                       "\ud800\udf0a" + // U+1030A OLD ITALIC LETTER KA
+                       // 60 - 65: "a." tests end of para
                        " c\u0300\u0327 a.",
                        /*jshint quotmark:single */
                textString = new unicodeJS.TextString( text ),
@@ -30,18 +37,22 @@
                        11, 12, 13, 14, 15, 16, 17, 19,
                        21, 25, 30,
                        31, 39, 40,
-                       41, 42, 43, 44, 45, 46, 50,
-                       51, 54, 55, 56, 57
+                       41, 42, 43, 44, 45, 46, 48, 49, 50,
+                       51, 52, // TODO: these should not be in the list
+                       53, 54, 57, 58,
+                       59, // TODO: this should not be in the list
+                       60,
+                       61, 62, 63, 64, 65
                ];
 
-       QUnit.expect( text.length + 1 );
+       QUnit.expect( textString.getLength() + 1 );
 
-       for ( i = 0; i <= text.length; i++ ) {
+       for ( i = 0; i <= textString.getLength(); i++ ) {
                result = ( breaks.indexOf( i ) !== -1 );
                context =
-                       text.substring( Math.max( i - 4, 0 ), i ) +
+                       textString.substring( Math.max( i - 4, 0 ), i 
).getString() +
                        '│' +
-                       text.substring( i, Math.min( i + 4, text.length ) )
+                       textString.substring( i, Math.min( i + 4, text.length ) 
).getString()
                ;
                assert.equal(
                        unicodeJS.wordbreak.isBreak( textString, i ),
diff --git a/modules/ve/ve.js b/modules/ve/ve.js
index fa15afa..266e1e7 100644
--- a/modules/ve/ve.js
+++ b/modules/ve/ve.js
@@ -770,15 +770,10 @@
                return ve.init.platform.getMessage.apply( ve.init.platform, 
arguments );
        };
 
-       /**
-        * Split a string into individual characters, leaving multibyte 
characters as one item
-        *
-        * @param {string} text Text to split
-        * @returns {string[]} Array of characters
-        */
-       ve.splitCharacters = function ( text ) {
-               return text.split( /(?![\uDC00-\uDFFF\u0300-\u036F])/g ); // 
don't split UTF surrogate pairs
-       };
+    /**
+     * @see unicodeJS#splitClusters
+     */
+       ve.splitCharacters = unicodeJS.splitClusters; // TODO: rename to 
ve.splitClusters
 
        /**
         * Determine if the text consists of only unattached combining marks

-- 
To view, visit https://gerrit.wikimedia.org/r/66123
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I202b98199d2780534d1e02519b72579ba796f08f
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Divec <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Divec <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to