Santhosh has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/170000

Change subject: Revert "Derive MT subsentence map from uppercasing"
......................................................................

Revert "Derive MT subsentence map from uppercasing"

This reverts commit 78fc396b491f16ab566b9d93940b1767be1a9a89.

Commit introduce a regression- Sections with lists are not translated

Change-Id: I6d4b6e99b409ca863b9cd3221bb6118c5752e9ad
---
D bin/apertium-xhtml
M index.js
M lineardoc/LinearDoc.js
M mt/Apertium.js
A tests/apertium/Apertium.test.js
M tests/index.js
M tests/lineardoc/LinearDoc.test.js
M tests/lineardoc/translate.test.json
D tests/mt/Apertium.test.js
9 files changed, 152 insertions(+), 508 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/cxserver 
refs/changes/00/170000/1

diff --git a/bin/apertium-xhtml b/bin/apertium-xhtml
deleted file mode 100755
index 08d6865..0000000
--- a/bin/apertium-xhtml
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env node
-var sourceLang, targetLang, sourceHtml, script, args,
-       Apertium = require( __dirname + '/../mt/Apertium.js' );
-
-script = process.argv[ 1 ];
-args = process.argv.slice( 2 );
-if ( args.length !== 2 ) {
-       process.stderr.write(
-               'Usage: node ' + script + ' <sourceLang> <targetLang> < 
xhtmlSource\n\n' +
-               'xhtml must be wrapped in a block element such as <p>...</p> or 
<div>..</div>.\n' +
-               'Example:\n\techo "<p>A <b>red</b> box.</p>" | node ' + script 
+ ' en es\n\n'
-       );
-       process.exit( 1 );
-}
-
-sourceLang = args[ 0 ];
-targetLang = args[ 1 ];
-
-sourceHtml = [];
-
-process.stdin.on( 'data', function( data ) {
-       sourceHtml.push( data );
-} );
-process.stdin.on( 'end', function() {
-       Apertium.translate(
-               sourceLang,
-               targetLang,
-               sourceHtml.join( '' )
-       ).then( function( targetHtml ) {
-               process.stdout.write( targetHtml + '\n' );
-       }, function( error ) {
-               console.error( 'error', error );
-               process.exit( 2 );
-       } );
-} );
diff --git a/index.js b/index.js
index f955bdb..1b309e1 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,5 @@
 module.exports = {
        Segmenter: require( './segmentation/CXSegmenter.js' ).CXSegmenter,
-       Apertium: require( './mt/Apertium.js' ),
        LinearDoc: require( './lineardoc/LinearDoc.js' ),
        Dictionary: require( './dictionary' )
 };
diff --git a/lineardoc/LinearDoc.js b/lineardoc/LinearDoc.js
index 3144bad..58a0cdd 100644
--- a/lineardoc/LinearDoc.js
+++ b/lineardoc/LinearDoc.js
@@ -109,7 +109,7 @@
  *
  * @private
  * @param {Object[]} tagArray SAX open tags
