【UWP】China Daily中划词翻译的实现

在博主开发的UWP应用China Daily中有一个比较好玩的功能——划词翻译,现将其具体的实现方式拿出来与各位分享一番。

首先,我们来看看划词(划句)翻译的具体表现: image
image

废话不多说,接下来我们看看具体的代码实现
注:新闻的主体是放在一个WebView里面的,所以,说白了就是解决WebView的问题(JS通知后台C#以及C#调用JS)。

1.XAML部分

<WebView x:Name="webView"  
    NavigationCompleted="webView_NavigationCompleted"  
    ScriptNotify="webView_ScriptNotify"  
    />  

2.在后台代码中添加对JS的alert方法的监听

private async void webView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)  
{
    //检测alert
    var inject = WebViewUtil.RiseNotification();

    await webView.InvokeScriptAsync("eval", new List<string>() { inject });
}
public static string RiseNotification()  
{
    string func = @"window.alert = function(arg) {
                        window.external.notify(arg);
                    };";
    return func;
}

3.后台C#代码检测到JS的alert方法发出通知时的处理

private void webView_ScriptNotify(object sender, NotifyEventArgs e)  
{
    var data = e.Value.Trim();

    if (data.IsUrl()) // 判断字符串是否为Url的一个自定义扩展方法
    {
        // 判断点击的是图片
        LoadImage(data);
    }
    else
    {
        // 判断点击的是文本
        LoadTranslation(data);
    }
}

说明:此处的data是WebView通过JS方法传递过来的信息。如果点击的是图片,则传递图片的url;如果点击(选中)的是文本,则把文本传递过来。
疑问:WebView会这么机智的将我们想要的信息准确的传递过来吗?答案是肯定的,前提是我们交给它一个锦囊告诉它应该怎么做。请往下看~

4.交给WebView的锦囊

4.1 小插曲

China Daily 接口返回的新闻内容主体样式及其简陋,所以我决定稍微为它补个妆(自定义了一点点样式),如下:

public static int GetBaseFontSize() => DeviceUtils.IsMobile ? 32 : 14;

public static int GetBaseLineHeight() => DeviceUtils.IsMobile ? 64 : 28;

/// <summary>
/// 内容样式
/// </summary>
/// <returns></returns>
public static string GetContentCSS()  
{
    string commonStyle = "touch-action: pan-y; font-family:'微软雅黑';" + (DeviceUtils.IsMobile ? "padding:2px 19px 2px 19px;" : "text-align:justify;  padding:2px 16px 2px 5px;");

    string colorstyle = "background-color:#ffffff; color:#000000;" ;

    string webStyle = "body{" + commonStyle + colorstyle +
                      $"font-size:{GetBaseFontSize()}px; line-height:{GetBaseLineHeight()}px" +
                      "}";
    string css = "<style>" + webStyle + "</style>";

    return css;
}

注:GetContentCSS方法只是用于给整段新闻添加一个样式,使布局看起来美观一点,大家不用太在意它(甚至也许你的应用场景根本不需要手动修改原有样式)。

4.2 关键部分
/// <summary>
/// JS方法
/// </summary>
/// <returns></returns>
public static string GetContentJS()  
{
    string js = @"window.lastOriginData = '';

                  function mouseUp (param) {
                      if(param == 'y') return;
                      if (window.getSelection) {
                          var str = window.getSelection().toString();
                          window.lastOriginData = str.trim();
                          alert(str);
                      }
                  };
                  function getImageUrl(url) {
                      alert(url);
                  }
                  window.onload = function () {
                      var as = document.getElementsByTagName('a');
                      for (var i = 0; i < as.length; i++) {
                          as[i].onclick = function (event) {
                              window.external.notify(JSON.stringify({ 'type': 'HyperLink', 'content': this.href }));
                              return false;
                          }
                      }
                  };";

    return js;
}

注:这便是锦囊的主要内容了。我在GetContentJS方法中定义了几个JS的方法。其中的window.onload部分不用太在意;mouseUp部分用于在光标离开的时候,通知后台C#用户所选中的文本;而getImageUrl方法则是用于传递所点击的图片的url。需要注意的是,接口给定的内容中一般来说不太可能给出img的onclick事件,因此,这个部分也需要我们进行相关处理,请继续往下看~

