吕克让的资料库 - lhelper's repository

克己服人,礼智谦让!
Weblcome to lhelper's repository!

Thursday, August 17, 2006

 

Encounter Toolkit.getImage()'s unintended cache

在借助ImageJ 的 ij.jar 做图片处理的过程中遇到一个奇怪的问题:原始的图片文件被修改之后,基于“新图片”的再加工经常(1)不能生效,再加工的结果往往是基于原始图片的。

通过在程序的关键位置加入调试信息,开启ImageJ 的调试开关(IJ.debugMode = true;),最后发现问题发生在new java.io.Opener().openImage(source) 这一步。查看Opener 的源代码,发现了

Toolkit.getDefaultToolkit().getImage(dir+name);

总算定位到了问题的所在:)。下面是在mindprod.com 上找到的有关getImage cache 的讲解:

getImage() caches results. If you change the disk file of the image, and refetch you will likely get the old version again. Use createImage can bypass the caching.

——其实JDK 的Doc 中就有相关的说明,只是搜索引擎让我变懒了,把它给忽视了!

接下来,修改Opener 的源代码(用createImage 代替 getImage)、编译、把Opener.class重新打包入ij.jar中,最终问题“算是”得到解决!

一直想用GraphicsMagick (derived from ImageMagick)来代替 ImageJ 做图片处理——ImageJ 在处理某些特殊格式的图片文件时显得有些力不从心,很多图像处理算法相比流行的图片处理软件也相差甚远——个人感觉ImageJ 更适于拿来解决“从无到有”的温饱问题。

P.S. IrfanView 的图像处理算法相当不错,只可惜作者没有提供基于它的SDK,别人无法基于它来做二次开发,可惜!

参考:


Tuesday, July 04, 2006

 

TrackChanges.js:用JavaScript 记录用户在表单中所作的修改

原理:
1.为form 中的每个field (submit/reset/button/image除外)生成一个虚假的INPUT element,用于保存页面装载时form 中每个element的初始值。

2.在form 提交之前,比较form 中的每个field 与上面一步生成的虚假的INPUT element,记录二者的差别(diff)。然后合并所有的差别并赋值到另一个新的INPUT element中。

这样,用户在前台操作过程中所修改的内容就可以随form 一起提交到服务器端进行处理了。

1. trackChange.js



/**********************************************************
TrackChanges v0.1 By lhelper <lhelper at gmail dot com>



track changes of client end form fields
1. append dummy input fields onto the raw form, which has
the origianl value, checked status, etc. as the real one

2. compare the dummy input fields with the originals, record
the changes (unix/linux diff format) with one more real
field, whose name is '_tc_c' (default)
**********************************************************/
var TrackChanges={
initialize : function _init(frm) {
if(!frm) { // smart check
return;
}
var eles=frm.elements;
for(var i=0, n=eles.length; i<n; i++) {
var ele=eles[i];
// alert(ele.tagName + ":" + ele.type + ":" + ele.name + ":" + ele.value);
// 1. omit the submit button (submit, reset, button, image, etc.)
if(ele.tagName=="INPUT") {
if(ele.type=="submit" ele.type=="reset" ele.type=="button" ele.type=="image") {
continue;
}
if(ele.type!="checkbox" && ele.type!="radio" && document.getElementsByName(ele.name).length > 1) {
alert("There are " + document.getElementsByName(ele.name).length + " field elements with the same name (" + ele.name + "), which can result in unbelievable output!");
return false;
}
}



// 2. append dummy field _tc_f[ele.name][ele.id][0] onto the form
var obj=document.createElement("INPUT");
obj.type="hidden";
//obj.type="text";
if(ele.tagName=="INPUT" && (ele.type=="checkbox" ele.type=="radio")) {
if(!ele.id) {
ele.id=ele.name + "_" + ele.value;
}
obj.name="_tc_f[" + ((ele.type=="checkbox") ? "checkbox" : "radio") + "][" + ele.name + "][" + ele.id + "][0]";
obj.checked=ele.checked;
} else {
if(!ele.id) {
ele.id=ele.name;
}
obj.name="_tc_f[input][" + ele.name + "][" + ele.id + "][0]";
}
obj.value=ele.value;
obj.disabled=true;
frm.appendChild(obj);
}
},



recordChanges : function _diff(frm, fn) {
if(!frm) { // smart check
return;
}
var cmsDiff="";
for(var eles=frm.elements, i=0; i<eles.length; i++) {
var re=new RegExp("_tc_f\\[(.*?)\\]\\[(.*?)\\]\\[(.*?)\\]\\[0\\]");
var m=re.exec(eles[i].name);
if(m == null) {
continue;
}
if(m[1] == "checkbox" m[1] == "radio") {
if(document.getElementById(m[3]).checked != eles[i].checked) {
cmsDiff+="< " + m[3] + ":" + eles[i].checked + "\n----------\n" + "> " + m[3] + ":" + !eles[i].checked + "\n\n";;
}
} else {
if(document.getElementById(m[3]).value != eles[i].value) {
cmsDiff+="< " + m[3] + ":" + eles[i].value + "\n----------\n" + "> " + m[3] + ":" + document.getElementById(m[3]).value + "\n\n";;
}
}
}
//alert(cmsDiff);
var obj=document.createElement("INPUT");
obj.type="hidden";
//obj.type="text";
obj.name=(fn) ? fn : "_tc_c";
obj.value=cmsDiff;
frm.appendChild(obj);
}
};




2. 使用方法:



<script language=JavaScript>
<!--
function _submit(frm) {
// 记录用户修改的内容
TrackChanges.recordChanges(frm);


frm.method="post";
frm.action="news_edit_save.jsp";
return true;
}
// -->
</script>
......



