使用 Inkscape 和 XSLT 创建跨平台报表和表单
我在一家医疗保健公司工作,开发应用软件。我和我的同事负责编写软件来处理医疗保健索赔、管理工作流程,并尽可能提高公司效率。我们最近决定替换一个第三方软件,该软件可以将医疗保健索赔数据覆盖到标准的 HIPAA(健康保险可携性与责任法案)索赔表单上。该软件会将数据转换为 PDF 文件,我们将其存储在大型文件服务器上。每个 PDF 文件包含一份索赔及其相应的表单。我们决定替换该软件是因为我们需要更灵活的解决方案。我们想要一种可以动态创建索赔图像且不占用服务器空间的方案。
医疗保健索赔非常复杂(图 1)。需要绘制许多框和样板文本。使用软件应用程序执行此操作的传统方法是使用坐标和长度绘制一系列线条,然后将静态和动态内容放在新绘制的线条之上。编程像这样的应用程序的过程漫长而乏味,更不用说容易出错。我们想要一种更易于创建和维护的方案。我们的要求如下:
我们必须能够打印高质量版本的索赔。
必须可以从 Web 浏览器访问索赔。
该解决方案必须与编程语言无关。我们使用 Python、PHP、Perl 和 Java。需要使用这些语言中的任何一种来创建图像。
我们必须能够将索赔数据和表单转换为几种不同的文件格式,特别是 PDF 和 PNG。
整个解决方案必须是平台无关的。
在审查了这些要求之后,我们研究了几种不同的开源和闭源选项。它们都不能满足我们的所有要求,因此我们转向创建自己的解决方案。我们尝试扫描空白索赔表单,并使用 ImageMagick 将索赔数据放在上面。这几乎给了我们想要的东西。问题是,在所有需要的语言中创建解决方案将是乏味且冗余的。接下来,我们转向 FOP(Formatting Objects Processor)。这个解决方案更接近我们想要的。但是,创建索赔表单将花费太长时间。此外,该解决方案实际上也不是语言无关的(FOP 是一个 Java 库)。我们可以为 FOP 命令行界面编写包装器,但我们确信仍然有更好的解决方案。
在探索 FOP 解决方案时,我们想到了使用可缩放矢量图形 (SVG)。基本上,我们将采用索赔表单的 SVG 图像,并将其制成 XSLT(可扩展样式表语言转换),因为 SVG 格式是一种特殊的 XML 格式。然后,我们将从数据库中提取索赔数据,并将其转换为 XML 字符串。使用我们的任何语言,我们都可以使用 XSLT 和 XML 创建索赔的 SVG 图像。这个解决方案满足了我们的所有要求。它是语言和平台无关的。我们可以打印 SVG 图像并将它们嵌入到网页中。此外,SVG 图像可以轻松转换为不同的文件格式。此解决方案的另一个优点是 SVG 图像的文件大小很小。如果我们想存档图像,它们将占用旧解决方案所占用空间的一小部分。由于 SVG 图像是文本,而不是压缩的二进制文件,因此可以压缩文件并节省更多空间。
使 SVG 解决方案如此吸引人的因素之一是创建和维护表单的主 SVG 图像是如此容易。为此,我们将使用 Inkscape。Inkscape 是一款 SVG 创作工具,可在 Linux、Mac OS X、Windows 和其他类 UNIX 操作系统上运行。还有其他 SVG 创作工具可用,但我们选择 Inkscape 是因为它开源,并且它位于大多数 Linux 发行版的软件包管理器中。
我们创建主 SVG 的第一件事是打开 Inkscape 并创建一个新的 US Letter 尺寸文档。为了保持井井有条,我们在新文档中创建了四个图层:扫描、覆盖、样板和动态文本。使用扫描图层,我们导入了索赔的扫描件。这样做使我们无需测量任何东西即可将所有内容在 Inkscape 画布上对齐。导入图像后,我们锁定了该图层,使其不会被意外修改。实际上,在我们完成 SVG 上的每个图层后,我们都会锁定它以确保它不被篡改。
接下来,我们使用覆盖图层来描绘我们导入的原始索赔中的所有线条和框。这一步有点棘手。当我们扫描的图像最初创建时,线条的间距出于某种原因并不均匀。我们决定在我们的版本上正确对齐事物。幸运的是,Inkscape 具有自动执行此操作的工具。通过选择所有需要均匀间隔的对象(Shift+左键单击)并使用“对齐和分布”对话框(菜单中的“对象”→“对齐和分布”),Inkscape 修复了间距问题。完成后,我们得到了如图 2 所示的内容。
绘制完所有线条后,就该添加所有样板文本了。为此,我们使用了恰如其名的样板图层。在我们开始之前,我们决定完全删除扫描图层,因为我们不再需要它了。为了正确对齐文本,我们使用了 Inkscape 中的参考线。参考线正是名称所暗示的——仅存在于 Inkscape 内部的参考线,用于对齐对象。要使用参考线,只需单击顶部或左侧边距,然后将线条拖动到位即可。为了充分利用参考线,我们启用了“捕捉点到参考线”功能(“文件”→“文档首选项”→“参考线”)。这样做使我们能够将所有文本完全对齐。图 3 显示了完成此步骤后的 SVG 的外观。
最后,我们切换到动态文本图层,并添加了将放置索赔数据的占位符。同样,我们使用参考线对齐所有内容。对于文本占位符,我们为每个文本块使用单个 $ 符号。然后,为了简化以后的操作,我们将每个动态文本对象重命名为相关的名称。我们通过左键单击对象并转到菜单中的“对象”→“对象属性”来完成此操作。图 4 显示了带有参考线的最终主 SVG。
创建主 SVG 大约花费了整整四个小时的工作时间。我敢肯定,以编程方式完成这项工作需要几天时间。
一旦我们完成了主 SVG,就该将其转换为 XSLT 了。由于 SVG 图像只是 XML 文件,因此我们使用文本编辑器添加了所有 XSLT 标记。转换 SVG 是一个相当简单的事情。为了使其成为真正的 XSLT,只需要在标头中添加几行。列表 1 显示了修改前 SVG 的几行。列表 2 显示了带有 XSLT 标记的相同行集。
列表 1. 修改前的 SVG 的几行
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg ... </svg>
列表 2. 带有 XSLT 标记的相同行集
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <xsl:stylesheet version="1.0" ↪> <xsl:template match="/claim"> <svg ... </svg> </xsl:template> </xsl:stylesheet>
如您所见,有四行新行。第一行新行声明此文件为 XSLT。第二行新行包含一个 XPath(XML 路径语言)表达式,该表达式与我们的索赔数据 XML 中的根元素匹配。此行告诉 XML 转换引擎从哪里开始读取 XML 以进行转换。最后两行新行只是关闭打开的 xsl 标签。
此时,XSLT 可以与我们的索赔数据 XML 结合使用以生成 SVG。但是,生成的 SVG 将看起来与我们修改之前 SVG 的样子相同。为了使其真正显示索赔数据,我们必须进入 XSLT 并添加所有 XPath 表达式以填充 SVG。由于我们将 SVG 对象划分为图层,因此我们只需要修改动态文本图层。在 SVG XML 中,动态文本图层只不过是一系列文本标签。列表 3 显示了我们的索赔表单上“患者城市”框的文本标签。
列表 3. 索赔表单上“患者城市”框的文本标签
<text xml:space="preserve" style="..." x="33.237278" y="231.77995" id="textPatientCity" sodipodi:linespacing="125.00000%" inkscape:label="#text7272"> <tspan sodipodi:role="line" id="tspan7274" x="33.237278" y="231.77995"><xsl:value-of select="patient/address/city"/></tspan></text>
当 XSLT 应用于索赔数据 XML 时,/claim/patient/address/city 的值将在此处被替换。我们浏览了整个 XSLT,并在它们所属的位置添加了适当的 XPath 表达式。在特殊情况下,我们还添加了 XPath 条件逻辑和格式规则。
如前所述,我们所有的索赔数据都在数据库中——更具体地说,是 Postgres 数据库。由于我们想要一个与语言无关的解决方案,因此我们必须设计一种方法,在不依赖特定编程语言的情况下,将索赔数据从数据库中提取出来并转换为 XML 格式。我的一位开发人员同事提出了编写一系列 PL/pgSQL 函数以返回包含 XML 数据的单个 XML 字符串的想法。他的解决方案非常出色,并且非常适合。我们获取索赔数据所需要做的就是运行一个带有索赔 ID 的小查询(列表 4)。结果是格式良好的 XML,我们用它来制作索赔图像。
最初,创建此解决方案的主要目的是在我们的 Web 界面中显示索赔。我们所有的 Web 应用程序都是用 PHP5 编写的,并在 Apache/mod_php 环境中运行。为了进行 XSLT 转换,我们使用了 PHP 中的 XSL 函数。这组函数作为 PHP 的扩展出现。该扩展是 libxslt C 库的前端。
XSLT 扩展使过渡变得容易。列表 5 显示了 PHP 脚本的一部分,该脚本将索赔 XML 转换为 SVG 并在浏览器中显示它。
列表 5. 将索赔 XML 转换为 SVG 并在浏览器中显示的 PHP 脚本的一部分
// import the SVG XSLT $xsl = new XSLTProcessor(); $xsl->importStyleSheet(DOMDocument::load("svg_xslt.xsl")); // load the claim data XML // $claim is the database result from Listing 4 $doc = new DOMDocument(); $doc->loadXML($claim); // tell the browser this is an SVG document header("Content-Type: image/svg+xml"); // print the SVG to the browser echo $xsl->transformToXML($doc);
列表 5 是我们解决方案的简化版本。在我们的解决方案中,一份索赔可能有多页。为了解决这个问题,我们必须进行多次转换,每页一次。为了使多页索赔在同一个浏览器窗口中显示,我们必须嵌入它们。这可以使用 embed 和 object HTML 标签来完成。请注意,在使用这些标签时,浏览器兼容性存在一些问题。为了解决兼容性问题,我们编写了一个脚本来检查用户的浏览器并决定使用哪个标签。然后,我们将目标对象数据/嵌入式源设置为类似于列表 5 中的脚本。这允许 Web 浏览器在同一窗口中显示多个 SVG 图像。
在 Web 浏览器环境中使用 SVG 图像时,必须考虑其他因素。Internet Explorer 不原生支持 SVG 图像。用户被迫使用第三方插件来显示图像。Adobe 免费提供其中一个插件。Mozilla Firefox 从 1.5 版本开始内置了对 SVG 图像的支持。但是,Firefox 不支持 SVG 图像的几个方面,例如缩放和分组对象。幸运的是,我们所有的用户都使用最新版本的 Firefox。
这就是全部内容。图 5 显示了填充了所有数据的索赔图像。
在我们完成解决方案的 Web 端之后,我们将目光转向我们集成的其余部分。这意味着我们必须打印 SVG 图像并找到一种存档它们的方法。一些客户要求我们将索赔的副本打印和/或以电子方式发送给他们。由于我们所有的后端软件都是用 Python 编写的,因此这也意味着我们必须用不同的语言进行 XML 转换。为了完成所有 XML 工作,我们使用了 4Suite XML API。
为了打印图像,我们再次求助于 Inkscape,因为我们的 PostScript 打印机驱动程序无法打印 SVG 图像。Inkscape 有一些命令行选项,可以告诉 Inkscape 在命令行模式下运行,从而抑制图形界面。我们用于打印的选项是 -p 选项。这与 lpr 命令结合使用,使我们无需任何用户交互即可打印图像。列表 6 显示了我们在列表 5 中所做的相同转换,只是现在在 Python 中。该示例还显示了我们如何调用 Inkscape 来打印我们的索赔图像。
列表 6. 与列表 5 中所示相同的转换,只是使用 Python
from Ft.Xml.Xslt import Processor from Ft.Xml import InputSource from Ft.Xml.Domlette import NonvalidatingReader // load the claim data XML // claim is the database result from Listing 4 doc = NonvalidatingReader.parseString(claim, "http://spam.com/doc.xml") // load and process the XSLT xsl = InputSource.DefaultFactory.fromUri("file://svg_xslt.xsl") processor = Processor.Processor() processor.appendStylesheet(xsl) // do the transformation result = processor.runNode(doc, "http://spam.com/doc.xml") // write the SVG to a file f = open("/tmp/"+ claim +".svg", "w") f.write(result) f.close() // print the image on the default printer os.system("inkscape /tmp/"+ claim +".svg -p | lpr")
早些时候,我提到我们经常每个索赔有多页。打印时,这不是问题;我们只是将每页作为单独的作业发送到打印机。当涉及到存档时,我们必须做一些不同的事情。与 Web 界面一样,我们必须将页面分组,这次是分组到一个文件中,而不是一个 Web 浏览器中。存档时,我们必须以 PDF 格式存储文件,因为这是我们的客户想要的。为了将图像转换为 PDF 并组合多页索赔,我们使用了 Inkscape 和 Ghostscript。
与打印一样,Inkscape 具有将文件导出为 PostScript 格式的选项。我们不使用 -p,而是使用 -P 并将所需的输出文件名传递给 Inkscape。在将索赔的所有页面写入文件后,我们使用以下 Ghostscript 命令将页面放入单个 PDF 并存档它们:
gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=out.pdf /tmp/foo1.ps /tmp/foo2.ps
在我们完成该项目后不久,我们就面临着对表单布局进行两轮更改。第一轮更改涉及文本对象的位置。第二轮更改范围更广——我们必须在表单上绘制一系列新框以适应新的识别系统。由于我们无法在 Inkscape 中打开修改后的 SVG,因此我们必须对主 SVG 进行更改,然后手动将其应用于 XSLT 版本。
起初,我们认为进行更改会很困难和乏味,但事实证明该过程很简单。对于第一轮,我们只是使用 Inkscape 在主文件中进行了更改,注意记录我们更改的对象。然后,使用文本编辑器,我们将旧的 XML 部分替换为 XSLT 中的新部分。由于第二批更改仅是添加内容,因此我们决定简单地在主文件中创建另一个图层以添加框。当我们完成添加新框后,我们只需使用文本编辑器将新图层复制到 XSLT 中。
Chad Files 是一名软件开发人员,居住在阿肯色州康威市。他是一位狂热的徒步旅行者和长期的 Linux 用户。欢迎您通过 cpfiles@gmail.com 向他发送评论。