4.3 处理img的onclick事件
/// <summary>
/// 处理新闻内容(主要是过滤Style和为图片添加点击事件)
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public static string UpdateContentStyle(string content)  
{
    if (string.IsNullOrEmpty(content))
        return null;

    // 正则匹配img,统一替换它的Style,并添加点击事件(此处的匹配参数仅适用于ChinaDaily接口数据,不具代表性,各位请灵活处理)
    var pattern = @"<img[\s\S]*?(style='[\s\S]*?')[\s\S]*?/?>";
    var matches = Regex.Matches(content, pattern); 
    List<string> list = new List<string>();

    if (matches.Count > 0)
        list.Add(matches[0].Groups[1].Value);

    for (int i = 1; i < matches.Count - 1; i++)
    {
        if (!list.Contains(matches[i].Groups[1].Value))
            list.Add(matches[i].Groups[1].Value);
    }

    foreach (var item in list)
    {
        content = content.Replace(item, imgStyle);
    }

    return content;
}
4.4 资源整合

现在我们已经拥有了实现划词翻译功能的必要模块,接下来要做的就是把他们整合起来,并保证交给WebView的是一段完整的HTML代码。

/// <summary>
/// 将新闻主体部分拼凑成一段完整的html
/// </summary>
/// <returns></returns>
public string ProcessNewsContent()  
{
    var newsContent = WebViewUtil.UpdateContentStyle(news.Content);

    string webContent = "<html><head>" + contentCSS + "<script>" + js + "</script>" + "</head>" + "<body><div onmouseup='mouseUp()'> " + newsContent + "</div>" + "</body></html>";

    return webContent;
}

简单说明:代码中的contentCSS就是上方GetContentCSS方法返回的结果,js是GetContentJS方法返回的结果,news则是用于展示的新闻,news.Content则是新闻内容,其大致模样如下:

<p>  
    <strong>Today three top economists look at the state of the global economy and give their outlooks for 2017.</strong>
</p>  
<p>  
    <img attachmentid=""232694"" src=""http://iosnews.chinadaily.com.cn/newsdata/news/201612/26/432220/picture_232694.jpg"" style='max-width: 100%' />
</p>  
4.5 交货

把完整的html字符串(下方代码将其存放在变量content中)交给WebView

webView.NavigateToString(content);  

5. 测试

至此,关键的部分均已实现,让我们按照以上套路运行一番。(假设LoadImage和LoadTranslation方法都只是将JS传递过来的信息直接显示出来)

我们发现,PC上正常运行毫无问题;但是,手机上却是另一番光景了——选中文本的时候没有任何反应,取消选中的时候反倒是显示信息了。(尚未搞清楚是为何)

因此,我采用了一个纠正措施,如下:

/// <summary>
/// 为wp修正文本选中问题
/// </summary>
public async void ProcessTextSelected4Phone(string text)  
{
    if (DeviceUtils.IsMobile)//wp上表现和电脑上不一样...
    {
        // 判断是否选中文本
        DataPackage dataPackage = await webView.CaptureSelectedContentToDataPackageAsync();
        if (dataPackage == null)// 表示未选中文本
        {
            LoadTranslation(null);// 隐藏翻译栏
            return;
        }

        var handleMouseUp = string.IsNullOrEmpty(text) ? false : true;
        await webView.InvokeScriptAsync("mouseUp", new[] { handleMouseUp ? "y" : "" });// 若参数为y,则不触发事件
    }
}

注:实质就是在手机端,在正常的mouseUp触发基础上,通过代码主动多触发一次mouseUp,以达到修正目的。

6. 结语

到这里,划词翻译中的划词提取部分已完全呈现给了各位,至于其中的翻译部分,就不再此篇博客中赘述了,各位可以自行寻找一些翻译渠道来完成(PS:博主采用的是有道的免费翻译接口,虽接口调用频率有限,倒也是够用了)。

7. Demo

Download Demo

8. 参考

【WP8.1】WebView笔记:http://www.cnblogs.com/bomo/p/4320077.html