<form method="POST" action="/dev/null" name="news_info" onSubmit="return _submit(this)">
<input type="text" name="news_title" maxlength="50" value="">
......
<input type="submit" value="确认录入" name="btnSave" class="submit">
</form>


<script language="javascript">
<!-- //
// 初始化 TackChanges
TrackChanges.initialize(document.news_info);
// -->
</script>


 

Hibernate 的 "SQL insert, update or delete failed (row not found)" 异常

关键字:hibernate, trigger(触发器), store procedure(存储过程), NonBatchingBatcher,rowCounts, updateCount


在借助hibernate[1] 执行更新操作,包括插入(insert)/修改(update)/删除(delete)操作的过程中,如果数据库一方返回的update count 与 hibernate 所预期的 count[2] 不同,那么hibernate 的Batcher 就会抛出异常:



HibernateException("SQL insert, update or delete failed (row not found)");

下面是一段完整的错误信息:


[ERROR][tcpConnection-8080-3] - Could not synchronize database state with session
org.springframework.orm.hibernate.HibernateSystemException: SQL insert, update or delete failed (row not found); nested exception is net.sf.hibernate.HibernateException: SQL insert, update or delete failed (row not found)
net.sf.hibernate.HibernateException: SQL insert, update or delete failed (row not found)
at net.sf.hibernate.impl.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:25)

该异常通常是由于更新过程中所涉及的触发器(trigger) 或存储过程(store procedure) 中又包含有更新操作,使得数据库返回的update count 为整个过程中所涉及的所有count 的总和。


一般情况下可以通过在触发器或存储过程中加入 'SET NOCOUNT ON' 声明的方式来解决问题,如:
CREATE TRIGGER tri_t1_delete
ON t1
FOR DELETE
AS
SET NOCOUNT ON -- set 'NOCOUNT' to ON, so the count (indicating the number of rows affected by a Transact-SQL statement) is not returned.
DELETE t2
FROM t2 AS a INNER JOIN deleted AS b
ON a.id=b.id



但似乎也有例外,如:
http://sourceforge.net/forum/message.php?msg_id=3074706



下面是hibernate(2.1)的NonBatchingBatcher (Batcher 的具体实现)比较数据库一方返回的update count 与程序预期的 count 是否相同并伺机抛出异常的程序片断:


public void addToBatch(int expectedRowCount) throws SQLException, HibernateException {
int rowCount = getStatement().executeUpdate();
//negative expected row count means we don't know how many rows to expect
if ( expectedRowCount>0 && expectedRowCount!=rowCount )
throw new HibernateException("SQL insert, update or delete failed (row not found)");
}



注:
[1]: 笔者使用的是2.1 版本的hibernate 发行包
[2]: 通常是 1,


参考:
About 'SET NOCOUNT ON'
http://msdn.microsoft.com/library/en-us/tsqlref/ts_set-set_3ed0.asp?frame=true


 

JavaCC based Simple-QueryParser For Lucene

修改了Lucene 自带的QueryParser,
屏蔽了Token Definitions 中的部分内容,去掉了对:
RangeQuery、
WildcardQuery、
PrefixQuery、
( Query )、
boost、
slop
等的直接支持——以上几项可以改为用Filter 来实现

加入了对不完整表达式的容错处理,减少ParserException 的发生:
Java AND AND Lucene
Java AND "Lucene
Java AND Lucene"
Java AND
AND Lucene
title:
:Java

加入validFieldSet,用于限制可以出现在":" 左侧的有效field,防止:
tom said: "hi everyboy"
主持人:"大家好"
等被解析成错误的Query



如果Analyzer 过滤了所有term 的话,则让parser() 方法返回null,如:
+AND a
+OR NOT the was will
parser() 方法返回null 的情况下,可以仿照google 将请求转向到另外一个页面。


当整个Query 中的所有BooleanClause 都是 prohibited (Pure NOT Query) 的情况下,可以选择:
将第一个clause 的prohibited 属性至为false
在当前clauses 的基础上再加一个MatchAllDocsQuery (http://lucene.apache.org/java/docs/api/org/apache/lucene/search/MatchAllDocsQuery.html)
直接返回pure-NOT-query



屏蔽了部分内容之后的 Token Definitions (/// 为注释部分):


/* ***************** */
/* Token Definitions */
/* ***************** */


<*> TOKEN : {
<#_NUM_CHAR: ["0"-"9"] >
<#_ESCAPED_CHAR: "\\" [ "\\", "+", "-", "!", "(", ")", ":", "^",
"[", "]", "\"", "{", "}", "~", "*", "?" ] >
<#_TERM_START_CHAR: ( ~[ " ", "\t", "\n", "\r", "+", "-", "!", "(", ")", ":", "^",
"[", "]", "\"", "{", "}", "~", "*", "?" ]
<_ESCAPED_CHAR> ) >
<#_TERM_CHAR: ( <_TERM_START_CHAR> <_ESCAPED_CHAR> "-" "+" ) >
/// <#_WHITESPACE: ( " " "\t" "\n" "\r") >
<#_SKIP_CHAR: (" " "\t" "\n" "\r" "(" ")" "{" "}" "[" "]" "^" "~" "*" "?") >
}



/// <DEFAULT, RangeIn, RangeEx> SKIP : {
/// <<_WHITESPACE>>
/// }
<DEFAULT> SKIP : {
<<_SKIP_CHAR>>
}


