在线时间 1957 小时 最后登录 2024-6-29 注册时间 2004-4-26 听众数 49 收听数 0 能力 60 分 体力 40960 点 威望 6 点 阅读权限 255 积分 23863 相册 0 日志 0 记录 0 帖子 20501 主题 18182 精华 5 分享 0 好友 140
TA的每日心情 奋斗 2024-6-23 05:14
签到天数: 1043 天
[LV.10]以坛为家III
群组 : 万里江山
群组 : sas讨论小组
群组 : 长盛证券理财有限公司
群组 : C 语言讨论组
群组 : Matlab讨论组
<b>构建含有数学内容的动态网站; ~4 ]" T d- g7 N O/ N
</b>JavaServer Pages 技术和 LaTeX 帮助的联机科学教育与研究 1 W8 C3 \; N7 b- l- K. t( ^6 t
$ p7 n b1 I3 X. E/ H3 Y < ><a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#author1" target="_blank" ><FONT color=#000000>Michael Juntao Yuan</FONT></A><A herf="mailtjuntao@astro.as.utexas.edu">juntao@astro.as.utexas.edu</A>)/ b# \, c, `# t- R. \, T
博士研究生,德州大学奥斯汀分校
* g K" P$ J0 G0 O& w& c& z 2002 年 2 月</P>
; e, H" ^- f( ~) Z# T <BLOCKQUOTE>将含有下标、上标和特殊符号的数学公式转换成 HTML 非常困难。大多数科学家和数学家使用灵活的 LaTeX 文本处理器来创建外表美观的公式 — 但是如何将这个输出转换成 Web 友好的格式呢?在本文中,Michael Yuan 说明了如何使用 JSP 页面和定制标记将 LaTeX 格式的公式放到 Web 上,这种方法对于编写者来说很简便并且不需要任何特定的客户机端软件。正如他在各种日益有效的方法中反复说明的,您会发现服务器方 Java 技术如何利用不同的第三方工具来解决看似很难处理的问题。通过单击本文顶部或底部的<B>讨论</B>在<A>论坛</A>中将您对本文的想法与作者和其他读者一起分享。</BLOCKQUOTE>" |) o& L" \) W
< >因特网和 Web 最初是由科学家设计用来交换科学和数学研究信息的。出乎意料的是,经过因特网 30 多年的发展以及 Web 10 年的发展,仍然没有一种简便的方法将数学密集型内容发布到 Web 上。已经开发了 Web 数学通信标准,例如 W3C 的 MathML 建议;但是为了使用它们,数学内容的作者和读者都需要特定软件,比如用于 Netscape Navigator 和 Internet Explorer 的 IBM techexplorer 数学浏览器插件。(请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>以获取到 TechExplorer 和 MathML 的链接。)由于技术困难、成本和安全性危险因素,用户不太愿安装特定客户机端软件。因此,使那些标准的采用被限制在狭窄的研究领域,并且广大因特网用户无法使用它们。当前在 Web 上共享数学信息最方便的方法仍是将所有非文本公式转换成快照图像,然后将那些图像插入标准 HTML 页面。</P>
1 |/ [" T4 j4 d: \" _# a( s < >最新的基于 GUI 的数学权威性工具具有将文档保存为“Web 页面”的选项;这些工具为您生成公式图像和静态 HTML 页面。如果您只是想在 Web 上发布静态内容,例如论文,则这种方法是很方便的。但是,静态网站的时代已经早就过去了,因为在大多数网站中用户的参与已经变得不可缺少。高级数学网站应该从后台数据库中动态地发布内容并允许用户提交他们自己的内容以与其他用户分享(示例包括交互式考试系统和研究协作系统)。</P>9 P- T" R: _ U5 Q3 I5 |! l
< >本文将向您展示该问题的一种解决方案。样本代码提供了一种方法,它可以快速简便地将表示数学公式的标记文本转换成显示该公式的可用于 Web 的图形文件。在此过程中,您会看到解决更多一般性难题的方法:我们将使用 JSP 页面和定制标记将几个完全不同的且互不相连的工具链接在单个程序中。当您使用多变的动态 Web 应用程序时,请记住不必从头做起;解决方案也可以其它形式提供,这由您控制。</P>% _3 d4 d( A) n* E
< ><A>JSP 技术回顾</A>7 f1 \! L$ n+ O6 R `3 q
实现动态服务器方应用程序的领先技术是 JavaServer Pages(JSP)技术。可以使用 JSP 页面来控制如何在前端 Web 页面(显示逻辑)上显示后台数据。JSP 页面使用类似 HTML 的标记语言将 Java 数据对象与 HTML 显示元素粘合在一起。</P>
. c. c1 U3 q h. n < >JSP 技术的主要优点是可扩展性。可以使用定制标记来扩展 JSP 标记语言,它允许我们封装在标记处理器类内部处理显示逻辑的 Java 代码。Web 编写者可以直接使用预定义的标记库完成复杂的显示任务,而不需要编写任何 Java 代码。定制标记使重用显示控制码成为可能,并且极大地简化了 Web 页面编写者的工作。常用的 JSP 定制标记类型是<I>内容转换</I>标记,它将包含在标记主体内对编写者友好的内容转换成对访问者友好的显示元素。</P>
' Y6 O) c3 z# V < >对于数学网站,可以使用内容转换标记将数学语义表示转换成 Web 图像。我们将在接下来的几节中更详细地讨论这一技术。</P># g7 W/ \' `% D* b/ ]
< ><A>预备知识</A>' |& Z+ l: c" v) Z( P' Z1 w$ A
这里说明了内容转换标记是如何工作的。当 JSP 容器遇到定制标记时,它在当前页的标记库描述符文件中查找标记名称。如果它找到一个匹配项,则 JSP 容器将上下文信息传递到标记处理器类。JSP 标记处理器首先从定制标记的主体中抽取表示数学公式的文本字符串。然后处理器根据语义文本公式创建图像,接着将该图像的 HTML 标记(<CODE><IMG></CODE>)发送到浏览器。在标记处理器中所需的数学图像生成工具应该具有下列功能部件:</P>
. } M, k4 c2 i# w4 {5 h# b <UL>, l- k4 v6 z7 B R' h
<LI>必须用纯文本表示数学公式,纯文本是 JSP 标记主体中唯一支持的格式。纯文本还是通过 HTTP 协议传送信息和在数据库或 XML 文档中存储信息的标准格式。2 k8 ~& A3 \) b- ?
# ?7 C; p4 @: }; J2 u
& E8 T* y( W, H4 T. B& a- Q. c, D 3 ?$ D: g# C8 M0 D$ h1 n
<LI>数学公式的纯文本表示法必须符合语义并且是直观的,这样 Web 编写者可以在无需经过许多训练的情况下使用它。为了实现该目的,如果全世界的科学家都已使用语义数学表示法体系,那么这是最好的。0 n! `# N2 M$ ^4 J8 D3 |9 d
5 Y( p! R7 L- k& E C$ T
3 S& g! Z1 I7 [! q8 u! M 2 q6 Z/ p; y. w7 B5 U) B+ i: d, G. Z
<LI>数学显示工具在许多平台上应该都可用来发挥 Java 应用程序的跨平台性能。</LI></UL>
- l) h' ]' l; ]! P2 i& t0 ~7 C < ><A>LaTeX 简介</A>; ]1 ~' F! l& u: M2 f* Z
符合以上所有要求的数学编写软件是一套业界标准的 TeX/LaTeX 类型设置工具(此后简称为 LaTeX)。LaTeX 提供下列功能:</P>
! o" \6 m. A- O <UL>5 C- J( u& d' y
<LI>LaTeX 使用标准文本输入文件和一种完全开放的格式。事实上,大多数 LaTeX 实现也是开放源码的。/ Y" l0 F& u" K, X
3 y. z" \/ W5 o9 N) Z
4 r! R( j2 h% R p$ o: |1 X
k. z) n9 H2 {' M
<LI>由于 LaTeX 的语法非常直观,所以很容易学习。例如,下列输入文本2 q# [, J% x) s- c- a0 _
; O' `9 G' M( Z3 j2 W
3 o& ?3 J& y; ?( h! ~
/ F) f! Y& P# u& z/ e/ p
<TABLE cellSpacing=0 cellPadding=5 width="80%" bgColor=#cccccc border=1>
! a3 C/ p* ]6 Q# d
1 v. D4 [; P) j% j8 l <TR>" x( @# o& B% S
<TD>< RE><CODE> \alpha_0^2 + \beta_0^2 = \gamma_0^2 </CODE></PRE></TD></TR></TABLE>
7 P# t: \( z2 ]4 s; S! ~& N 9 g- j* _ w' o& A* g2 L
# d& y! k8 F @6 H k. o1 P# _
- z2 n1 Q/ H! R+ j3 f0 E3 {# Z
< >通过 LaTeX 处理后生成图 1 中显示的公式。" ^" k/ v( }, k& {, ]6 }
' l( z) |) j3 Q9 ` </P>
7 U0 g: ?# I: c) Q( |" d0 o1 ]5 q < ><A><B>图 1. 样本 LaTeX 输出</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig01.gif"></P>
) Z! a9 G5 o/ N# h; @ ?1 N+ J0 M) J3 b) r1 }5 L
2 k& u }$ c4 J4 l7 _2 r 3 u5 U) |" m2 Z9 L* g" V
<LI>LaTeX 已经建立了一个很好的用户库,因为它在科学界中成为一种标准的类型设置语言已有一段时间了。大多数数学作者都已经熟悉它了。
# n! u5 p4 z9 B, g8 z# I/ i q9 Y
9 g+ C! w( t2 R' t
5 H! e7 F8 N* G$ z5 g6 ^& y
8 }) Z( l, U, K3 j' X5 i <LI>在很多平台上都可以使用 LaTeX 处理器,包括最常用的 UNIX、Linux、Windows 和 Mac OS。因此 LaTeX 文档有很高的可移植性。
1 X$ E! z$ }8 Z) Q( I
k. o2 Q. t, z2 ^% j 6 b+ ]) T3 z: |
- t( c/ j6 `; E0 @; ~: S1 B \! d
<LI>LaTeX 生成专业的具有出版质量的公式。这已经超出了 Web 图形的要求。图 2 显示使用 LaTeX 后呈现的麦克斯威尔方程组。
! R1 e4 q9 N4 R8 s" _ ; C3 Q% i3 L; t0 ~. G7 j9 E% n) z
# s' m \7 K; k! l
; H8 s6 G2 N- e% P/ c6 n5 C < ><A><B>图 2. 使用 LaTeX 呈现的麦克斯威尔方程组</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig02.gif"></P></LI></UL>8 y6 R J, H" w5 p% g/ L; `( E
< >在继续阅读本文之前,如果对 LaTeX 及其语法感到陌生,最好熟悉一下。在下面的<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>一节中包含了到有用信息的链接。</P>8 w1 n# w) T9 N3 L" k
< ><A>从 LaTeX 中生成 GIF 图像</A>9 R$ e" L- R8 c& J* E
LaTeX 可以将语义数学公式处理成图像,但是无法将那些图像直接显示在大多数 Web 浏览器中。所有 Web 浏览器都支持 GIF 图像,因此我们需要将 LaTeX 公式图像转换成 GIF 格式。在本节中,我们将研究如何使用第三方工具从 LaTeX 输入文本中生成 GIF 图像。我们的最终目的是尽可能地使这个过程自动化,但是研究一下我们必须使用的基本构件块也是有所帮助的。我们将使用 Linux/UNIX 命令行工具作为示例,但是具有相同功能的工具也可以在 Windows 和 Mac OS 上使用。可以类似地生成其它格式的图像,例如,PNG 和 JPEG。下列步骤将创建 GIF 图像格式的数学内容:</P>9 Q8 o" Y" `3 \* ?# G9 m% V
<OL>; L8 P0 k$ M$ E$ Z. d2 W& b
<LI>遵循 LaTeX 语法在文本输入文件中创建数学内容(我们把样本文件称为 sample.tex)。
/ A: d2 T9 W$ ]7 Z. {
* H( p. Q2 T2 r& X: b+ ?, N
4 l* T% F1 @( g* K* ~ <LI>使用图 3 中显示的 LaTeX 程序将 sample.tex 处理成与设备无关的文件(sample.dvi)。命令是 <CODE>latex sample.tex</CODE>。在该过程中,LaTeX 还生成一个日志文件和一个辅助文件。
, n R- M- d" ]% |; [
4 P8 V2 g( A: D8 U* x6 x' n 8 L: [& W4 u7 o4 D
< ><A><B>图 3. 运行 LaTeX</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig03.gif"></P>
. t H) x! O+ g+ { c <LI>使用图 4 中显示的 Dvips 实用程序将 .dvi 文件转换成 PostScript 文件。命令是 <CODE>dvips -o sample.ps sample.dvi</CODE>。(有关 Dvips 的更多信息,请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>。)) K$ h- h% s" ~- Y( c* D/ r
/ J3 d G2 t! W( B
0 o5 u4 @1 x5 f9 a. O < ><A><B>图 4. 运行 Dvips</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig04.gif"></P>% y* \" ^: X4 Z
<LI>使用 ImageMagick 工具 <CODE>convert</CODE> 将 PostScript 文件 sample.ps 转换成 GIF 格式。(有关 ImageMagick 的更多信息,请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>。)命令是 <CODE>convert -antialias -crop 0x0 sample.ps sample.gif</CODE>。我们不想使 PostScript 页面上公式周围的空白部分变成 GIF 的一部分,因此我们使用 <CODE>-crop 0x0</CODE> 命令行选项来切除周围的背景。我们还使用 <CODE>-antialias</CODE> 选项来创建 antialias 字体。
6 m7 Z3 a; ~: G' f# h1 a 5 d1 O8 Z" H4 |" l4 [
9 M0 z a, E5 z. v& B
< ><A><B>图 5. 运行 ImageMagick</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig05.gif"></P>
8 e) T& N- T8 Q9 S) C, Q
6 |+ ~. f% `8 \* F3 e( J1 f6 T 您在命令行上告诉 ImageMagick 加到输出文件名上的文件扩展名确定输出图像格式。在上面的示例中,将输出图像指定为 <CODE>sample.gif</CODE>,它指示 ImageMagick 输出 GIF 图像。请参阅标题为“<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#sidebar2" target="_blank" ><FONT color=#000000>矢量图形与位图的对比</FONT></A>”的边注,以获取有关 PostScript 到 GIF 转换的更多详细信息。</LI></OL>
5 n8 m- A7 v# l: n; s < ><A>在 Java 应用程序中运行外部程序</A>
- J( b* d/ O6 o8 E9 O 到目前为止我们所列出的过程涉及了许多分离的命令行程序。要简便地将数学图像放到 Web 上,我们需要将那些进行数学编写的工具集成到 JSP Web 应用程序中。Java 程序可以使用 <CODE>java.lang.Runtime</CODE> API 来启动和管理 JVM 外部的主机 OS 进程。可以用类似下面的方法来启动新的进程:</P>5 b8 e1 C( P# c" `6 f* {
< >: h4 G: ^6 [# e' z. C' E; O
<TABLE cellSpacing=0 cellPadding=5 width="65%" bgColor=#cccccc border=1>
& _& g8 \0 L3 O5 q' W 9 O7 x* e3 E/ s
<TR>
- Q4 n: x4 x8 j3 V6 |# ^% V6 M+ H- L <TD>< RE><CODE> rocess p = Runtime.getRuntime().exec(cmd, envp, dir); </CODE></PRE></TD></TR></TABLE></P>1 O4 i# O% ^! B3 Y8 [1 v0 O7 s: q
< >其中,参数 <CODE>cmd</CODE> 不是包含命令的单个字符串就是包含命令及其选项的字符串数组;字符串数组 <CODE>envp</CODE> 包含 <CODE>name=value</CODE> 格式的环境变量设置;参数 <CODE>dir</CODE> 是 <CODE>java.io.File</CODE> 类型并且它表示进程的工作目录。进程启动后,我们就可以将数据通过进程的输入流发送给它。</P>4 B+ h! p2 M/ C
< > X. r A2 R% g; s, x
<TABLE cellSpacing=0 cellPadding=5 width="65%" bgColor=#cccccc border=1>6 g' i, o* C. m: |& i. \
$ Q& X5 x5 }4 A' W8 L" S ?
<TR>
2 z0 Y) F9 T" n6 k2 J& A <TD>< RE><CODE>InputStream ins = p.getInputStream();</CODE></PRE></TD></TR></TABLE></P>
5 ^! D9 ?: S' {( X$ U/ e' L < >我们还可以从进程的输出流中获取其输出:</P>" r% K) S; w7 I: G
< >$ c! }5 ] { H" P7 K4 h u
<TABLE cellSpacing=0 cellPadding=5 width="65%" bgColor=#cccccc border=1>* V/ T; D; c9 A1 O% @' z1 a
; Z2 ^' g7 ]& h
<TR>; G, |; \" K1 Z& ?; t$ \" L& b
<TD>< RE><CODE>OutputStream ous = p.getOutputStream();</CODE></PRE></TD></TR></TABLE></P>
0 Y9 j& j* s- D% X; Q1 n( H < >如果想暂停 Java 应用程序直到完成外部进程为止,可以使用下列代码:</P> a" Z& }. d( ` h/ ?7 O# \6 C% s' E
< >
8 G2 W! y- S f' U- w+ R1 x <TABLE cellSpacing=0 cellPadding=5 width="65%" bgColor=#cccccc border=1>
% n" S- ]7 w% @4 [" J
0 e# K: s. Q' g# \ <TR>4 ?, H& a/ u% i. F, D( ]; h- A) [' V
<TD><PRE><CODE>p.waitFor(); // Move on to other tasks. </CODE></PRE></TD></TR></TABLE></P>
4 ]' u4 i1 a0 @; k+ r. L <P><A>示例</A>
% m$ a8 F5 }. r: A 我们讨论了实现含有动态数学内容的网站所需的工具和基础技术。现在,我们需要将它们放在一起。说明该方法的最好方式是运行与本文一起提供的示例应用程序。</P>
3 n6 Q1 ]( H1 G2 Z3 ]6 ^5 q. C( [ <P><A>如何部署示例</A></P>+ H) O. R# {% i! U! l8 D. h
<OL>; C. V8 o8 B% X `( Z1 f$ S ?/ s
<LI>下载并展开代码的 <a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/JSPMathCode.zip" target="_blank" ><FONT color=#000000>zip 归档文件</FONT></A>。' r* x* Z4 W2 ~* c
" ^1 ? ~" P" G8 e: Z
' h: e$ f: b+ x4 g- a3 |' J
<LI>编译 <CODE>support</CODE>、<CODE>SlowMathTag</CODE> 和 <CODE>MathTag</CODE> 包。您将需要在类路径中包含 J2EE v1.3 <CODE>j2ee.jar</CODE> 文件。
; ?- \6 r+ ^3 T3 `; E$ o
o @8 K* b2 Y
- p( h3 b4 k% H <LI>在 JSP 服务器中创建一个新的 Web 应用程序。例如,使用 Tomcat 服务器为测试应用程序创建目录 <CODE>Webapps/JSPMath</CODE>。
& k' D* C; e Q! }
( V. ~7 e6 S, ^! V" E& x
# b2 G( \1 S) [; r2 X <LI>将已编译包 <CODE>support</CODE>、<CODE>SlowMathTag</CODE> 和 <CODE>MathTag</CODE> 复制到 <CODE>Webapps/JSPMath/WEB-INF/classes</CODE> 目录下。" m7 A+ \! \# b D( p/ A. t" D- ]: L
2 s' I, B P& w, n% {, Q! Q$ ?
4 |9 {: R1 [6 w# r: ~: E2 Q <LI>将标记库描述符文件 <CODE>tlds/*.tld</CODE> 复制到 <CODE>Webapps/JSPMath/WEB-INF/tlds</CODE> 目录下。
# ~1 v* Y/ { {8 W$ J ( c; l6 b9 v: v& n
- G2 T5 @+ g2 ]2 }. q
<LI>将 JSP 和 HTML 文件 <CODE>jsp/*</CODE> 复制到 <CODE>Webapps/JSPMath</CODE> 内的任何目录下。) r* X+ P+ ]' I/ N/ z0 K7 B( n
1 z3 O! O4 Y9 c5 `" _4 A
) L' d9 X' N/ h5 D <LI>现在,可以通过将浏览器指向 <CODE>Test01.html</CODE> 或 <CODE>Test02.html</CODE> 来开始测试。</LI></OL>$ f: K1 \& \# ~& E' t4 D9 d
<P>一旦安装了应用程序,就可以遵循本节中的步骤来查看示例是如何工作的。</P>& L1 K9 H- S7 _, M: m9 _
<P><A>正在运行的示例代码</A>' l! f5 r: v1 t( t) `
样本代码包含两种新的 JSP 定制标记。<CODE><LatexRaw></CODE> 标记将原始 LaTeX 文本转换成 HTML 图像标记。可以使用它在单个图像中显示多个公式、表或简短文本标题。<CODE><LatexMath></CODE> 标记只将单个公式图像的数学方式 LaTeX 文本转换成 HTML 图像标记。可以使用它来实现公式的快速显示。</P>
, r# c; l9 e, j* @* l <P>每个标记都有两个属性,<CODE>height</CODE> 和 <CODE>width</CODE>,它们对应于结果 <CODE><IMG></CODE> 标记中的属性 <CODE>height</CODE> 和 <CODE>width</CODE>。这些属性不是必需的。</P>
" h) ?4 H- e& ]; ~. U <P>下面的屏幕快照显示了正在运行的定制标记。Test01.html 在两个大文本域中采集 LaTeX 文本字符串。该页面顶部的两个小的文本输入框采集用户期望的其中一个结果图像的高度和宽度。在 Test01.jsp 中,我们将以这里指定的用户期望尺寸和由 ImageMagick 指定的缺省尺寸显示图像,以演示如何在定制标记中使用尺寸属性。已填写的 HTML 表单出现在浏览器中,如图 6 所示。</P>
8 N( l0 H# N4 ?7 q3 {! N [ <P><A><B>图 6. LaTeX Math 输入表单</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig06.gif"></P>( z" @% z* x0 _' @' j
<P>当我们在 Test01.html 中提交表单时,它将数据张贴到 JSP 页面 Test01.jsp 上,该页面显示了生成的公式图像,如图 7 所示。第一个图像是由标记 <CODE><LatexMath></CODE> 使用来自 HTML 输入表单中 <CODE>MathText</CODE> 域(单行文本域)的数学方式 LaTeX 文本生成的。请注意,第一个图像的 <CODE><LatexMath></CODE> 标记具有两个属性。然后按照用户指定的 <CODE>height</CODE> 和 <CODE>width</CODE> 属性的尺寸来显示图像。按照与第一个图像相同的方法来生成第二个图像,只是以缺省尺寸来显示第二个图像(生成第二个图像的标记没有尺寸属性)。第三个图像是混合文本和数学符号的示例,它由从 <CODE>RawMathText</CODE> 输入域(大型多行文本域)中的 LaTeX 文本的标记 <CODE><LatexRaw></CODE> 生成。指定的高度和宽度域不适用于这个图像。</P>, u' J2 r/ L- B( ^4 a S
<P><A><B>图 7. 数学图像输出</B></A><IMG src="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/fig07.gif"></P># ~" Q9 v7 e( `5 a* x) H: |
<P>清单 1 显示了 Test01.jsp 中的代码,它们可以实现以上所有目标。</P>
1 ?0 |" k5 }: `. ^ n7 b I/ a* N <P><A><B>清单 1. Test01.jsp</B></A> . ^0 i* b' M; P. x
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
! {( i& p6 h7 b: T - b. H5 |& D$ ]4 V. m) G5 N
<TR>) x! c8 W+ f* `5 K$ t
<TD><PRE><CODE><%@ taglib uri="/WEB-INF/tlds/SlowMathTaglib.tld" prefix="SlowMathTag" %><% String mathText = request.getParameter( "MathText" ); String rawMathText = request.getParameter( "RawMathText" ); String height = request.getParameter( "height" ); String width = request.getParameter( "width" );%><html><head> <title>JSPMath test without image cache</title></head><body bgcolor=white><p><font color=green> Math formula in desired dimensions: </font><p><SlowMathTag atexMath height="<%=height%>" width="<%=width%>"> <%=mathText%></SlowMathTag atexMath><p><font color=green> Math formula in default dimensions: </font><p><SlowMathTag atexMath> <%=mathText%></SlowMathTag atexMath><p><font color=green> Latex segment in default dimensions: </font><p><SlowMathTag atexRaw> <%=rawMathText%></SlowMathTag atexRaw></body></html></CODE></PRE></TD></TR></TABLE></P>( L# i) r# ~' | g. b% x
<P>清单 2 显示了在一次执行中 JSP 容器发送到 Web 浏览器的实际 HTML 代码。</P>) {# ]2 w( [$ f( z: f
<P><A><B>清单 2. 从 Test01.jsp 发送到浏览器的 HTML 代码</B></A> 0 P6 F: T6 D: H( Q4 g- ^
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
8 D# c; ^7 G6 f; y / D$ q: _* O2 g1 n0 e5 X
<TR># F7 A( u9 q5 Z% q
<TD><PRE><CODE><html><head> <title>JSPMath test without image cache</title></head><body bgcolor=white><p><font color=green> Math formula in desired dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709773390.gif" height=20 width=150 /><p><font color=green> Math formula in default dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709776535.gif" /><p><font color=green> Latex segment in default dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709777612.gif" /></body></html></CODE></PRE></TD></TR></TABLE></P>) [# K2 d" B; p( Y) _
<P>这里的 GIF 图像是由标记处理器生成的。如图 7 所示,使用用户指定的尺寸放大的图像看起来不是很平滑。有关出现这一情况的原因以及如何在 UNIX/Linux 系统上处理它,请参考标题为“<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#sidebar2" target="_blank" ><FONT color=#000000>矢量图与位图的对比</FONT></A>”的边注。</P>
. f Y n4 M! b$ `4 T <P>我们如何实现这些定制标记操作呢?请继续阅读,找出答案。</P>
2 U5 y3 f! Q4 E <P><A>一种简单(但缓慢)的方法</A>
8 {$ S1 i2 ]/ A9 y- M 可以用很简单和直接的方式实现上面的示例。对于本文,您应该已经知道如何实现 JSP 定制标记处理器了。如果您想更新一下您的知识,请参考下面的<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>一节。</P>
o; R6 E2 H# c Q& Y7 X <P><CODE><SlowMathTag:*></CODE> 标记处理器调用静态方法 <CODE>support.MathUtil.latex2gif()</CODE> 以生成 GIF 图像。让我们研究一下这种方法的源代码以及它是如何工作的。</P>2 N% M- S4 m8 H& W$ \
<P>首先,该方法确定当前标记生成的 GIF 图像以及中间文件的唯一名称,如清单 3 所示。那些文件的根名称是当前时间的毫秒值。</P>
. k& O% T Z7 v6 @7 A <P><A><B>清单 3. 生成文件名</B></A>
/ d# u+ y' i( G' p' W: w; K <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>) K7 {* ]: v7 k6 ]$ }
+ i( m/ f/ @, K6 O* M) T
<TR>
. A9 F# ~1 a) S+ w <TD><PRE><CODE>Date now = new Date ();String filename = "image" + Long.toString(now.getTime());// ...File f = new File(path + filename + ".tex");</CODE></PRE></TD></TR></TABLE></P>
3 x1 V/ I9 Q* R( J4 D# E8 @ <P>接下来,该方法从标记主体中采集内容,然后组装成一个完整的 LaTeX 输入文件,如清单 4 所示。JSP 容器用字符串变量 <CODE>latexStr</CODE> 将标记主体传送到方法 <CODE>latex2gif()</CODE>。使用 <CODE>\pagestyle{empty}</CODE> 命令以消除在结果页面底部显示的页号。使用 <CODE>\batchmode</CODE> 命令使 LaTeX 处理器在非交互方式下执行。</P>7 e% y1 X2 Z. f& y* Y5 N0 |
<P><A><B>清单 4. 汇编 LaTeX 输入文件</B></A> ' G* T, h; }( r: v' e/ k
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
! n% Z4 k) `9 O \8 c9 m: f
. U: ]% b& U. [4 u% v2 x% Q <TR>% U7 h: A% N% R
<TD><PRE><CODE>private static String latexHead = "\\batchmode\n" + "\\documentclass[12pt]{article}\n" + "\\pagestyle{empty}\n" + "\\begin{document}\n";private static String latexEnd = "\\end{document}\n";// ...// String variable "latexStr" contains the body of the current tag.// Process modes.if ( mode.equals("displaymath") ) { latexStr = "\\Large\n\\begin{displaymath}\n" + latexStr.trim() + "\\end{displaymath}\n";} else { // Unrecognized modes are raw latex strings.}// Write content into a latex file.FileWriter fw = new FileWriter(f);fw.write( latexHead, 0, latexHead.length() );fw.write( latexStr, 0, latexStr.length() );fw.write( latexEnd, 0, latexEnd.length() );</CODE></PRE></TD></TR></TABLE></P>% y9 |4 B+ I! ~7 l; ^
<P>现在,该方法执行命令 <CODE>latex</CODE>、<CODE>dvips</CODE> 和 <CODE>convert</CODE>,以在服务器方的预先配置目录 <CODE>path</CODE> 下生成最终的 GIF 图像,如清单 5 所示。</P>- S" s/ c: N) l* [5 i2 {: S
<P><A><B>清单 5. 生成 GIF 文件</B></A>
4 W( Q1 U; @/ k, S <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>. w% W2 I* T Z3 q
/ e! r- |: V3 K8 Y& q* C7 B <TR>
, a- J) @& Y+ h: d" Z% k7 o% w3 S <TD><PRE><CODE>private static String latexCommand = "/usr/bin/latex";private static String dvipsCommand = "/usr/bin/dvips";private static String convertCommand = "/usr/X11R6/bin/convert -antialias -crop 0x0";// ...public static synchronized String latex2gif (String path, String mode, String latexStr) throws Exception { // Generate root file name and create ".tex" file // ... // Get Runtime. Runtime r = Runtime.getRuntime(); Process p; File imagePath = new File( path ); // ... // Run latex in directory "imagePath". p = r.exec(latexCommand + " " + filename + ".tex", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in Latex\n"); // Run dvips in directory "imagePath". p = r.exec(dvipsCommand + " -o " + filename + ".ps " + filename + ".dvi", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in dvips\n"); // Run convert in directory "imagePath". p = r.exec(convertCommand + " " + filename + ".ps " + filename + ".gif", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in convert\n"); // ...}</CODE></PRE></TD></TR></TABLE></P>$ |! A. Y5 H; d' x
<P>最后,该方法删除所有中间文件,如清单 6 所示。</P>
- U- R# x. ~1 ]/ S$ M2 u5 L <P><A><B>清单 6. 删除中间文件</B></A> + ]8 m2 V& R5 l ]/ A
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>0 S; O( U6 t0 |5 {- U
4 {& V8 D' t B# P( g <TR>7 a) D2 D3 S% @' T( r% J
<TD><PRE><CODE>f = new File(path + filename + ".tex");f.delete();f = new File(path + filename + ".log");f.delete();f = new File(path + filename + ".aux");f.delete();f = new File(path + filename + ".dvi");f.delete();f = new File(path + filename + ".ps");f.delete();</CODE></PRE></TD></TR></TABLE></P>
- [9 P" E9 x6 E$ y <P>现在,标记处理器可以将生成的 GIF 图像的 <CODE><IMG></CODE> HTML 标记写入服务器输出流。</P>' A; d- p( x$ G8 d8 A8 K- q, O% @# z
<P>刚刚描绘的方法很简单,但是正如我们马上就会看到的它很慢而且容易出错。在接下来的几节中,我们将讨论改进这一解决方案的性能和健壮性的方法。</P>
1 P: A ]% M/ a2 { <P><A>通过高速缓存改进性能</A> @4 T. ^8 N; n1 E
从 LaTeX 输入文本生成图像是很慢的,因为它涉及多个外部程序。在上一节的简单实现中,每次调用 <CODE><SlowMathTag:*></CODE> 定制标记处理器时,它都必须生成一个新图像。这样效率是非常低的。在示例应用程序中,Test01.jsp 中的两个 <CODE><SlowMathTag atexMath></CODE> 标记需要为每个访问者两次生成相同的公式图像,只是为了用不同的尺寸来显示它。现实世界中,应用程序很可能比示例应用程序复杂得多,并且生成不必要的图像带来的性能开销可能很严重。想象一下,我们的站点将提供动态数学考试,并且许多访问者会遇到相同的考试题目。为每个访问者一次次地重新生成同一公式的图像太昂贵了。</P>
/ a+ G: `2 y$ J! V% g( d4 _ <P>对于一个规模可伸缩的解决方案,我们需要对生成的图像进行高速缓存,然后在将来需要的时候重用它们。可以将 LaTeX 文本用作图像名称的<I>键</I>。如果标记处理器第二次遇到同一个 LaTeX 文本,则它可以找出高速缓存图像的文件名,然后直接返回它。可以将一对对的 LaTeX 文本和图像名称存储在 SQL 数据库以便长久保存和快速搜索。可以使用具有不同作用域的数据库访问 JavaBean 来对那些高速缓存的图像的访问作进一步的调节。虽然对于读者来说,这样的调节可能是一个有趣的实践,但是却超出了本文的范围。</P>, G/ J6 ^$ P3 V n7 }
<P>这里,我们将把高速缓存的图像作为静态数据成员存储在标记处理器助手类中。那些静态数据成员具有与 JSP 服务器容器相同的生命期。如果更改网站,然后重新启动服务器,则会丢弃旧的高速缓存。将高速缓存的 LaTeX 文本和图像名称存储在两个静态的 <CODE>Vector</CODE> 中。当标记处理器遇到 LaTeX 文本字符串时,它遍历高速缓存的 LaTeX 文本 <CODE>Vector</CODE>。如果它发现一个匹配项,则返回相应的图像名称。如果没发现,则继续生成一个新的公式图像并将新的 LaTeX 文本/图像对存储在高速缓存中。清单 7 说明了这个方法。</P>
. S. ^+ o" j1 b: y* z <P><A><B>清单 7. MathUtil.getImage()</B></A> 4 g3 \6 b4 n1 f' U3 n
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
0 x3 f/ l" Z& X+ p6 r 7 p6 z' p! t- [
<TR># U8 E: A) t& J3 `
<TD><PRE><CODE>public static synchronized String getImage (String path, String mode, String latexStr) throws Exception { // Formatted string stored in the latexStrVec vector. String comp = "MODE=" + mode + "\nLaTeX=" + latexStr.trim(); // Iterate through the "latexStrVec" cache and check if any // element matches the input string and mode. for (int i = 0; i < latexStrVec.size(); i++) { String l = (String) latexStrVec.elementAt(i); if ( comp.equals(l) ) { // Return the cached image name if a match is found. return (String) imageNameVec.elementAt(i); } } // If no match is found, generate a new image // and store it in the cache. return storeImage(mode, latexStr, latex2gif(path, mode, latexStr) );}</CODE></PRE></TD></TR></TABLE></P>
+ o/ ~: K. E O; e( Q <P>Test02.html 和 Test02.jsp 将高速缓存改良的标记处理器用于 <CODE><MathTag:*></CODE> 标记。尝试它们,然后在 <CODE><SlowMathTag:*></CODE> 标记上感受这一改进。</P>
& S5 W! K, E4 \. ]. P F( @ <P><A>减少中间文件</A>
9 c* o( W! o4 Y/ ]' n P7 @ 正如我们所见,实用程序使用很慢的磁盘文件互相通信,并且我们需要在完成该过程时删除所有中间文件。那些中间文件不仅造成了不必要的开销还容易导致磁盘和文件系统错误。在 UNIX/Linux 系统中,可以使用管道连接不同程序的输入和输出,并且部分消除对中间文件的需要。因为 Java 运行时不直接支持 UNIX 管道,所以我们必须从 shell 调用它们,如清单 8 所示。</P>
% s- H2 M# F0 y7 V. t( O; y: d, q <P><A><B>清单 8. 使用管道输入/输出调用命令</B></A> ) {. q0 k9 h. `( E. [/ M
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
: L: d& V8 O# P2 ]& z
% ^4 Z1 Z1 o# ^: @ <TR>: E2 l# x: h1 b% [7 |6 f
<TD><PRE><CODE>String convertCommand = "/usr/X11R6/bin/convert -antialias -crop 0x0";String dvipsCommand = "/usr/bin/dvips";// ...Process p = Runtime.getRuntime().exec( new String[] { "/bin/sh", "-c", dvipsCommand + " -f < " + filename + ".dvi | " + convertCommand + " - " + filename + ".gif" }, null, imagePath);</CODE></PRE></TD></TR></TABLE></P>
3 H3 o* X5 @0 K: ]4 N7 P) U <P>但是 LaTeX 程序不支持标准输入/输出管道,并且 Dvips 需要可搜索的输入(即,它不能是管道)。因此我们仍然需要 .tex 和 .dvi 中间文件。</P>
; M g2 @7 e. A: `; P <P>因为这个方法只适用于 UNIX,并且无法除去所有中间文件,所以我在示例程序中没有使用它。在下一节中,我们将讨论更一般和有效的方法。</P>3 h4 l/ y1 ] }7 M z
<P><A>用于外部应用程序的 Java API</A>. i% k! p% X; j E, B. I8 H/ V
象我们在上面所做的在独立 Java 运行时进程中调用单独的程序很方便。但是,这个方法不仅仅会导致我们已经遇到的中间文件问题;而且无法与复杂的 Java 应用程序一起很好地工作。我们无法容易地将外部程序集成到 Java 类分层体系结构中。</P>
3 ~: \3 M. h9 m" L <P>如果我们可以直接从 Java 程序调用 LaTeX 和图像转换实用程序功能,并使用 Java 内存中的缓冲区来交换进程间的数据,则创建的代码可以更快且可维护性更高。这需要 Java 编程 API 来访问那些功能。</P>6 a- Q( c$ y6 c i
<P>为非 Java 应用程序生成 Java API 的一种标准方法是使用 Java 本地接口(JNI)。要使用 JNI,需要重新编译带有新的 Java 可识别的头文件的非 Java 应用程序。到目前为止提到的所有数学工具都来自开放源码项目,并且它们的源代码可用于重新编译。我们可以修改源代码的输入/输出部分让那些工具能够处理输入/输出缓冲区而不是文件。实际上,已经开发了 JMagick,它的目的是将一个 JNI Java API 添加到 ImageMagick 库(请参阅<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>)。为所有数学工具创建 JNI API 明显超出了本文的范围。但是,如果您正在考虑实现复杂 Java 应用程序的类似体系结构,则可能要考虑朝这个方向努力。有关 JNI 的更多信息,请参考下面<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" ><FONT color=#000000>参考资料</FONT></A>一节。</P>
5 `; P# b- R, k5 ?4 l0 ^ <P><A>总结</A>
4 J( y. l9 \6 V- n 在本文中,您已经了解了几种提高效率的技术,它们将使用 JSP 定制标记把 LaTeX 样式的文本转换成数学图像。可以很简便地扩展这个方法,为其它高级内容管理技术(例如 <a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#advanced" target="_blank" ><FONT color=#000000>XSLT</FONT></A>)创建 Java 扩展。应该考虑还有什么其它工具可用来修改它们,然后集成到 Web 项目中。使用这里提到的技术,我们现在拥有了一些能创建含有数学密集型内容的高级网站的工具。</P>
* h; n# J" A* |+ j, Y* n& t <P>我想知道您关于如何改进这一解决方案的意见和建议。请使用<A>论坛</A>让大家分享您的想法。</P>4 P/ T. I% U) B1 N$ p
<P><A>参考资料</A>
+ k$ N# g0 j: g7 R( S <UL>2 T3 w! _' _$ f
<LI>请通过单击本文顶部或底部的<B>讨论</B>来参与本文的<A>论坛</A>。
' C: U; C: S2 q$ T+ p. J4 J r
- E" I1 z3 n, Q& l0 I! t. @% z ) u+ V0 p& R( S8 P# t& z* `
<LI>下载本文中示例项目的<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/JSPMathCode.zip" target="_blank" ><FONT color=#000000>源代码</FONT></A>。</LI></UL>
- X6 {+ c; z8 a8 f0 R9 e9 l; Y <P>
. I/ U4 u' w7 S! [ <UL>
; f! f d' o7 ~3 o$ A& t <LI>有关 JSP 的背景知识,请查看 Noel J. Bergman 的教程“<a href="http://www-105.ibm.com/developerWorks/education.nsf/java-onlinecourse-bytitle/882707E838C672A185256770004BDE72?OpenDocument" target="_blank" ><FONT color=#000000>Introduction to JSP technology</FONT></A>”(developerWorks,2001 年 8 月)。
4 S, ]. `4 M- a- F' n
" D9 v6 r) ?) E t! _) B$ j# e
+ f7 _2 U: v, h( e <LI>在“<a href="http://www-106.ibm.com/developerWorks/java/library/j-taglib/" target="_blank" ><FONT color=#000000>Take control of your JSP pages with custom tags</FONT></A>”一文中,Jeff K. Wilson 提供了定制标记库如何允许您向 JSP 添加更复杂逻辑、更稳定地控制数据显示以及在标记中共享数据的详细信息。* _- Y, L+ |+ ?5 }$ v6 |
+ ?* U7 W6 T3 q" M+ g6 n j6 q
: j2 o- {1 ]5 X w- n+ p# x
<LI><I><A herf="http://www.manning.com/fields2/">Web Development with JavaServer Pages, 2nd Edition</A> 一书(由 </I>D. K. Fields,M. A. Kolb 和 Shawn Bayern 合著)(Manning Publications,2001 年)是一本几乎包含了 JSP 各个方面的好书。
6 L, g, {1 p+ J; c# h
% K8 E5 ?! C7 w7 [- P0 O2 F: k) E
1 j4 g- V) h, b% D. I <LI><I><A herf="http://www.manning.com/shachor/">JSP Tag Libraries</A>(</I>由 Gal Shachor、Adam Chace 和 Magnus Rydin 合著)(Manning Publications,2001 年)对于如何设计和实现 JSP 定制标记库有一些好的示例和解释。$ B' R& ~1 k: x, m
{# @- a1 Z# e+ Y( U/ p
, K6 {0 p7 b" r q# B- a
<LI>在“<a href="http://www-106.ibm.com/developerWorks/java/library/j-jsptags.html" target="_blank" ><FONT color=#000000>JSP taglibs: Better usability by design</FONT></A>”(developerWorks,2001 年 12 月)中 Noel J. Bergman 讨论了 JSP 定制标记设计问题。
' @; q5 z: p5 a# \2 }1 E G
0 {3 @) B* y! ?, U " ?: Z. B0 P: K: I- x
<LI>WebSphere 开发者园地为那些想关注<a href="http://www-106.ibm.com/developerWorks/cgi-bin/click.cgi?url=http://www7b.boulder.ibm.com/wsdd/library/tutorials/vajwebsph353/Part-I/JSP11Part-I.html&origin=j" target="_blank" ><FONT color=#000000>将 JSP 定制标记和 VisualAge for Java 和 WebSphere Studio 组合起来</FONT></A>的人提供了一个很好的教程。</LI></UL>( Z: g6 }2 R- H2 |
<P><B>工具</B></P>
+ V* u: `/ q+ | <UL>
+ Q8 P1 J% D C4 q* F |; [ <LI>研究一下 <a href="http://www.latex-project.org/" target="_blank" ><FONT color=#000000>LaTeX</FONT></A> 项目的主页。. @+ _0 F* n) s! S# @6 S" R
; \9 `: |3 C) {9 X& E; C R
. ]( s* h7 C+ o, J. t, u% U <LI>查看 <a href="http://www.imagemagick.org/" target="_blank" ><FONT color=#000000>ImageMagick</FONT></A> 图像操作工具。
) H3 G9 f0 S- R7 x + @- C# s) G+ [5 K3 X* P q4 P
/ n+ p3 x( G' O$ J8 ~0 i& N- t <LI><a href="http://www.yeo.nu:81/jmagick/" target="_blank" ><FONT color=#000000>JMagick</FONT></A> 一个 ImageMagick Java API 的工作正在进行。8 J" O: L) Z! ~) |
: L, Q' O1 x! S3 @& _# p
! E0 W6 I( C4 g% ~9 ]0 V9 R t2 O <LI><a href="http://www.fourmilab.ch/webtools/textogif/textogif.html" target="_blank" ><FONT color=#000000>Textogif</FONT></A> 将 TeX/PostScript 文件转换成任何分辨率的 GIF 图像。) m7 c. o. t% ?& L! i
7 @1 u/ H$ j1 Y$ N; w ^ ! B( k7 m3 ^( I/ H/ G
<LI><a href="http://java.sun.com/products/jdk/1.2/docs/guide/jni/" target="_blank" ><FONT color=#000000>Java Native Interface</FONT></A>。</LI></UL>
) {4 x. q% w$ R9 O" |2 o# ^. S <P>
& Y0 _: ~5 w# S, D, y5 h <UL>" ]0 j7 I' {1 [# d7 [
<LI><a href="http://www.w3.org/Math/" target="_blank" ><FONT color=#000000>MathML</FONT></A> 是 W3C 的标准数学标记语言。3 |# o8 p' B% @+ j2 N
& k3 `0 T0 B) s
" G; Q% \ U5 u8 o( V <LI>查看 IBM 的数学文档浏览器 <a href="http://www-3.ibm.com/software/network/techexplorer/" target="_blank" ><FONT color=#000000>techexplorer</FONT></A>。</LI></UL>
/ G. Y8 I$ m# K" h7 a Z <P>. l* W) M0 C0 g) k' F( e
<UL>
% r6 @4 L c4 i4 d ?# w- c <LI>Pramod Kankure 教您如何“<a href="http://www-106.ibm.com/developerWorks/library/x-dynweb/index.html" target="_blank" ><FONT color=#000000>Produce Dynamic Web Pages with Java and XSLT</FONT></A>”(developerWorks,2001 年 4 月)。5 g g; T P7 U/ u' K
6 D, D9 @. Z! u* Z6 j2 F , z. C$ G; _5 r$ ~/ I
<LI>有关 Java 和 XSLT 的更多信息,请查看由 Taylor Cowan 所著的“<a href="http://www.javaworld.com/javaworld/jw-12-2001/jw-1221-xslt.html" target="_blank" ><FONT color=#000000>XSLT Blooms with Java</FONT></A>”(<I>JavaWorld,</I>2001 年 12 月)。</LI></UL>
# ~/ T6 @8 K6 t! U# M <P>
- o! I( _; u( S+ r: V4 d <TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
- @, ]- y* G/ |+ X 5 g$ ^3 K- d4 ]$ p4 ]- J
<TR>
7 i* c- w# G) U1 \% J0 Y <TD><A>关于作者
5 r9 ~. v( g* g% I# e Michael Yuan 是德州大学奥斯汀分校的博士研究生。他对使用服务器方 Java 技术来促进科学教育和研究很感兴趣。可以通过 <a href="http://www.shumo.com/bbs/mailtjuntao@astro.as.utexas.edu" target="_blank" ><FONT color=#000000>juntao@astro.as.utexas.edu</FONT></A> 与他联系。</TD></TR></TABLE></P>
zan