- * @return {string[]} Tag names
+ * @returns [string[]] Tag names
  */
 function dumpTags( tagArray ) {
        var i, len, tag, attr, attrDumps,
@@ -153,7 +153,7 @@
  *
  * @private
  * @param {string} tagName The name of the tag (lowercase)
- * @return {boolean} Whether the tag is an inline empty tag
+ * @returns {boolean} Whether the tag is an inline empty tag
  */
 function isInlineEmptyTag( tagName ) {
        // link/meta as they're allowed anywhere in HTML5+RDFa, and must be 
treated as void
@@ -166,7 +166,7 @@
  *
  * @private
  * @param {string} tagName The name of the tag (lowercase)
- * @return {boolean} Whether the tag is an inline annotation
+ * @returns {boolean} Whether the tag is an inline annotation
  */
 isInlineAnnotationTag = ( function ( tagArray ) {
        var i, len,
@@ -215,7 +215,7 @@
  * @param {number[]} boundaries Boundary offsets
  * @param chunks Chunks to which the boundaries apply
  * @param {Function} getLength Function returning the length of a chunk
- * @return {Object[]} Array of {chunk: ch, boundaries: [...]}
+ * @returns {Object[]} Array of {chunk: ch, boundaries: [...]}
  */
 function getChunkBoundaryGroups( boundaries, chunks, getLength ) {
        var i, len, groupBoundaries, chunk, chunkLength, boundary,
@@ -282,28 +282,13 @@
 function TextBlock( textChunks ) {
        var i, len, cursor;
        this.textChunks = textChunks;
-       this.offsets = [];
+       this.startOffsets = [];
        cursor = 0;
        for ( i = 0, len = this.textChunks.length; i < len; i++ ) {
-               this.offsets[ i ] = { start: cursor, length: this.textChunks[ i 
].text.length };
-               cursor += this.offsets[ i ].length;
+               this.startOffsets[i] = cursor;
+               cursor += this.textChunks[i].text.length;
        }
 }
-
-/**
- * Get the start and length of each non-common annotation
- * @return {Object[]}
- * @return[i].start {number} Position of each text chunk
- * @return[i].length {number} Length of each text chunk
- */
-TextBlock.prototype.getTagOffsets = function () {
-       var textBlock = this,
-               commonTags = this.getCommonTags();
-       return this.offsets.filter( function ( offset, i ) {
-               var textChunk = textBlock.textChunks[ i ];
-               return textChunk.tags.length > commonTags.length && 
textChunk.text.length > 0;
-       } );
-};
 
 /**
  * Get the (last) text chunk at a given char offset
@@ -315,37 +300,11 @@
        // TODO: bisecting instead of linear search
        var i, len;
        for ( i = 0, len = this.textChunks.length - 1; i < len; i++ ) {
-               if ( this.offsets[ i + 1 ].start > charOffset ) {
+               if ( this.startOffsets[ i + 1 ] > charOffset ) {
                        break;
                }
        }
        return this.textChunks[ i ];
-};
-
-/**
- * Returns the list of SAX tags that apply to the whole text block
- * @return {Object[]} List of common SAX tags
- */
-TextBlock.prototype.getCommonTags = function () {
-       var i, iLen, j, jLen, commonTags, tags;
-       if ( this.textChunks.length === 0 ) {
-               return [];
-       }
-       commonTags = this.textChunks[ 0 ].tags.slice();
-       for ( i = 0, iLen = this.textChunks.length; i < iLen; i++ ) {
-               tags = this.textChunks[ i ].tags;
-               if ( tags.length < commonTags.length ) {
-                       commonTags.splice( tags.length );
-               }
-               for ( j = 0, jLen = commonTags.length; j < jLen; j++ ) {
-                       if ( commonTags[ j ] !== tags[ j ] ) {
-                               // truncate
-                               commonTags.splice( j );
-                               break;
-                       }
-               }
-       }
-       return commonTags;
 };
 
 /**
@@ -354,11 +313,11 @@
  * @method
  * @param {string} targetText Translated plain text
  * @param {Object[]} rangeMappings Array of source-target range index mappings
- * @return {TextBlock} Translated textblock with tags applied
+ * @returns {TextBlock} Translated textblock with annotations applied
  */
-TextBlock.prototype.translateTags = function ( targetText, rangeMappings ) {
+TextBlock.prototype.translateAnnotations = function ( targetText, 
rangeMappings ) {
        var i, iLen, j, rangeMapping, sourceTextChunk, text, pos, textChunk, 
offset,
-               sourceRangeEnd, targetRangeEnd, tail, tailSpace, commonTags,
+               sourceRangeEnd, targetRangeEnd, tail, tailSpace,
                // map of { offset: x, textChunks: [...] }
                emptyTextChunks = {},
                emptyTextChunkOffsets = [],
@@ -379,7 +338,7 @@
        // Create map of empty text chunks, by offset
        for ( i = 0, iLen = this.textChunks.length; i < iLen; i++ ) {
                textChunk = this.textChunks[ i ];
-               offset = this.offsets[ i ].start;
+               offset = this.startOffsets[ i ];
                if ( textChunk.text.length > 0 ) {
                        continue;
                } 
@@ -394,7 +353,7 @@
        emptyTextChunkOffsets.sort( function ( a, b ) { return a - b; } );
 
        for ( i = 0, iLen = rangeMappings.length; i < iLen; i++ ) {
-               // Copy tags from source text start offset
+               // Copy annotations from source text start offset
                rangeMapping = rangeMappings[ i ];
                sourceRangeEnd = rangeMapping.source.start + 
rangeMapping.source.length;
                targetRangeEnd = rangeMapping.target.start + 
rangeMapping.target.length;
@@ -434,9 +393,8 @@
        textChunks.sort( function ( textChunk1, textChunk2 ) {
                return textChunk1.start - textChunk2.start;
        } );
-       // Fill in any textChunk gaps using text with commonTags
+       // Fill in any textChunk gaps with unannotated text
        pos = 0;
-       commonTags = this.getCommonTags();
        for ( i = 0, iLen = textChunks.length; i < iLen; i++ ) {
                textChunk = textChunks[ i ];
                if ( textChunk.start < pos ) {
@@ -448,7 +406,7 @@
                                length: textChunk.start - pos,
                                textChunk: new TextChunk(
                                        targetText.substr( pos, textChunk.start 
- pos ),
-                                       commonTags
+                                       []
                                )
                        } );
                        i++;
@@ -465,11 +423,11 @@
        }
 
        if ( tail ) {
-               // Append tail as text with commonTags
+               // Append tail as unannotated text
                textChunks.push( {
                        start: pos,
                        length: tail.length,
-                       textChunk: new TextChunk( tail, commonTags )
+                       textChunk: new TextChunk( tail, [] )
                } );
                pos += tail.length;
        }
@@ -480,11 +438,11 @@
                pushEmptyTextChunks( pos, emptyTextChunks[ offset ] );
        }
        if ( tailSpace ) {
-               // Append tailSpace as text with commonTags
+               // Append tailSpace as unannotated text
                textChunks.push( {
                        start: pos,
                        length: tailSpace.length,
-                       textChunk: new TextChunk( tailSpace, commonTags )
+                       textChunk: new TextChunk( tailSpace, [] )
                } );
                pos += tail.length;
        }
diff --git a/mt/Apertium.js b/mt/Apertium.js
index c7f3acf..3d53c92 100644
--- a/mt/Apertium.js
+++ b/mt/Apertium.js
@@ -3,22 +3,16 @@
        request = require( 'request' ),
        conf = require( __dirname + '/../utils/Conf.js' ),
        LinearDoc = require( '../lineardoc/LinearDoc' ),
-       //logger = require( '../utils/Logger.js' ),
+       Entities = require( 'html-entities' ).AllHtmlEntities,
+       logger = require( '../utils/Logger.js' ),
+       spawn = require( 'child_process' ).spawn,
        // TODO: Tokenize properly. These work for English/Spanish/Catalan
        TOKENS = 
/[\wáàçéèíïóòúüñÁÀÇÉÈÍÏÓÒÚÜÑ]+(?:[·'][\wáàçéèíïóòúüñÁÀÇÉÈÍÏÓÒÚÜÑ]+)?|[^\wáàçéèíïóòúüñÁÀÇÉÈÍÏÓÒÚÜÑ]+/g,
        IS_WORD = 
/^[\wáàçéèíïóòúüñÁÀÇÉÈÍÏÓÒÚÜÑ]+(?:[·'][\wáàçéèíïóòúüñÁÀÇÉÈÍÏÓÒÚÜÑ]+)?$/;
 
 apertiumLangMapping = require( __dirname + '/mappings.js' );
 
-/**
- * Split text into tokens
- * @param {string} lang Language code
- * @param {string} text Text to split
- * @return {Object[]} List of tokens
- * @return[].text Text of the token
- * @return[].isWord Whether the token is a word
- */
-function getTokens( lang, text ) {
+function getTokens( text ) {
        // TODO: implement for other languages than English/Spanish/Catalan
        return text.match( TOKENS ).map( function ( tokenText ) {
                return {
@@ -28,166 +22,114 @@
        } );
 }
 
-/**
- * Language-aware uppercasing
- * @param {string} lang Language code
- * @param {string} text Text to uppercase
- * @return {string} Upper-cased text (possibly identical)
- */
-function toUpperCase( lang, text ) {
-       // stub: just use the javascript ASCII method for now
-       return text.toUpperCase();
+function getRangedText( text ) {
+       var i = 0;
+       return getTokens( text ).map( function ( token ) {
+               return '<span id="mtToken' + ( i++ ) + '">' +
+                       LinearDoc.esc( token.text ) +
+                       '</span>';
+       } ).join( '' );
 }
 
-/**
- * Create variants of the text, with a different annotation uppercased in each.
- * @param {string} lang Language code
- * @param {string} text Text
- * @param {Object[]} annotationOffsets start and length of each annotation
- * @return {Object[]}
- * @return[].start {number} Start offset of uppercasing
- * @return[].length {number} Length of uppercasing
- * @return[].text {string} Text variant with uppercasing
- */
-function getCaseVariants( lang, sourceText, annotationOffsets ) {
-       var i, len, offset, chunk, upperChunk, variantText,
-               caseVariants = [];
-
-       for ( i = 0, len = annotationOffsets.length; i < len; i++ ) {
-               offset = annotationOffsets[ i ];
-               chunk = sourceText.slice( offset.start, offset.start + 
offset.length );
-               upperChunk = toUpperCase( lang, chunk );
-               if ( upperChunk === chunk ) {
-                       // Already uppercased; can't detect change
+function readRangedText( text ) {
+       var i, len, part, match, partText,
+               offset = 0,
+               ranges = {},
+               entities = new Entities(),
+               parts = text.match( /<span id="mtToken\d+">[^<>]+<\/span>/g );
+       for ( i = 0, len = parts.length; i < len; i++ ) {
+               part = parts[ i ];
+               match = part.match( /^<span 
id="mtToken(\d+)">([^<>]+)<\/span>$/ );
+               if ( !match ) {
+                       console.warn( 'Bad part: "' + part + '"' );
                        continue;
                }
-               variantText = [
-                       sourceText.slice( 0, offset.start ),
-                       upperChunk,
-                       sourceText.slice( offset.start + offset.length )
-               ].join( '' );
-               caseVariants.push( {
-                       start: offset.start,
-                       length: offset.length,
-                       text: variantText
-               } );
-       }
-       return caseVariants;
-}
-
-/**
- * Finds offsets of ranges at which tokens have changed to uppercase
- * @param {string} text Original text
- * @param {string} text Changed text
- * @return {Object[]} start and length for each changed range
- */
-function getChangedCaseRanges( lang, originalText, changedText ) {
-       var orig, upper, changed, len, ranges, start, startChar, end, endChar;
-       orig = getTokens( lang, originalText );
-       upper = getTokens( lang, toUpperCase( lang, originalText ) );
-       changed = getTokens( lang, changedText );
-
-       len = orig.length;
-       if ( len !== upper.length || len !== changed.length ) {
-               throw new Error( 'token length mismatch' );
-       }
-
-       // Find start/end of changed text token ranges. Track char ranges too, 
and store these.
-       ranges = [];
-       // start token
-       start = 0;
-       // start char
-       startChar = 0;
-
-       while ( true ) {
-               // Skip to first changed word token
-               while ( start < len && (
-                       !( orig[ start ].isWord ) ||
-                       (
-                               orig[ start ].text === changed[ start ].text ||
-                               upper[ start ].text !== changed[ start ].text
-                       )
-               ) ) {
-                       startChar += orig[ start ].text.length;
-                       start++;
-               }
-               if ( start >= len ) {
-                       break;
-               }
-               // Find last consecutive changed non-word token
-               end = start;
-               endChar = startChar + orig[ end ].text.length;
-
-               while ( end < len && (
-                       !( orig[ end ].isWord ) ||
-                       (
-                               orig[ end ].text !== upper[ end ].text &&
-                               upper[ end ].text === changed[ end ].text
-                       )
-               ) ) {
-                       end++;
-                       if ( end < len ) {
-                               endChar += orig[ end ].text.length;
-                       }
-               }
-               do {
-                       if ( end < len ) {
-                               endChar -= orig[ end ].text.length;
-                       }
-                       end--;
-               } while ( !( orig[ end ].isWord ) );
-               // Store ranges
-               ranges.push( {
-                       start: startChar,
-                       length: endChar - startChar
-               } );
-               start = end + 1;
-               startChar = endChar;
+               partText = entities.decode( match[ 2 ] );
+               ranges[ match[ 1 ] ] = {
+                       text: partText,
+                       start: offset,
+                       length: partText.length
+               };
+               offset += partText.length;
        }
        return ranges;
 }
 
-/**
- * Calculate range mappings based on the target text variants
- * @param {string} targetLang The target language
- * @param {Object[]} sourceVariants The start and length of each variation
- * @param {
- * @param {Object} annotationOffsets The start and length of each offset, by 
sourceVariantId
- */
-function getRangeMappings( targetLang, sourceVariants, targetText, targetLines 
) {
-       var i, iLen, j, jLen, changedCaseRanges, sourceRange,
+function getTextAndRangeMappings( rangedSourceText, rangedTargetText ) {
+       var sourceParts, targetParts, ids, i, len, id,
+               targetTextParts = [],
                rangeMappings = [];
-       if ( sourceVariants.length !== targetLines.length ) {
-               throw new Error( 'Translation variants length mismatch' );
-       }
-       for ( i = 0, iLen = sourceVariants.length; i < iLen; i++ ) {
-               sourceRange = {
-                       start: sourceVariants[ i ].start,
-                       length: sourceVariants[ i ].length
-               };
-               changedCaseRanges = getChangedCaseRanges(
-                       targetLang,
-                       targetText,
-                       targetLines[ i ]
-               );
-               for ( j = 0, jLen = changedCaseRanges.length; j < jLen; j++ ) {
-                       rangeMappings.push( {
-                               source: sourceRange,
-                               target: changedCaseRanges[ j ]
-                       } );
+       sourceParts = readRangedText( rangedSourceText );
+       targetParts = readRangedText( rangedTargetText );
+       ids = Object.keys( sourceParts );
+       ids.sort( function ( x, y ) {
+               return parseInt( x ) - parseInt( y );
+       } );
+       for ( i = 0, len = ids.length; i < len; i++ ) {
+               id = ids[ i ];
+               if ( targetParts[ id ] === undefined ) {
+                       continue;
                }
+               rangeMappings.push( {
+                       source: sourceParts[ id ],
+                       target: targetParts[ id ]
+               } );
+               targetTextParts.push( targetParts[ id ].text );
        }
-       return rangeMappings;
+       return {
+               text: targetTextParts.join( '' ),
+               rangeMappings: rangeMappings
+       };
 }
 
 /**
- * Translate plain text with Apertium API
+ * Translate the text
  * @param {string} sourceLang Source language code
  * @param {string} targetLang Target language code
- * @param {string} sourceText Source language text
- * @return {Object} Deferred promise: Target language text
+ * @param {string} sourceText Source plain text
+ * @return {Object} Deferred promise: Translated plain text
  */
-function translateTextApertium( sourceLang, targetLang, sourceText ) {
+function translateText( sourceLang, targetLang, sourceText ) {
+       var apertium, rangedSourceText, rangedTargetText,
+               rangedTargetTextData = [],
+               deferred = Q.defer();
+       rangedSourceText = getRangedText( sourceText );
+       apertium = spawn(
+               'python', [ 'mt/apertium.py', sourceLang + '-' + targetLang, 
'-u' ], {
+                       stdio: 'pipe',
+                       env: {
+                               PATH: process.env.PATH,
+                               LC_ALL: 'en_US.utf8'
+                       }
+               }
+       );
+       apertium.stderr.on( 'data', function ( data ) {
+               logger.error( data );
+       } );
+       apertium.stdout.on( 'data', function ( data ) {
+               rangedTargetTextData.push( '' + data );
+       } );
+       apertium.on( 'close', function ( code ) {
+               if ( code !== 0 ) {
+                       deferred.reject( new Error( '' + code ) );
+                       return;
+               }
+               rangedTargetText = rangedTargetTextData.join( '' );
+               deferred.resolve( getTextAndRangeMappings( rangedSourceText, 
rangedTargetText ) );
+       } );
+       apertium.stdin.write( rangedSourceText );
+       apertium.stdin.end();
+       return deferred.promise;
+}
+
+/**
+ * Translate marked up text relying on the MT engine
+ * @param {string} sourceLang Source language code
+ * @param {string} targetLang Target language code
+ * @param {string} sourceHtml Source rich text
+ * @return {Object} Deferred promise: Translated rich text
+ */
+function translate( sourceLang, targetLang, sourceHtml ) {
        var deferred = Q.defer(),
                postData;
 
@@ -196,93 +138,22 @@
                form: {
                        markUnknown: 0,
                        langpair: apertiumLangMapping[ sourceLang ] + '|' + 
apertiumLangMapping[ targetLang ],
-                       format: 'txt',
-                       q: sourceText
+                       q: sourceHtml
                }
        };
        request.post( postData,
                function ( error, response, body ) {
-                       var message;
                        if ( error ) {
                                deferred.reject( new Error( error ) );
                                return;
                        }
                        if ( response.statusCode !== 200 ) {
-                               message = 'Error ' + response.statusCode;
-                               message += ' sourceText={' + sourceText + '}, 
body={' + body + '}';
-                               deferred.reject( new Error( message ) );
+                               deferred.reject( new Error( 'Error while 
translating content using apertium' ) );
                                return;
                        }
                        deferred.resolve( JSON.parse( body 
).responseData.translatedText );
                }
        );
-       return deferred.promise;
-}
-
-/**
- * Translate multiple lines of plaintext with apertium
- * @param {string} sourceLang Source language code
- * @param {string} targetLang Target language code
- * @param {string[]} sourceLines Source plaintext lines
- * @return {Object} Deferred promise: Translated plaintext lines
- */
-function translateLinesApertium( sourceLang, targetLang, sourceLines ) {
-       var deferred = Q.defer();
-       // Join lines into single string. Separator must break sentences and 
pass through unchanged
-       translateTextApertium(
-               sourceLang,
-               targetLang,
-               sourceLines.join( '\n.CxServerApertium.\n' )
-       ).then( function ( targetLinesText ) {
-               var targetText = targetLinesText
-                       .replace( /^\s+|\s+$/g, '' )
-                       .split( /\n\.CxServerApertium\.\n/g );
-               deferred.resolve( targetText );
-       }, function ( error ) {
-               deferred.reject( error );
-       } );
-       return deferred.promise;
-}
-
-/**
- * Translate text, using case variants to map tag offsets
- * @param {string} sourceLang Source language code
- * @param {string} targetLang Target language code
- * @param {string} sourceText Source plain text
- * @param {Object[]} tagOffsets start and length for each annotation chunk
- * @return {Object} Deferred promise: Translated plain text and range mappings
- */
-function translateTextWithTagOffsets( sourceLang, targetLang, sourceText, 
tagOffsets ) {
-       var sourceVariants, sourceLines, deferred;
-       sourceVariants = getCaseVariants( sourceLang, sourceText, tagOffsets );
-       sourceLines = sourceVariants.map( function ( variant ) {
-               return variant.text;
-       } );
-       sourceLines.splice( 0, 0, sourceText );
-
-       deferred = Q.defer();
-       // Call apertium through module.exports, so tests can override it
-       // Join segments with a string that will definitely break sentences and 
be preserved
-       module.exports.translateLinesApertium(
-               sourceLang,
-               targetLang,
-               sourceLines
-       ).then( function ( targetLines ) {
-               var targetText, rangeMappings;
-               targetText = targetLines.splice( 0, 1 )[ 0 ];
-               rangeMappings = getRangeMappings(
-                       targetLang,
-                       sourceVariants,
-                       targetText,
-                       targetLines
-               );
-               deferred.resolve( {
-                       text: targetText,
-                       rangeMappings: rangeMappings
-               } );
-       }, function ( error ) {
-               deferred.reject( error );
-       } );
        return deferred.promise;
 }
 
@@ -293,7 +164,7 @@
  * @param {string} sourceText Source html
  * @return {Object} Deferred promise: Translated html
  */
-function translate( sourceLang, targetLang, sourceHtml ) {
+function translateHtml( sourceLang, targetLang, sourceHtml ) {
        var i, len, sourceDoc, targetDoc, itemPromises, deferred,
                parser = new LinearDoc.Parser();
        parser.init();
@@ -302,50 +173,40 @@
        // Clone and adapt sourceDoc
        targetDoc = new LinearDoc.Doc( sourceDoc.wrapperTag );
        itemPromises = [];
-
-       function translateItemDeferred( deferred, item ) {
-               itemPromises.push( deferred.promise );
-               if ( item.type !== 'textblock' ) {
-                       deferred.resolve( item );
-                       return;
-               }
-               translateTextWithTagOffsets(
-                       sourceLang,
-                       targetLang,
-                       item.item.getPlainText(),
-                       item.item.getTagOffsets()
-               ).then( function ( translated ) {
-                       var newTextBlock;
-                       try {
-                               newTextBlock = item.item.translateTags(
-                                       translated.text, 
translated.rangeMappings
-                               );
-                               deferred.resolve( {
-                                       type: 'textblock',
-                                       item: newTextBlock
-                               } );
-                       } catch ( ex ) {
-                               deferred.reject( ex );
-                       }
-               }, function ( error ) {
-                       deferred.reject( error );
-               } );
-       }
        for ( i = 0, len = sourceDoc.items.length; i < len; i++ ) {
-               translateItemDeferred( Q.defer(), sourceDoc.items[ i ] );
+               /*jshint loopfunc:true */
+               ( function ( deferred, item ) {
+                       itemPromises.push( deferred.promise );
+                       if ( item.type !== 'textblock' ) {
+                               deferred.resolve( item );
+                               return;
+                       }
+                       translateText( sourceLang, targetLang, 
item.item.getPlainText() ).then( function ( translated ) {
+                               var newTextBlock;
+                               try {
+                                       newTextBlock = 
item.item.translateAnnotations(
+                                               translated.text, 
translated.rangeMappings
+                                       );
+                                       deferred.resolve( {
+                                               type: 'textblock',
+                                               item: newTextBlock
+                                       } );
+                               } catch ( ex ) {
+                                       deferred.reject( ex );
+                               }
+                       } );
+               }( Q.defer(), sourceDoc.items[ i ] ) );
        }
        deferred = Q.defer();
        Q.all( itemPromises ).spread( function () {
                targetDoc.items = Array.prototype.slice.call( arguments, 0 );
-               deferred.resolve( targetDoc.getHtml() );
-       }, function ( error ) {
-               deferred.reject( error );
+               return deferred.resolve( targetDoc.getHtml() );
        } );
        return deferred.promise;
 }
 
 module.exports = {
        translate: translate,
-       translateLinesApertium: translateLinesApertium,
-       getTokens: getTokens
+       translateHtml: translateHtml,
+       translateText: translateText
 };
diff --git a/tests/apertium/Apertium.test.js b/tests/apertium/Apertium.test.js
new file mode 100644
index 0000000..ff9ae93
--- /dev/null
+++ b/tests/apertium/Apertium.test.js
@@ -0,0 +1,8 @@
+var caHtml, Apertium = require( '../../mt/Apertium' );
+
+caHtml = '<p>un dos <b><a href="3">tres</a> quatre <i><a href="5">cinc</a> sis 
set</i> vuit nou</b> deu</p>.';
+
+Apertium.translate( 'ca', 'es', caHtml ).then( function ( esHtml ) {
+       console.log( 'caHtml:', caHtml );
+       console.log( 'esHtml:', esHtml );
+} );
diff --git a/tests/index.js b/tests/index.js
index ddf10d2..17ee135 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -3,7 +3,6 @@
        tests = [
                './tests/segmentation/CXSegmenter.test.js',
                './tests/lineardoc/LinearDoc.test.js',
-               './tests/mt/Apertium.test.js',
                './tests/dictionary/Dictionary.test.js'
        ];
 
diff --git a/tests/lineardoc/LinearDoc.test.js 
b/tests/lineardoc/LinearDoc.test.js
index 745fb36..7b09d22 100644
--- a/tests/lineardoc/LinearDoc.test.js
+++ b/tests/lineardoc/LinearDoc.test.js
@@ -32,7 +32,7 @@
        }
 } );
 
-QUnit.test( 'LinearDoc translatetags', function ( assert ) {
+QUnit.test( 'LinearDoc translateAnnotations', function ( assert ) {
        var parser, textBlock1, textBlock2, i, len, test, doc;
 
        QUnit.expect( 2 * transTests.length );
@@ -49,7 +49,7 @@
                        test.source,
                        'Reconstructed source HTML'
                );
-               textBlock2 = textBlock1.translateTags(
+               textBlock2 = textBlock1.translateAnnotations(
                        test.targetText,
                        test.rangeMappings
                );
diff --git a/tests/lineardoc/translate.test.json 
b/tests/lineardoc/translate.test.json
index 0508548..da48d1c 100644
--- a/tests/lineardoc/translate.test.json
+++ b/tests/lineardoc/translate.test.json
@@ -46,23 +46,6 @@
        "expect": "S \n"
 },
 {
-       "source": "Un cotxe blau.",
-       "targetText": "A blue car.",
-       "rangeMappings": [],
-       "expect": "A blue car."
-},
-{
-       "source": "Un <a href=\"x\">cotxe</a> blau.",
-       "targetText": "A blue car.",
-       "rangeMappings": [
-               {
-                       "source": { "start": 3, "length": 5 },
-                       "target": { "start": 7, "length": 3 }
-               }
-       ],
-       "expect": "A blue <a href=\"x\">car</a>."
-},
-{
        "source": "<b>a1 a2</b> <u>b1 b2</u> <i>c1 c2</i>",
        "targetText": "C1 A1 B1 B2 A2 C2",
        "rangeMappings": [
diff --git a/tests/mt/Apertium.test.js b/tests/mt/Apertium.test.js
deleted file mode 100644
index 7d2c5a9..0000000
--- a/tests/mt/Apertium.test.js
+++ /dev/null
@@ -1,129 +0,0 @@
-QUnit.module( 'Apertium' );
-
-var tests,
-       Q = require( 'q' );
-
-// In each case, below, just "source" and "textTranslations" should be 
sufficient for linearDoc
-// to derive "target".  // The plaintext strings in "textTranslations" are 
real Apertium output.
-// They are pre-cached (Apertium is not actually called during the test), so 
that:
-// 1. Unit testing does not depend on Apertium
-// 2. Tests will not break if an Apertium upgrade changes some translations
-tests = [
-       {
-               title: 'All caps words',
-               source: '<p>A <b>Japanese</b> <i>BBC</i> article</p>',
-               target: '<p>Un artículo de BBC <b>japonés</b></p>',
-               textTranslations: {
-                       'A Japanese BBC article': 'Un artículo de BBC japonés',
-                       'A JAPANESE BBC article': 'Un artículo de BBC JAPONÉS'
-               }
-       },
-       {
-               title: 'Title caps one-to-many',
-               source: '<div>A <b>modern</b> Britain.</div>',
-               target: '<div>Una Gran Bretaña <b>moderna</b>.</div>',
-               textTranslations: {
-                       'A modern Britain.': 'Una Gran Bretaña moderna.',
-                       'A MODERN Britain.': 'Una Gran Bretaña MODERNA.',
-                       'A modern BRITAIN.': 'Una GRAN BRETAÑA moderna.'
-               }
-       },
-       {
-               title: 'Reordering with nested tags',
-               source: '<p>The <b>big <i>red</i></b> dog</p>',
-               target: '<p>El perro <b><i>rojo</i></b> <b>grande</b></p>',
-               textTranslations: {
-                       'The big red dog': 'El perro rojo grande',
-                       'The BIG red dog': 'El perro rojo GRANDE',
-                       'The big RED dog': 'El perro ROJO grande'
-               }
-       },
-       {
-               title: 'Many-to-one with nested tags',
-               source: '<p>He said "<i>I tile <a 
href="x">bathrooms</a>.</i>"</p>',
-               target: '<p>Diga que "<i>enladrillo</i> <i><a 
href="x">baños</a></i>."</p>',
-               textTranslations: {
-                       'He said "I tile bathrooms."': 'Diga que "enladrillo 
baños."',
-                       'He said "I TILE bathrooms."': 'Diga que "ENLADRILLO 
baños."',
-                       'He said "I tile BATHROOMS."': 'Diga que "enladrillo 
BAÑOS."'
-               }
-       },
-       {
-               title: 'Reordering at either ends of a tag',
-               source: '<p>The <b>big red</b> dog</p>',
-               target: '<p>El perro <b>rojo grande</b></p>',
-               textTranslations: {
-                       'The big red dog': 'El perro rojo grande',
-                       'The BIG RED dog': 'El perro ROJO GRANDE'
-               }
-       },
-       {
-               title: 'Identical tags separated by whitespace',
-               source: '<p>The <b>big</b> <b>red</b> dog</p>',
-               target: '<p>El perro <b>rojo</b> <b>grande</b></p>',
-               textTranslations: {
-                       'The big red dog': 'El perro rojo grande',
-                       'The BIG red dog': 'El perro rojo GRANDE',
-                       'The big RED dog': 'El perro ROJO grande'
-               }
-       },
-       {
-               title: 'Non-identical links separated by whitespace',
-               source: '<p>The <a href="1">big</a> <a href="2">red</a> 
dog</p>',
-               target: '<p>El perro <a href="2">rojo</a> <a 
href="1">grande</a></p>',
-               textTranslations: {
-                       'The big red dog': 'El perro rojo grande',
-                       'The BIG red dog': 'El perro rojo GRANDE',
-                       'The big RED dog': 'El perro ROJO grande'
-               }
-       }
-];
-
-QUnit.test( 'Apertium wrapper tests', function ( assert ) {
-       var textTranslations;
-
-       // Fake the actual Apertium call
-       CX.Apertium.translateLinesApertium = function ( sourceLang, targetLang, 
sourceLines ) {
-               var deferred = Q.defer();
-               setTimeout( function () {
-                       var targetLines;
-                       try {
-                               sourceLines.map( function ( line ) {
-                                       return textTranslations[ line ] || 'X' 
+ line + 'X';
-                               } );
-                               deferred.resolve( targetLines );
-                       } catch ( error ) {
-                               deferred.reject( error );
-                       }
-               } );
-               return deferred.promise;
-       };
-
-       QUnit.expect( tests.length );
-
-       function resumeTests( i ) {
-               var test;
-               if ( i >= tests.length ) {
-                       return;
-               }
-               test = tests[ i ];
-               textTranslations = test.textTranslations;
-
-               QUnit.stop();
-
-               CX.Apertium.translate( 'xx', 'yy', test.source ).then( function 
( target ) {
-                       assert.strictEqual(
-                               target,
-                               test.target,
-                               test.title
-                       );
-                       QUnit.start();
-                       resumeTests( i + 1 );
-               }, function ( error ) {
-                       assert.ok( false, test.title + ': ' + error );
-                       QUnit.start();
-                       resumeTests( i + 1 );
-               } );
-       }
-       resumeTests( 0 );
-} );

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6d4b6e99b409ca863b9cd3221bb6118c5752e9ad
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/services/cxserver
Gerrit-Branch: master
Gerrit-Owner: Santhosh <[email protected]>

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

Reply via email to