// OG: to support prefix queries:
// http://nagoya.apache.org/bugzilla/show_bug.cgi?id=12137
// Change from:
// <WILDTERM: <_TERM_START_CHAR>
// (<_TERM_CHAR> ( [ "*", "?" ] ))* >
// To:
//
// <WILDTERM: (<_TERM_CHAR> ( [ "*", "?" ] ))* >



<DEFAULT> TOKEN : {
<AND: ("AND" "&&") >
<OR: ("OR" "") >
<NOT: ("NOT" "!") >
<PLUS: "+" >
<MINUS: "-" >
/// <LPAREN: "(" >
/// <RPAREN: ")" >
<COLON: ":" >
/// <CARAT: "^" > : Boost
<BILATERAL_QUOTED: "\"" (~["\""])+ "\"">
<UNILATERAL_QUOTED: "\"" (~["\""])+>
<QUOTE_AT_THE_END: "\"">
<NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? >
<TERM: <_TERM_START_CHAR> (<_TERM_CHAR>)* >
/// <FUZZY: "~" >
/// <SLOP: "~" (<_NUM_CHAR>)+ >
/// <PREFIXTERM: <_TERM_START_CHAR> (<_TERM_CHAR>)* "*" >
/// <WILDTERM: <_TERM_START_CHAR>
/// (<_TERM_CHAR> ( [ "*", "?" ] ))* >
/// <RANGEIN_START: "[" > : RangeIn
/// <RANGEEX_START: "{" > : RangeEx
}



/// <Boost> TOKEN : {
/// <NUMBER: (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? > : DEFAULT
/// }


/// <RangeIn> TOKEN : {
/// <RANGEIN_TO: "TO">
/// <RANGEIN_END: "]"> : DEFAULT
/// <RANGEIN_QUOTED: "\"" (~["\""])+ "\"">
/// <RANGEIN_GOOP: (~[ " ", "]" ])+ >
/// }



/// <RangeEx> TOKEN : {
/// <RANGEEX_TO: "TO">
/// <RANGEEX_END: "}"> : DEFAULT
/// <RANGEEX_QUOTED: "\"" (~["\""])+ "\"">
/// <RANGEEX_GOOP: (~[ " ", "}" ])+ >
/// }


Thursday, November 03, 2005

 

crawl-urlfilter.txt vs. regex-urlfilter.txt in nutch

crawl-urlfilter.txt 之于 regex-urlfilter.txt
注:写这些内容的时候,作者运行的是nutch 的0.7 release;如果你运行的是nutch 的其他版本,那么你可能需要自己去分析二者的区别:*)
首先有一种说法是:crawl-urlfilter.txt 是供intranet 抓取用的,而regex-urlfilter.txt 是供internet 抓取用的——因为两者抓取的重点不同,从而导致过滤规则不同。如果你只是想知道两者的简单区别的话,那么看到这里也就可以了,但是如果你想知道更多细节那么下面的内容还是值得一看的:)
crawl-urlfilter.txt 和 regex-urlfilter.txt 都是用来保存过滤url 的正则表达式的。类 RegexURLFilter(org.apache.nutch.net.RegexURLFilter) 通过 NutchConf.get().get("urlfilter.regex.file") 来从中(当然如果你装载了其他配置文件的话,也可能有其他的候选者)进行选择。
regex-urlfilter.txt 是 urlfilter.regex.file 属性的缺省值(1),定义在 $NUTCH_JAVA_HOME/conf/nutch-default.xml(如果你设置了环境变量NUTCH_CONF_DIR 的话,那就是 $NUTCH_CONF_DIR/nutch-default.xml)中。该值可以被后续加载的配置文件所覆盖,例如如果你装载了类 CrawlTool(org.apache.nutch.tool.CrawlTool),那么缺省值就会被 crawl-tool.xml 中的urlfilter.regex.file 属性覆盖(2)。也就是说,如果你在操作过程中调用了类CrawlTool,那么类RegexURLFilter 将会采用crawl-tool.xml 中指定的文件,否则就用缺省的文件。
当然如果nutch-site.xml 中也定义了urlfilter.regex.file 属性的话,那么 NutchConf.get().get("urlfilter.regex.file") 返回的值就以nutch-site.xml 所指定的值为准。(关于nutch 的resource chain)
注:(1)NutchConf 初始化时会装载nutch-default.xml 和nutch-site.xml
public class NutchConf {
......
public NutchConf() {
resourceNames.add("nutch-default.xml");
resourceNames.add("nutch-site.xml");
}
......
}
(2)装载类CrawlTool 时,crawl-tool.xml 会被插入到resource chain 中
public class CrawlTool {
......
static {
NutchConf.get().addConfResource("crawl-tool.xml");
}
......
}
参见:
What's the difference between crawl-urlfilter.txt and regex-urlfilter.txt?

 

hack URLFilters so as to test RegexURLFilter

只是简单给URLFilters(org.apache.nutch.net.URLFilters)加了一个 main(String[]) 方法 :*)
public static void main(String args[]) throws IOException, MalformedPatternException, Exception {
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
String line;
while((line=in.readLine())!=null) {
String out=URLFilters.filter(line);
if(out!=null) {
System.out.print("+");
System.out.println(out);
} else {
System.out.print("-");
System.out.println(line);
}
}
}

这样测试url filter rule 就方便多了:)
$ ./bin/nutch org.apache.nutch.net.URLFilters
......
http://www.lhelper.org/blog/
using fitler 0:org.apache.nutch.net.RegexURLFilter
+http://www.lhelper.org/blog/
http://www.lhelper.org/blog/?q=lhelper
using fitler 0:org.apache.nutch.net.RegexURLFilter
-http://www.lhelper.org/blog/?q=lhelper

 

fix bugs of nutch's demo webapp

