FCKEditor源代码分析及基于.Net的简化

FCKEditor源代码分析及基于.Net的简化

吴剑 2010-01-08

原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian/

 

前言

FCKEditor 是一款开源的非常优秀的WEB在线编辑器,它的JS类库几乎匹敌于当前流行的JQuery,目前最新版本为2.65,官方网站:http://www.fckeditor.net/ 。本文基于.Net的应用针对FCKEditor的源代码进行分析,同时改造了部分过于复杂的功能,将其简单化。供大家学习讨论之用,个人能力有限,不足之处还请指正。
DEMO环境:.Net Framework 3.5,Visual Studio 2008,Client:FCKEditor 2.6.5,Server:FCKEditor.Net 2.6.3

 

下载安装

FCKEditor包含了两部分:应用于客户端的FCKEditor 2.6.5 和 应用于.Net服务端的FCKEditor.Net 2.6.3,从官网下载解压后如下:

2.6.3是C#的源代码,如果不需要自定义修改对DLL进行重新编译,那么只要引用项目中的 bin\Debug\2.0\FredCK.FCKeditorV2.dll即可。因为FCKEditor提供了对多语言的支持,在2.6.4中能看到.PHP,.ASP,.PL,.CFC,.CFM等开发源文件后缀,在此我们只进行FCKEditor与.Net的结合应用,所以精减后如下:

fckconfig.js配置

工具栏菜单配置:

FCKConfig.ToolbarSets["Default"] = [
[
'Source','DocProps','-','Save','NewPage','Preview','-','Templates'],
[
'Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
[
'Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
[
'Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
'/',
[
'Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
[
'OrderedList','UnorderedList','-','Outdent','Indent','Blockquote','CreateDiv'],
[
'JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
[
'Link','Unlink','Anchor'],
[
'Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],
'/',
[
'Style','FontFormat','FontName','FontSize'],
[
'TextColor','BGColor'],
[
'FitWindow','ShowBlocks','-','About'] // No comma for the last row.
] ;

/*
- : 分隔线
/ : 换行

Source : 源代码
DocProps : 页面属性
Save : 保存
NewPage : 新建
Preview : 预览
Templates : 模版

Cut : 剪切
Copy : 复制
Paste : 粘贴
PasteText : 粘贴为无格式文本
PasteWord : 从MS-WORD粘贴
Print : 打印
SpellCheck : 拼写检查
Undo : 撤消
Redo : 重做
Find : 查找
Replace : 替换
SelectAll : 全选
RemoveFormat : 清除格式

Form : 表单
Checkbox : 复选框
Radio : 单选按钮
TextField : 单行文本
Textarea : 多行文本
Select : 列表/菜单
Button : 按钮
ImageButton : 图像域
HiddenField : 隐藏域

Bold : 加粗
Italic : 倾斜
Underline : 下划线
StrikeThrough : 删除线
Subscript : 下标
Superscript : 上标

OrderedList : 插入/删除编号列表
UnorderedList : 插入/删除项目列表
Outdent : 减少缩进量
Indent : 增加缩进量
Blockquote : 块引用
CreateDiv : 新增DIV标签

JustifyLeft : 左对齐
JustifyCenter : 居中对齐
JustifyRight : 右对齐
JustifyFull : 两端对齐

Link : 插入/编辑超链接
Unlink : 取消超链接
Anchor : 插入/编辑描点链接

Image : 插入/编辑图像
Flash : 插入/编辑FLASH
Table : 插入/编辑表格
Rule : 插入水平线
Smiley : 插入表情图标
SpecialChar : 插入特殊符号
PageBreak : 插入分页符

Style : 样式
FontFormat : 格式
FontName : 字体
FontSize : 大小

TextColor : 文本颜色
BGColor : 背景颜色

FitWindow : 全屏编辑
ShowBlocks : 显示区块
About : 关于FCKeditor
*/

其它配置:

/*回车键 [ p | div | br ]*/
FCKConfig.EnterMode
='br' ;
/*Shift + 回车键 [ p | div | br ]*/
FCKConfig.ShiftEnterMode
='p' ;

 

已发现BUG修复

修复文件上传脚本执行权限错误

参见:http://dev.fckeditor.net/attachment/ticket/2115/2115.patch

使用VS2005打开FredCK.FCKeditorV2.vs2005.csproj,打开FileBrowser > FileWorkerBase.cs,在第118行

Response.Write( @"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();" );
Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.parent.OnUploadCompleted;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");

 

插入/编辑超链接

原始:

简化:

如上图所示,对“插入/编辑超链接”功能进行了彻底简化,不可否认源代码中考虑了超链接的几乎所有应用情景,非常之完善和全面,甚至提供了服务器目录的浏览和文件上传功能,但这也恰恰暴露了更多的安全隐患,参考了各大网站的应用,简化为仅显示文本和链接地址。

这部分功能的源代码主要分布在两个文件中:

UI部分 /editor/dialog/fck_link.html

功能函数 /editor/dialog/fck_link/fck_link.js

因为原始版本包含了过于复杂的功能,所以在分析和修改js代码时也费了不少功夫,删除了多余的功能函数,支持了文本的显示,缩减大约90%的代码。具体细节可查看DEMO。

 

插入/编辑FLASH

原始:

简化:

如上图所示, 去除了服务器浏览和文件上传功能;去除了“高级”里的多余选项,将功能整合在一个标签下。

这部分功能源代码分布在三个文件中:

UI界面 /editor/dialog/fck_flash.html

功能函数 /editor/dialog/fck_flash/fck_flash.js

Flash预览 /editor/dialog/fck_flash/fck_flash_preview.html

 

插入/编辑表格

原始:

简化:

该部分功能主要包含在 /editor/dialog/fck_table.html 中,如上图,去除了“标题”、“摘要”、“标题单元格”三项,通过生成的HTML代码可以发现,FCKEditor是一款非常严谨的软件,它严格遵循了W3C。但大部开发人员都不熟悉的HTML标签对于用户来说使用就更较少了,固去除了这三项,原始与简化后控件生成的HTML差别如下:
原始:

<table border="1" cellspacing="1" summary="summary" cellpadding="1" width="200" align="center" height="200">
<caption>caption</caption>
<thead>
<tr>
<th scope="col">&nbsp;</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>

简化:

<table border="1" cellspacing="1" cellpadding="1" width="200" align="center" height="200">
<tbody>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>

 

插入/编辑图片

原始:

简化:

一直对 FCKEditor的上传功能不满意,首先是安全问题,“服务器浏览”这项功能之前就曾暴露过严重安全漏洞,所以完全屏蔽了这项功能,彻底删除了 /editor/filemanager/browser 目录;

其次是上传代码的扩展受限,比如上传图片时生成缩略图,实现这样一个常见的附加功能在FCKEditor现有结构下困难重重;

再有配置复杂且冗余,比如我的项目中十个页面用到了FCKEditor,每处的上传处理逻辑均不相同,这种需求难道要放十个FCK客户端十个配置?

在此并非刻意将FCK的上传批的一无是处,恰恰相反,我倒觉得FCK的开发团队是绝对的完美主义者,如果仔细研究了源代码你就会毫不怀疑这一点。他们要把这个控件做的支持多语言,多功能,又要保持绝对一至性。过于追求完美必然就会带来其它的牺牲。

OK,我决定重写FCKEditor的图片上传代码框架,仅争对.Net,目标是让开发者最简单最便捷的处理用户的上传。

概述一下我的想法:删除FCKEditor 中一切上传相关的配置,而只需指定一个参数:“我的上传处理类”。FCK会将上传交由这个类来处理,在这个类里你可以随心所欲的添加任何代码,权限控制、与数据库交互、图片处理、存放位置......所有逻辑均在这个类里实现,然后告诉FCK这个类的位置即可。下面详细描述现实的过程:

Step1.让FCKEditor将上传交由“我的上传处理类”

首先为FCKEditor添加属性“ImageUploadAssembly” ,这个属性告诉FCK“我的上传处理类”的位置,格式为“程序集名称,类名称”,后面将会在FCK中扩展代码利用反射来创建该类的对象,并将上传交由这个对象处理。在服务端代码FCKEditor.cs中添加属性:

///<summary>
/// 用于指明当前控件上传图片处理的类
/// 格式为“程序集名称,类名称”
///</summary>
[DefaultValue("")]
publicstring ImageUploadAssembly
{
get
{
object o = ViewState["ImageUploadAssembly"];
return (o ==null?"" : (string)o);
}

set
{
ViewState[
"ImageUploadAssembly"] = value;
}
}

接着在页面上添加FCKEditor控件时就能指定该属性了,如下代码所示:

<div>
<FCKeditorV2:FCKeditor
ID="fck" runat="server"
BasePath
="/fckeditorsimplenet265/"
Height
="400px"
ToolbarSet
="Default" ImageUploadAssembly="FCKEditorSimpleNet.Web,FCKEditorSimpleNet.Web.MyImageUploadHandler.Default">
</FCKeditorV2:FCKeditor>
</div>

然后是一个关键:如何将属性值传递到FCK中的Upload.cs中?花了不少时间对FCK源代码进行研究,整理出如下步骤:

1、首先将属性值以URL方式传递到 /editor/fckeditor.html中,如 /editor/fckeditor.html?ImageUploadAssembly=FCKEditorSimpleNet.Web,FCKEditorSimpleNet.Web.MyImageUploadHandler.Default ,这一步需要修改服务端Fckeditor.cs,如下:

protectedoverridevoid Render(HtmlTextWriter writer)
{
//....部分代码略
if ( _IsCompatible )
{
//....部分代码略

//将图片上传处理程序集,类名称作为参数传递到 Fckeditor.html
if (this.ImageUploadAssembly.Length >0)
sLink
+="&amp;ImageUploadAssembly="+this.ImageUploadAssembly;
}
}

2、从 fckeditor.html 将参数传递到 /editor/fckdialog.html ,这个过程包含在 /editor/js/fckeditorcode_gecko.js 和 /editor/js/fckeditorcode_ie.js 中,这两个js是经过min处理的,分别对应了IE和非IE浏览器,两个文件基本一至,只是JS写法上稍有差别,修改如下:

O.src=FCKConfig.BasePath+'fckdialog.html?ImageUploadAssembly='+(FCKURLParams['ImageUploadAssembly']||'');

3、从 fckdialog.html 将参数传递到 /editor/dialog/fck_image.html

在fckdialog.html定义函数getUrlParameter,该函数供iframe子页面调用,以获取Url参数值:

//获取fckdialog.html的参数
function getUrlParameter(pName){
var pVal ="";
var aParams = document.location.search.substr(1).split('&');
if(aParams.length >0){
for(var i =0; i < aParams.length; i++ ){
var aParam = aParams[i].split('=') ;
var sParamName = decodeURIComponent( aParam[0] );
var sParamValue = decodeURIComponent( aParam[1] );
if(pName.toUpperCase() == sParamName.toUpperCase()){
pVal
= sParamValue;
}
}
}
return pVal;
}

4、从 fck_image.html 将参数传递到 Uploader.cs

fck_image.html 会将图片放在一个INPUT中,然后POST到服务端(ASPX),INPUT的名称为NewFile,服务端ASPX对应的Class就是Uploader.cs,现在要添加一个ImageUploadAssembly属性值一并提交给服务端,在此使用GET方式在URL后附加一个参数,修改 /editor/dialog/fck_image/fck_image.js 的 window.onload 函数,将参数值附加到 form.action,如下:

window.onload =function()
{
//....部分代码略
GetE('frmUpload').action = FCKConfig.BasePath +"filemanager/image/upload.aspx?ImageUploadAssembly="+ dialog.getUrlParameter("ImageUploadAssembly");
}

OK,至此在Uploader.cs里用Request.QueryString["ImageUploadAssembly"]就能获取到我们设置的属性值了。

接下来是根据该属性的值动态构造出对象,这里利用了.Net的反射,帖出Uploader.cs代码:

using System;
using System.Reflection;
using System.Web;

namespace FredCK.FCKeditorV2.ImageUpload
{
///<summary>
/// 图片上传接口
/// 接收GET参数 ImageUploadAssembly,根据参数值反射出自定义图片处理对象
///</summary>
publicclass Uploader : System.Web.UI.Page
{
protectedoverridevoid OnLoad(EventArgs e)
{
if (Request.QueryString["ImageUploadAssembly"] !=null)
{
string ass = Request.QueryString["ImageUploadAssembly"].Trim();
if (ass !=string.Empty)
{
string[] assArray = ass.Split(',');

if (assArray.Length ==2)
{
if (assArray[0].Trim() !=string.Empty && assArray[1].Trim() !=string.Empty)
{
//获取上传文件
HttpPostedFile hpf = Request.Files["NewFile"];
if (hpf !=null)
{
FredCK.FCKeditorV2.ImageUpload.Base customUploader
=null;
bool ok =true;

try
{
customUploader
= (FredCK.FCKeditorV2.ImageUpload.Base)this.createObject(assArray[0].Trim(), assArray[1].Trim());
customUploader.PostFile
= hpf;
}
catch
{
ok
=false;
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "自定义处理类不存在或设置的值不正确!< /span>");
}

if (ok)
{
//调用自定义类中的Save方法< /span>
customUploader.Save();
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "未获取上传文件!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "参数有误!");
}
}

///<summary>
/// 反射创建对象
///</summary>
///<param name="assemblyName">程序集名称</param>
///<param name="classFullName">完整类名称</param>
///<returns></returns>
privateobject createObject(string assemblyName, string classFullName)
{
return Assembly.Load(assemblyName).CreateInstance(classFullName);
}
}
}

至此上传逻辑告一段落,因前面的描述和代码即有客户端的又有服务端的,并且只是核心代码片断,不知各位有没有看明白。总结一下这部分的逻辑:在客户端定义一个参数,将该参数传递到服务端,服务端根据参数值构造对象。如下图所示:

Step2.自定义上传处理类的约束

很庆幸可以在.Net里用一个类来处理用户的上传,并且有反射这样的技术来动态构造对象。这段时间在写一些ANSI C,我想也只有在写C的时候才能越发体会C#、高级语言、OOP的好。所有代码都是对现实一件事情的抽象,用C#你的代码可以跟思维同步,而如果用C你就得考虑计算机是否明白和接受你的思维和代码了。

进入正题,分析一下自定义上传处理类的逻辑:首先得接收文件,.Net中是一个HttpPostedFile对象;然后对文件进行处理、保存等系列操作;最后将结果告诉FCKEditor,由它来控制UI及用户交互。把这些必需的逻辑封装成一个抽象父类,每个用户自定义的上传处理类必需从该类继承,并实现相应的抽象方法:

using System;
using System.Web;

namespace FredCK.FCKeditorV2.ImageUpload
{
///<summary>
/// 图片上传的抽象基类
///</summary>
publicabstractclass Base : System.Web.UI.Page
{
private System.Web.HttpPostedFile mPostFile;

///<summary>
/// 上传文件
///</summary>
public System.Web.HttpPostedFile PostFile {
get { returnthis.mPostFile; }
set { this.mPostFile = value; }
}

///<summary>
/// 文件上传结束回调客户端
///</summary>
///<param name="isSucceed">上传是否成功</param>
///<param name="fileUrl">上传完成后该文件的URL< /span></param>
///<param name="customMsg">自定义消息</param>
publicstaticvoid SendFileUploadResponse(bool isSucceed, string fileUrl, string customMsg)
{
System.Web.HttpContext.Current.Response.Clear();
System.Web.HttpContext.Current.Response.Write(
"<script type='text/javascript'>");
System.Web.HttpContext.Current.Response.Write(
@"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");
System.Web.HttpContext.Current.Response.Write(
"window.parent.OnUploadCompleted("+ isSucceed.ToString().ToLower() +", '"+ fileUrl +"', '"+ customMsg +"');");
System.Web.HttpContext.Current.Response.Write(
"</script>");
System.Web.HttpContext.Current.Response.End();
}

///<summary>
/// 图片文件处理及保存
/// 无论结果如何,类的逻辑终点处必需调用 SendFileUploadResponse 以响应FCK客户端
///</summary>
publicabstractvoid Save();

}
//end class
}

最后是自定义上传处理类,里面可以根据自己情况添加任何逻辑,但它必须继承于FredCK.FCKeditorV2.ImageUpload.Base,并且在处理终点调用SendFileUploadResponse方法,以告之Fck客户端进行后继处理,如下代码示例了对上传图片大小、文件类型验证、文件保存:

using System;
using System.Web;

namespace FCKEditorSimpleNet.Web.MyImageUploadHandler
{
publicclass Default : FredCK.FCKeditorV2.ImageUpload.Base
{
///<summary>
/// 允许的上传图片大小(KB)
///</summary>
privateconstint FILE_MAX =1024;

publicoverridevoid Save()
{
if (this.PostFile !=null)
{
if (this.PostFile.ContentLength >0)
{
if (this.PostFile.ContentLength <= FILE_MAX *1024)
{
if (this.PostFile.ContentType =="image/pjpeg"||this.PostFile.ContentType =="image/jpeg"||this.PostFile.ContentType =="image/gif"||this.PostFile.ContentType =="image/bmp"||this.PostFile.ContentType =="image/png")
{
//图片处理扩展

//保存
string fileName = Guid.NewGuid().ToString() +".jpg";
string path = HttpRuntime.AppDomainAppPath +@"fck_upload\";
this.PostFile.SaveAs(path + fileName);

//客户端响应
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(true, "/fck_upload/"+ fileName, "图片上传成功!");
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "图片格式不正确!目前支持JPG、GIF、BMP与PNG格式");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "图处大小不能超过"+ FILE_MAX.ToString() +"KB!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "未获取图片数据!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(
false, "", "未获取图片对象!");
}
}

}
//end class
}

 

DEMO下载

点击下载FCKEditorSimpleNet 2.65 
 

结束语

从学习的角度对FCKEditor原代码进行分析和作了部分修改,因FCKEditor是一个复杂的基于JS的开源软件,更多细节难以在文章中描述,可下载查看DEMO深入了解,希望对有需要的朋友带来帮助。同时因个人能力、时间、精力有限,不足之处还请指正,后继也会进一步完善本文,包括FCKEditor的JS框架、配置、Dialog实现、JS拖拽实现、窗体关系、多语言实现......每个细节几乎都能作为一个独立项来研究,之后再逐渐补充完整。

 

<全文完>

 

posted @ 2011-08-31 18:20  吴 剑  阅读(4405)  评论(4编辑  收藏  举报
@2010-2017 WuJian, All Rights Reserved.