2.4 一路酒席招待三路宾朋——利用模板实现跨编程语言
当第一次听说 MapGuide 可以支持使用 ASP.net 、 JSP 和 PHP 进行服务器端编程的时候,我很是惊讶。在我看来,这简直是一个不可能的任务。这是因为,虽然三种语言都可以用来产生动态网页,但是他们采用的技术也千差万别,甚至语法都各不相同。如果要支持三种语言,就要维护三套 API ,维护三套网页生成代码。举个例子,如果发现网页中存在某个问题,那么,就需要在 ASP.net 、 JSP 和 PHP 的网页生成程序中分别修改一次,这样的维护代价是很高的。
要想减小代码的维护代价,就要最大限度地实现代码重用。所谓代码重用,就是通过使用合理的结构,让一处代码在编写之后能够在多个地方使用而无需重新编写。代码重用的根本在于找到变化多端的需求中那个神奇的“不变的”地方,把它剥离开来,这个“不变的”就是将来我们可以重用的代码。而对于 MapGuide 的网页生成代码来说,编程语言是变化的,页面大小是变化的,连加载的地图内容都是变化的。那么,到底存不存在这个神奇的“不变的”东西呢?
答案是肯定的, MapGuide 找到的答案就是 —— 网页的 HTML 大部分是不变的,与地图无关,与大小无关,与编程语言无关。
当我们在使用各种网页生成语言的时候,我们往往会被里面各种抽象出来的新名词弄得眼花缭乱。其实,无论网页生成语言上面模型有多么复杂,其根本只有一个,那就是生成网页代码。服务器端眼花缭乱的组件技术,最终只是用来生成一个可以在网络上传递、可以被浏览器解析的字符串而已;浏览器中不管看起来多么绚丽,也只是源于对这个来自服务器的字符串的解析。而这个字符串中不变的部分,就是我们这里提到的——模板。
下面让我们以 asp.net 框架中的 mapframe.aspx 中 AJAX 部分的代码为例,看看 MapGuide 是如何使用这种模板技术来生成网页的吧。其他语言中的方案虽然有可能在语法上略有不同,但其核心思想是一样的。
mapframe.aspx 就是我们在 2.2 一节中说到的 mapFrame ,用来放置地图(包括缩放滑块)、图例窗格和属性窗格。该文件位于 web 安装路径下的 mapviewernet 页面中(默认路径为“ C:/Program Files/OSGeo/MapGuide/Web/www/mapviewernet/mapframe.aspx ”)。我们可以直接用文本编辑器打开这个文件。(下面及本章其余部分所称的页面行号均指的是 MapGuide 2.1.0 版本中该网页文件中的行号,其他版本该页面文件中的相应行号可能略有不同)
首先( 28-46 行),该页面定义了一些变量并进行了初始化。接下来,在 48 行,页面通过调用 GetRequestParameter 函数(这个函数的定义在这个网页文件的下面 215-250 行)将请求中的参数给这些变量中的一部分赋值。然后,代码根据网页类型不同,执行两套生成网页代码中的一套。为了更有代表性,我们这里以 AJAX 部分的代码为例, DWF 部分的代码与此大同小异。
AJAX 部分的代码主体位于页面代码的 85-189 行。其中 85-155 行都在初始化一个地图并且将地图的一些属性值赋值给一些网页内的跟地图信息有关的变量。而再接下来的这四行代码就是整个页面代码的关键:
String templ = MgLocalizer.Localize(LoadTemplate(Request, "../viewerfiles/ajaxmappane.templ"), locale, GetClientOS(Request)); String vpath = GetSurroundVirtualPath(Request); String[] vals = {/* 这里省略众多数组中的变量 */ }; Response.Write(Substitute(templ, vals)); |
代码 2-2 mapframe.aspx 利用模板生成网页文件的关键代码
第一行的意思是加载模板文件,并根据浏览器请求中的区域信息将国际化好的模板进行本地化。这里 “../viewerfiles/ajaxmappane.templ” 就是对应的模板文件的位置,我们会在后面讲解这个模板文件的内容。
第二行与模板无关,其作用是获取当前网页所在的虚拟目录的地址,这个地址用于组织下一行的文本数组中的某些变量的值。
第三行初始化了一个文本数组,这个文本数组里面含有前面所初始化的各个变量的值。
第四行, Substitute 函数把第三行组织出来的文本数组填充到第一行加载的本地化好的模板文件中,生成网页内容字符串,再由 Response 将其发送到客户端。
纵观整个 mapframe.aspx ,我们不难看出,在执行最终填充内容之前,我们一直在准备那些“变化的”部分。最后的 Substitue 函数( php 就是 printf 函数)把我们变化的部分和不变的模板进行融合,最后生成我们需要的浏览器可以解析的网页文件。
不管是 asp.net 、 JSP 还是 PHP ,大部分 MapGuide 的网页代码都是采用这样的组织形式,在 viewerfiles 下有一个后缀名为 templ 的模板文件,在 mapviewernet/php/java 中有一个用服务器端编程语言编写的文件,这个文件组织一些动态的内容,最后利用一个类似 Substitute 的函数把这个动态的内容填充到模板文件中。
所以,如果您想看某个页面的内容,比如 taskbar.aspx/php/jsp ,那么你不妨去看看 viewerfiles 下的 taskbar.templ 文件,那里面有你需要的大部分 HTML 代码。
2.4.1 就照着这样来——模板文件的格式
前面说到,网页生成代码的关键是利用一个函数把一个文本数组填充到一个模板中。那么, Substitute 是根据什么原则将文本数组填充到模板中的呢?换句话说,模板的格式是什么样子的呢?
我们以 viewerfiles/ajaxmappane.templ 为例,介绍一下模板文件是基于什么样的规则编写的。我们可以使用文本编辑器打开这个程序,不难发现,这个文件跟一个 HTML 网页文件大体相同,但是从第 250 行起,模板文件中开始出现一些不符合 HTML 规范的 “%s” 符号,这些符号表示的就是用来填充文本的位置。 Substitute 每读到一个“ %s ”,就用文本数组中的文本依次替换掉这个“ %s ”,最终形成一个网页的全部内容。
那么,如果网页中需要出现 “%s” 该怎么办呢?我们只要在模板中使用 “%%s” ,这样替换程序就会把它替换成为 “%s” 而不是用文本数组中的内容填充了。
2.4.2 民族的就是世界的——网页文件的国际化 与本地化
根据前面的内容,我们可以看到,利用模板,我们已经能够尽可能地将网页中不变的部分分离出来。对于网页来说,还有一个很重要的话题,就是国际化与本地化。一个网页要能被使用不同语言的人浏览,就需要能够根据浏览器的语言不同,提供不同的含有独特语言信息的网页内容。那么,模板是如何解决这个问题的呢?
我们依旧以 mapframe.aspx 为例,其实前面已经有所提及,跟本地化有关的代码在 159 行上,该行代码如下面所示:
String templ = MgLocalizer.Localize(LoadTemplate(Request, "../viewerfiles/ajaxmappane.templ"), locale, GetClientOS(Request)); |
代码 2-3 mapframe.aspx 中国际化相关的代码
MgLocalizer 类的 Localize 函数实现了代码的本地化,它的第一个参数是一个经过国际化好的模板,第二个参数是浏览器的区域信息,第三个参数是浏览器所在机器的操作系统的相关信息。
那么,什么样的模板是经过国际化的呢?
我们依旧以 ajaxmappane.templ 这个模板文件为例。在第 324 行您会看到类似“ __#ENDSEL#__ ”的代码。这就表示在本地化的时候, Localize 函数会用“ ENDSEL ”本地化后的文本替换掉“ __#ENDSEL#__ ”。
那么,本地化的文件位于什么位置呢?
网页文件的本地化文件位于 web 安装路径下的 localized 文件夹下,默认路径是“ C:/Program Files/OSGeo/MapGuide/Web/www/localized ”。如果您的区域是“ en ”,那么本地化的内容就放置在这个文件夹下名为“ en ”的文件中。这个文件的每行一般都遵循的是“国际化 ID = 本地化文本”的格式。(如果以“ # ”开头,则表示这一行是注释),比如根据 en 文件的 221 行, ENDSEL 在区域是“ en ”的情况下本地化文本为“ CTRL + click to end ”,所以在区域是“ en ”的情况下, ajaxmappane.templ 中 324 行的“ __#ENDSEL#__ ”将会被替换为“ CTRL + click to end ”。
通过本节的介绍,您已经对 MapGuide 是如何组织代码结构有了一定的了解,也了解到网页 HTML 中的内容大部分是来自于 viewerfiles 下的 templ 文件。那么,请您跟随我,去看一下 MapGuide 是如何从一个简单的 URL 请求开始,一步步变换,最后展现出一张漂亮的地图来的。