修正nutch (0.7 release) 的demo 中的几个小bug。


Monday, June 21, 2004

 

一项关于开源软件的有趣调查-Who Is Doing It?

有趣的调查-Who Is Doing It?
Who Is Doing It?



 

about security

about security
http://bbs.chinaunix.net/forum/viewtopic.php?t=335596
  



 

NULLS FIRST/LAST

NULLS FIRST/LAST
SELECT first_name, last_name, salary, commission_pct
FROM employees
ORDER BY commission_pct NULLS FIRST;

=====details=====
http://www.ocphk.org/newsletters/200404/oracle_faq.jsp

Question 2:
When I do a query with an order by column with NULL values, the NULLs seem to be the largest values in the column. How can I change this behaviour (to make NULL as the smallest value)?
Answer 2:
Do you know there is a new option called NULLS FIRST/LAST in the ORDER BY clause? Starting from 8.1.6, you can use this option to control the sequence of the NULL values:
SELECT first_name, last_name, salary, commission_pct
FROM employees
ORDER BY commission_pct NULLS FIRST;


Remember, this option dose not made NULLs the largest/smallest, it just made the NULLs appear on the top/bottom of your query.




Thursday, June 10, 2004

 

实现不同域(Domain)之间的数据交换

引言


前些天在对公司原有的 web 应用进行改版时遇到一个问题,当时需要从原有的应用中提取出一部分,用一个更为通用的来进行替换,并且仍然保留原有的应用接口。原有的应用属于 news.mycompany.com 域,而新应用将被部署到 upload.mycopany.com。当我试着从新的域向 news.mycompany.com传递数据时,在前台遇到了浏览器返回的“拒绝访问(Access Denied)” 的错误信息,通过参考在 google 中查到的大量英文资料找到了问题的症结,并通过指定两个域中页面的 docment.domain 属性使问题得到了部分解决。后来一时兴起在google 中查找与 document.domain 相关的中文资料,但得到的大部分是网络安全方面的文章,很少有文章提及通过指定页面的document.domain 属性能够实现两个域之间的数据交换,于是决定写下此文,希望能够做到抛砖引玉吧。

关键字: JavaScript, Domain, Access Denied
日期:2003-07-06

问题的提出


在开发 Web 应用时经常会遇到需要在两个帧之间传递数据的情况,这里的帧可以是 frameset 中的 frame也可以是独立的窗口。常见的情况是一个帧作为应用的主体,另一个帧则提供一些供用户选择的选项,用户选择完毕后,该帧把用户作出的选择发送到服务器并向主要的帧传递一些信息,这里的信息可能是用户的选择也可能是服务器返回的数据。当两个帧中的内容同属于一个域时实现以来比较简单,但是当它们分属于不同域时问题就变得复杂而棘手了,因为这里涉及到了数据访问的安全性问题,搞不好就会遇到浏览器返回的“拒绝访问(Access Denied)” 的错误信息。

可能的解决方案


下面我们将通过几个试验来分析一下在分属于不同域的帧之间传递数据的一些方法。

方案一


用客户端脚本实现两个帧之间的数据交换应该是最为轻量级的方式之一了,这样做不会增加服务器的负载也不会占用网络带宽,数据交换完全是在客户端完成。下面就让我们先来了解一下用客户端脚本(以JavaScript 为例)和窗口句柄如何实现一个域内的数据交换。

我们通过一个实例来进行说明:假设需要给用户提供一个新闻的录入界面,用户可以用它录入新闻的原始内容,并且可以在其中嵌入一副图片。为了实现这个功能界面我们设计了两个帧,或者说是两个窗口:

为了简单起见,我们假设两个窗口中的内容都是静态的,主窗口对应的文件为 NewsEdit.html,弹出窗口对应的文件为 ImgUpload.html(而大多数情况下两个窗口的内容都应该是动态生成的)。

其中 NewsEdit.html 位于 news.mycompany.com 的主目录下,其源代码如下所示:






<!-- File: NewsEdit.html
(http://news.mycompany.com/NewsEdit.html) -->
<html>
<head>
<title>The Content Editing Interface</title>
<meta http-equiv="Content-Type" content="text/html;
charset=gb2312">
<script language="JavaScript">
<!--
  /* OpenWin 用来在一个弹出窗口中显示 ImgUpload.html 的内容*/
  function OpenWin(){//Open window
    url='http://news.mycompany.com/upload/ImgUpload.html';
    newwindow =
window.open(url,"ImgUpload","height=135,width=300");
    if (!newwindow.opener) newwindow.opener=self;
  }
-->
</script>
</head>

<body>
<h2>Edit your content here</h2>
<!-- 调用后台应用 newsedit 来保存新闻内容 -->
<form action="http://news.mycompany.com/newsedit"
method="post" name="addnews">
  <!-- 新闻标题 -->
  Title:<input type="text" name=" style="color: rgb(255, 0, 0);">title"><br>
  <!-- 新闻作者 -->
  Author:<input type="text" name=" style="color: rgb(255, 0, 0);">author"><br>
  The content <br>
  <!-- 新闻内容 -->
  <textarea name="contentBody"
cols="100" rows="10"></textarea>
  <br>
  <!-- 点击连接打开上传图片的小窗口 -->
  <a href="JavaScript:OpenWin( style="color: rgb(255, 0, 0);">)">Upload Image
File</a>
  <br>
  <!-- UserImg 用来预览上传成功后的图片文件 -->
  <img name="UserImg"
style="width: 100px; height: 100px;" src="" border="1">
  <br><br>
  <input type="submit" name="SaveContent"
value="Submit">
  <input type="reset" name="ClearContent"
value="Reset">
</form>
</html>


ImgUpload.html 位于 news.mycompany.com 的 upload 子目录下,其源代码如下所示:






<!-- File: ImgUpload.html
(http://news.mycompany.com/upload/ImgUpload.html) -->
<html>
<head>
<title>Imgage Upload Interface</title>
<meta http-equiv="Content-Type" content="text/html;
charset=gb2312">
</head>

<body>
<h2>Image Upload</h2>
<!-- 调用后台应用来处理上传的图片 -->
<form action="http://news.mycompany.com/upload/imgupload"
method="post" enctype="multipart/form-data" name=" style="color: rgb(255, 0, 0);">upload">
  <!-- 由用户选择本地文件 -->
  <input type="file" name="imgfile">
  <input type="submit" name="Submit" value="Upload">
</form>
</html>


另外介绍一下我们的两个后台应用:

下面是 imgupload 处理完 POST 请求后返回的页面内容,该内容显示在 ImgUpload.html 所占据的弹出窗口中:






<html>
<head>
<title>File Upload Successfully</title>
</head>
<body>
<h3>File Uploaded Successfully!</h3>
<script language="JavaScript">
  <!-- 获取主窗口的句柄 -->
  parwin=self.opener;
  <!-- 获取对 img 元素的引用,并用上传文件的 url 为 img 元素的 src 属性赋值,这样在客户端就可以预览了 -->
  <!-- 为了简化问题,我们将对 img 元素的引用直接写在程序中 -->
  parwin.addnews.UserImg.src=" style="color: rgb(255, 0, 0);">http://news.mycompany.com/img/2003_07/06/1057478464859.gif";
</script>
</body>
</html>


返回的页面除了显示成功信息外,还利用脚本向主窗口传递了上传文件的 url,具体过程是:
首先通过 "self.opener" 获得主窗口(即 NewsEdit.html 所在窗口)的句柄;然后用上传文件的 url 对主窗口中UserImg 元素的 src 属性进行赋值,这样在主窗口中就可以看到上传后的图片了。

好了,我们的第一个实验已经成功了,实验结果告诉我们:当两个帧中的内容同属于一个域时,利用客户端脚本和窗口句柄在其中传递数据是没有问题的。接下来我们把ImgUpload.html 和 imgupload 从 news.mycompany.com 提取出来,部署到 img.mycompany.com 的对应目录下,并修改 NewsEdit.html 中引用 ImgUpload.html 时的url。这样当我们试着用 JavaScript 从 img.yourcompanu.com 向 bbs.yourcompany.com 传递数据时,浏览器就会弹出“拒绝访问(Access Denied)” 的错误框,表明我们违反了他的安全策略,并且数据无法正常传递过来。

style="text-align: left; width: 100%; background-color: rgb(204, 204, 204);">





其实,你可以直接把实验一中 imgupload
返回的内容另存为一个文件并部署到 img.mycompany.com,在 NewsEdit.html 中调用 window.open()
方法直接引用这个文件就可以进行测试了。


我们之所以会遇到“拒绝访问(Access Denied)” 的的错误信息,其原因在于:
最初,浏览器的开发商、开发团体出于安全性的考虑,默认情况下是不允许在分属于不同域的页面之间进行数据交换和方法调用的,当遇到这种情况时浏览器就会返回“拒绝访问(Access Denied)”的错误。

“那为什么即使我的两个页面属于同一个域我还是会遇到‘拒绝访问’的错误呀?”
如果是这种情况,那就要看你的弹出窗口中的内容是否始终属于同一个域,看一下你的 ImgUpload.html
是不是调用了属于其他域的应用,并且该应用在窗口中重新写入了内容,如果是这样那你的弹出窗口就变质了,它最后属于另外一个域,你当然会遇到“拒绝访问”的错误。

“这么说如果两个页面分属于不同域的话我们就无法在两个窗口之间传递数据了吗?”
事实基本上是这样的--一个令人沮丧的消息。
但答案也并非绝的--好像还有希望。

是的,一些浏览器的开发商、开发团体在开发高版本的浏览器时对原有策略进行了部分调整,这些调整给我们带来了一线生机:
当两个页面在进行数据交换时,浏览器会首先比较两个页面的 domain 属性,如果 domain
属性相同,那么浏览器就允许它们之间的数据交换,否则就返回“拒绝访问(Access Denied)”的错误。

“那么我们如何才能蒙蔽浏览器,让它认为两个页面的 domain
属性相同呢?”

这就要靠脚本来实现了,在 JavaScript 中我们可以通过在页面中加入如下声明来强制指定页面所属的域。

<script language="JavaScript">
<!--
document.domain = "mycompany.com"; //指定 document 所属的域
-->
</script>

加入上面的声明就可以蒙蔽浏览器,在原本属于两个不同域的页面之间进行数据交换了。但需要注意:只有把上面的声明加入到需要进行数据交换的所有文件中才会有效,只在一个域的文件中加入上面的声明是不起作用的。另外,声明部分最好能插入到页面的
<head></head> 标记中间,这一点也是用脚本进行开发时所被提倡的。有关 JavaScript 中的
document 和 domain 等可以参考 http://www.werelight.com/docs/JavaScript_Quick_Reference.htm


“使用这种方法有什么限制码?”
因该说用这种方法来实现不同域之间的数据传递还是有很多的限制的,主要表现为以下两点:







利用其他脚本,如 vbscript 或 jscript
实现这种跨域的数据交换其原理与用 JavaScript 是一样的,大家可以参考相关资料来实现。

方案二


下面我们来看一下利用 MSIE 提供的对话框能不能解决两个域之间的数据交换问题
首先我来简单介绍一下 MSIE 对话框:MSIE 提供的 showModalDialog
showModelessDialog
方法可以用来在一个单独的帧中显示一个模态或非模态对话框,两个方法都通过一个 URI 参数来指定对话框帧中的内容;可选的参数 vArguments
用来向对话框帧传递任何类型(包括数组类型)的参数;另外还有一个可选的参数 sFeatures 是用来定义对话框帧的显示效果,如位置、字体等等的;







注意 Netscape Navigator 、Mozilla 和
Opera 浏览器是没有与之对应的方法的,也就是说除了 MSIE 之外其他几大浏览器都不支持用 showModalDialog 或
showModelessDialog 显示对话框,如果你感兴趣的话这里有一篇文章能够教你如何通过其他方式来模拟一个模态对话框,详见 href="http://developer.netscape.com/viewsource/goodman_modal/goodman_modal.html">Simulating
Modal Dialog Windows


“Because a modal dialog box can include a URL to a resource in a
different domain, do not pass information through the vArguments parameter
that the user might consider private. The vArguments parameter
can be referenced within the modal dialog box using the dialogArguments
property of the window object.”--引自 MSDN showModalDialog
上面一段话说明:通过
sURL
参数我们可以将另一个域的资源用为话框的内容,但这种情况下我们就不能再向对话框传递任何参数了,只有当所引用的资源与引用它的页面属于同一个域时,我们才可以利用
window.dialogArguments 获得从引用页中传递过来的参数。

“那么我能不能像方案一中那样通过强制指定两个页面中的
document.domain 属性来蒙蔽浏览器,使其认为两个页面属于同一个域呢?”

确实有人提出过这种想法,笔者也试着这样做过,但最后还以失败而告终:在两个页面中强制指定 document.domain
了属性后,无论两个页面是否属于同一个域,对话框都无法正常识别从主页面传递过来的参数。

在此次实验中我使用了3个文件

main.html 在调用用 showModalDialog 方法时,通过 vArguments 向对话框传递了参数:"Can you
hear me?",希望对话框能够接收到这个参数;如果对话框接收到了,那么它将调用 window.alert() 方法打印出这条消息,然后向
main.html 返回一个结果:"Yes I do, I hear you from " + document.domain ;如果
main.html 接收到了对话框返回的结果,那么它同样会调用 window.alert() 打印出结果的内容。

其中 main.html 的源代码如下所示:






<html>
<head>
<title>show modal dialog</title>
<script>
<!--
//document.domain  = "mycompany.com";

<!-- 打开一个模态对话框,显示 url 所代表的资源 -->
function openDialog(url) {
    <!-- 向对话框传递参数 -->
    var args = new Object();
    args.content = "Can you hear me?";
    var rv = window.showModalDialog(url, args);
    <!-- 显示对话框所返回的结果 -->
    if (rv) {
        alert("dialog returns :" +
rv);
    } else {
        alert("dialog returns
nothing");
    }
}
-->
</script>
</head>

<body>
<!-- 引用 b.mycompany.com 中的资源 -->
<a href="#"
onclick="openDialog('http://b.mycompany.com/remotedialog.html');return
false;">
  I will Open a remote dialog from news.soufun.com
</a>
<br>
<!-- 引用本地的资源 -->
<a href="#" onclick="openDialog('./invokebyhouse.html');return
false;">
  I will Open a local dialog
</a>
</body>
</html>


localdialog.html(remotedialog.html) 的源代码如下所示:






<html>
<head>
<title>a remote dialog</title>
<script>
<!--
//document.domain  = "mycompany.com";

onload = function() {
  var args = window.dialogArguments;
  alert("You send me: " + args.content);
  btnCan.onclick = function() {
    window.returnValue = "Yes I do, I hear you from " +
document.domain;
    close();
  }
}
-->
</script>
</head>

<body>
Im here, Im a dialog <br>
I will return something to the main window<br>
<input id="btnCan" type="button" style="text-align:center;"
value="Close">
</body>
</html>


通过实验发现:

  1. main.html 总是能正常的接收从对话框中返回的结果,无论对话框是位于a.mycompany.com 还是 b.mycompany.com,也无论是否设置了 document.domain 属性;

  2. 在没有设置 document.domain 属性时,localdialog.html 可以正常接收从 main.html 传递过来的参数,但如果设置了 document.domain 属性, localdialog.html 读取到的参数就变成 null 了。

  3. 而无论是否设置了 document.domain 属性,在 remotedialog.html 读取从 main.html 传递过来的参数得到的始终都是 null。


非常遗憾,实验结果告诉我们:用对话框是无法实现这种跨域的数据交换的。

style="text-align: left; width: 100%; background-color: rgb(204, 204, 204);">





如果我的实验中存在某些漏洞,或者在你的实验中对话框读取到了从 main.html 传递过来的参数,有劳你通过 Email告知我,谢谢!

方案三


应该说利用服务器端应用实现这种跨域的数据交换是最为可靠的方式了,因为这几乎不会受到客户端的限制,不像前面两种方式:有的客户端不支持 document.domain 属性,有的不支持对话框等等。那么是不是说用服务器端应用解决不同域之间的数据交换是应该最优先考虑的方案呢?答案是否定的,因为服务器端应用也有它的致命伤:即接收数据的一方不能实时的显示从对方传递过来的数据,它只有在处理了 GET 或 POST 请求后才能使数据得到展现,在这个过程中如果未做任何特殊处理,那么用户在该帧中编辑的内容将被清除掉,这往往是我们所不希望看到的。有关用服务器端应用实现跨域的数据交换我们就不再举例子了,如果您有兴趣的话可以到 google 上查一下相关的资料。

总结


在上面介绍的三种方案中,除方案二尚不能实现在分属于不同域的帧之间进行数据交换之外,经证明方案一和方案三都是可行的,不过这两种方案又各有利弊:

针对这两种可行方案,大家在应用时应该灵活选择,如果你比较看重数据的实时展现那么就可以考虑采用前者;而如果应用的平台无关性是你衡量应用的标准那就应该考虑采用后者。

另外,如果您有其他可选方案的话,非常感谢您能通过 Email
告知我,以填补我在这面方面的空白,谢谢!

参考资料


What does the IE "Access is Denied" error mean?
http://www.dannyg.com/ref/jsminifaq.html#q15

PROPERTY:  Document::domain
http://www.powermct.co.kr/teched/ecma/doc_domain.html
Writing Cross-Domain Web Applications
http://www.knownow.com/support/devguide/Tutorials/Cross_Domain.html
Why do I get "access denied"-error in IE when calling a function in another frame?
http://www.faqts.com/knowledge_base/view.phtml/aid/1524/fid/127

 

所见即所得编辑器 aynHTML 系列文章(一)如何部署、嵌入、装载动态内容

aynHTML 简介

aynHTML 是一款基于HyperText Component 技术的所见即所得的在线编辑器,支持表格,图像,样式表,等等,目前只能运行在 5.5 (或更高版本)的 Internet Explorer 浏览器中。

目前 aynHTML 的最高版本是v0.20b(该版本尚存在一些bug),我们使用的版本是v0.18b

下面是aynHTML 官方网站的一个在线demo

http://www.aine.be/aynhtml/demo.html



这个在线demo 的反应速度可能会有点慢(至少曾经的一个版本是这样的),这是因为aynHTML有一个选项默认是打开的,只要关闭这个选项编辑器的处理速度就会有非常明显的提升。在这里先向大家简单介绍一下,见下面一段代码:



<AI:aiButton id="btnOutdent" contentEditable="false" cmd="Outdent" alt="减少缩进量" src="img/ico_outdent.gif" autoOff="1" check="1" />



上面一段代码位于aynHTML的核心文件aynHTML/aiEditor.htc 中,是用来描述编辑器的一个按钮(减少缩进量)的。默认情况下check 的值为1,意思是自动check 编辑器中焦点所在区域的状态,如字体大小、是否为粗体、斜体、对齐方式等等,这会大大降低编辑时的速度。

注:

1).aynHTML 比较适合对web 页面进行简单的在线编辑,并不适合用于复杂的美工处理。

2).aynHTML 编辑器不能被嵌入Frameset 中,如果被嵌入Frameset 的话,键盘上的快捷键(如Ctrl-v,粘贴功能)将全部失效;




如何把aynHTML 编辑器嵌入到页面中


如何部署aynHTMl

为了把 aynHTML editor 嵌入到 web 页面中,首先我们需要把aynHTML 的整个文件夹复制到 Web 站点的某个目录下(如程序文件的同级目录);然后在需要嵌入编辑器的页面中中加入 <AI:aiEditor> </AI:aiEditor> 两个标记,并在<AI:aiEditor> 标记中告诉编辑器该文件夹的相对位置,如下例所示

<html xmlns:AI>

<head>

<?import namespace="AI" implementation="aiEditor.htc" />

</head>

<body>

    <AI:aiEditor id="aiEditor" path="aynHTML/" load_method="inline">

        <p align="center"><b>Welcome to aynHTML Editor !</b></p>

    </AI:aiEditor>


    <!-- <AI:aiEditor> 与 </AI:aiEditor> 两个标记中间的内容将被格式化后显示在编辑器中 -->

</body>

</html>

下面一节我们将详细介绍如何定制aynHTML 编辑器。



注:

1).为了使aynHTML 编辑器正常运行,我们必须为其( <AI:aiEditor> </AI:aiEditor> )声明 AI 的 namespace,如上面代码中的



  <?import namespace="AI" implementation="aiEditor.htc" />



[注:

dreamweaver 无法正确解释该 namespace 申明,当用dreamweaver 打开包含该声明的页面时会导致后面的一个标记被解释错误,如下面一段代码:



<!-- aynhtml editor -->

<?import namespace="AI" implementation="aynHTML/aiEditor.htc" />

<link href="aynHTML/external.css" rel="stylesheet" type="text/css">

<!-- aynhtml editor -->



将会被解释为:

<!-- aynhtml editor -->

<?import namespace="AI" implementation="aynHTML/aiEditor.htc" />

link href="aynHTML/external.css" rel="stylesheet" type="text/css">

<!-- aynhtml editor -->

其中的 <link/> 标记被吞掉了一个'<'

]


如何定制 aynHTML

标记 <AI:aiEditor> 中的属性是用来定义 editor 的属性和操作的,如:

<AI:aiEditor id="aiEditor"

    color="buttonface"

    content =""

    font="宋体 ,楷体_GB2312 ,黑体 ,仿宋 ,Arial ,Verdana , Tahoma, Times New Roman,Georgia"

    font_size="8,9,10,11,12,13,14,16,20,24,48"

    height="400"

    hide_btn="open,save,image"

    load_method="inline"

    load_url=""

    link_get="aynHTML/sample/text/links.xml"

    open_method="default"

    palette="web, system"

    path="aynHTML/"

    style_import="1"

    stylesheet="aynHTML/internal.css"

    width="600">

....

</AI:aiEditor>

其中:

color       : 编辑器的界面颜色

content     :用来获得和设置编辑器的内容

font        :字体列表

font_size   :字体大小列表

height      :编辑器的高度

load_method :编辑器装载内容的方式(inline 是缺省的选项,意思是装载<AI:aiEditor> 与 </AI:aiEditor> 两个标记中间的内容)

load_url    :如果指定 load_method 为 html的话,则表示住在某个html页面,load_url 就是这个页面的链接地址

link_get    :aynHTML 可以以配置文件维护一个文件列表,用户可以选择其中一个文件进行编辑,link_get 是用来定义配置文件的路径的

open_method :与工具栏中打开按钮相关联的方法

palette     :调色板中的色彩样式

path        :aynHTML 编辑器核心文件的路径,该路径是相对于嵌入页面的相对路径

style_import:指示编辑器是否搜索所嵌入页面中的<style> 和 <link> 标记,缺省值为0

stylesheet  :应用于编辑器的样式表

width       :编辑器的宽度






如何在 editor 所嵌入的页面中读取 editor 中的 html 内容

1).如果 html tag 是直接嵌入在 <AI:aiEditor id="aiEditor"> </AI:aiEditor> tag 内, 则可以通过 aiEditor.innerHTML 或 aiEditor.innerText 来得到所要的内容。



2).如果 html 是通过 copy paste 形式填充到 editor 窗口中的,则无法用 aiEditor.innerHTML 或 aiEditor.innerText 读取 editor中的内容,目前的解决方法为:在 aiEditor.htc 中添加一个读取 editor 窗口中内容的方法 getContent(),如下所示:

<public:component tagName="aiEditor">

    <public:defaults viewLinkContent />

    ...

    <public:method name="getContent" internalname="c_get_content" />

</public:component>



<script language="javascript">

...

/* the custom method invoked by the extra aiEdirot */

function c_get_content() {

        return mode == 'html' ? aiEdit.innerHTML : aiEdit.innerText;

}

...

</script>



这样我们就可以在 editor 的外面读取 editor 中的内容了, 如:

<input type="button" value="getContent" onclick="javascript:alert(aiEditor.getContent());">




如何让编辑器显示动态内容

要在编辑器中编辑从数据库中取出的动态内容,只需要把<AI:aiEditor> 的属性 load_method 设置为 inline,然后把动态取得的内容插入<AI:aiEditor> 和 </AI:aiEditor> 两个标记中间就可以了。



简单实例:







<html xmlns:AI>

<head>

<?import namespace="AI" implementation="aiEditor.htc" />

</head>

<body>

<%

Set conn = Server.CreateObject("ADODB.Connection")

conn.Open Application("newsconn")

sql = "SELECT newscontent FROM news_detail WHERE newsid='"&request("newsid")&"'"

Set rs = Server.CreateObject("ADODB.Recordset")

rs.Open sql,conn,1,2

%>

<AI:aiEditor id="aiEditor"

    color="buttonface"

    content =""

    font="宋体 ,楷体_GB2312 ,黑体 ,仿宋 ,Arial ,Verdana , Tahoma, Times New Roman,Georgia"

    font_size="8,9,10,11,12,13,14,16,20,24,48"

    height="400"

    hide_btn="open,save,image"

    load_method="inline"

    load_url=""

    link_get="aynHTML/sample/text/links.xml"

    open_method="default"

    palette="web, system"

    path="aynHTML/"

    style_import="1"

    stylesheet="aynHTML/internal.css"

    width="600">

    <%=rs("newscontent")%>

</AI:aiEditor>

<%

rs.Close

conn.Close

Set rs = nothing

Set conn = nothing

%>

</body>

</html>



在线资源和参考文档

http://www.aine.be/aynhtml/

http://www.aine.be/aynhtml/doc/aiEditor.html

http://www.aine.be/aynhtml/doc/aiTree.html

http://aynhtml.sourceforge.net/

http://sourceforge.net/project/aynhtml/

Sunday, June 06, 2004

 

My GNU toolkit

  • GNU Emacs
  • Labels:


     

    我的简历(My Resume)

    SouFun.com (2003-03 ~ present)
  • search.soufun.com WebLucene

  • news.soufun.com OpenCMS

  •  

    About lhelper

    lhelper 原名吕克让,工学学士,2002 年毕业于北京工业大学计算机学院。他向往自由、喜欢linux,最喜欢的花是文竹,最想做的是成为一名象Richard Stallman 一样优秀的黑客。

     

    lhelper at blogspot

    很早就想拥有一个属于自己的站点了,并且年(2004)初的时候还信誓旦旦的在万网注册了一个心仪已久的域名lhelper.org,但之后一直把工作忙、时间少当成http://www.lhelper.org 迟迟没有建立起来的借口:-).

    前些天来到google 查找资料,无意间想到了google 的blog,于是一时的冲动导致了http://lhelper.blogspot.com 的诞生:)

    至于如何处理区别对待http://lhelper.blogspot.com 和 http://www.lhelper.org,我是这样考虑的:http://lhelper.blogspot.com 可以用来记录我平时的所学、所悟,以及从别人那里所收集来的资料,http://www.lhelper.org 则用来展现自己的作品;

    在继续我的下一项内容之前,先让我来向我的朋友和同事表达一下谢意:
    车东(http://www.chedong.com):感谢车东指引我进入了互联网行业,并涉足了目前炙手可热的全文检索领域!
    张步镇(CTO-我的部门主管):感谢张步镇宽恕了我的几次技术上的重大失误,为我提供了汲取新知识的机会。
    另外还要感谢的包括我的同事杨春宇、李宏伟、高源、张利海....(太多了,今后一定补齐)

    Archives

    June 2004   November 2005   July 2006   August 2006  

    This page is powered by Blogger. Isn't yours?