xml xsd xpath xsl css 简单入门...
时间:2010-08-11 来源:FireCoder
问题:
公司里面的一个项目需要建立一个Test Benchmark 来比较不同算法计算的mileage。
需求定义:
输入:预先收集的一套Test case,可以通过页面添加新的Test case
输出:不同算法在Test case上运行的结果
Solution
分析需求我们发现
- 只有少量的操作(页面)
- 结构化的数据(TestCase, TestResult)
我们的项目是运行在Tomcat下的Axis2的App,可以整个DB table来存储Test case和Test Result,然后使用jsp来生成动态页面。但考虑到大炮不能打蚊子的原则,存储一块我们选用xml;然后我们即可想到使用xsl来格式化(美化)xml,因为浏览器对xml是树状显示,而我们通常查看数据的方式是表格。
最终:选择使用XML instead of jsp
这里对xml的第一个概念就是:结构化数据的文件存储. 在互联网中,结构化的数据大量存在,比如酷讯网机票的查询,垂直搜索中的商品列表,淘宝中的商品列表。注意结构化的数据存储使用最多的就是数据库。
1. 定义XSD文件
xsd(XML Schema Definition) 是什么?
数据库中我们会有数据库的schema,就是各个表格的定义,描述了表格有哪些字段,这些字段的类型,是否为空等。XSD就是xml的schema,描述该xml文件的元素。下面的XSD例子,我们定义了测试的输入集inputSet由多个testCase组成(unbounded说明testCase在inputSet中是多个),每个testCase有四个element(trackId, start, ...)和三个属性(testCaseId)。
element vs attribute中提到如果该information是essential material 就是用element,如果是补充说明,使用attribute。同样在stackoverflow中提及了使用attribute的一些限制.
为什么要定义XSD,直接生成xml文件不就完事?我们生成方式无非两种:手写或程序生成。程序生成,就必须定义XSD,让程序知道如何去解析(unmarshall)/生成(marshall)xml文件。- 注意这里程序生成并不是说你使用类似于手写的方式在程序中将一个xml文件拼接出来,在解析时则使用某个xml parser(dom, jdom, sax, dom4j)将xml parser并转换成你的java 对象。传统的方式:
- 生成:类手写式拼接出xml
- 解析:xml parser -> dom tree(or parser event) -> your target java object
可以看到,这种方式一是容易出错,二是不够高效。
<?xml version="1.0"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="inputSet"> <xs:complexType> <xs:sequence> <xs:element ref="testCase" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="testCase"> <xs:complexType> <xs:sequence> <xs:element name="trackId" type="xs:string" /> <xs:element name="start" type="xs:string" /> <xs:element name="end" type="xs:string" /> <xs:element name="mileage" type="xs:double" /> </xs:sequence> <xs:attribute name="testCaseId" type="xs:int" use="required" /> <xs:attribute name="ptn" type="xs:string" use="optional" /> <xs:attribute name="description" type="xs:string" use="optional" /> </xs:complexType> </xs:element> <xs:element name="resultSet"> <xs:complexType> <xs:sequence> <xs:element ref="testRecord" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="testRecord"> <xs:complexType> <xs:sequence> <xs:element name="testCaseId" type="xs:int" /> <xs:element name="algoName" type="xs:string" /> <xs:element name="mileage" type="xs:double" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
2. 使用JAXB 编译xsd,生成相应java class
JAXB(Java Architecture for XML Binding)是一些流行框架Castor , jBind被JDK吸纳的产物。有了JAXB,我们只需要要定义个xsd,然后可以自动生成java class。使用JAXB的marshaller和unmarshaller,非常快速简洁的生成/解析xml文件。JAXB技术简而言之:
- XSD 和 java class 之间的相互转换
- java对象实例和xml文件之间的相互转换:由java对象实例直接生成xml文件;解析xml文件至java对象实例
我们指定class所在的package,后文中生成jaxb context对象使用此package name
xjc -d . -p com.xx.benchmark TestCase.xsd
3. 使用JAXB来marshall和unmarshall xml文件,xml文件和java对象实例相互转换
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <inputSet> <testCase description="" ptn="111" testCaseId="1"> <trackId>111</trackId> <start>2010-06-23 6:14pm PDT</start> <end>2010-06-23 6:34pm PDT</end> <mileage>7.7</mileage> </testCase> <testCase description="" ptn="111" testCaseId="2"> <trackId>111</trackId> <start>2010-08-08 04:21PM PDT</start> <end>2010-08-08 11:08PM PDT </end> <mileage>107.4</mileage> </testCase> </inputSet>
解析和生成xml文件,都只需要短短两行代码,this is life, so easy?
//create jaxb context JAXBContext jc = JAXBContext.newInstance(packageName); //xml -> java instance Unmarshaller u = jc.createUnmarshaller(); InputSet inputSet = (InputSet) u.unmarshal(new File(path));' //java instance -> xml Marshaller m = jc.createMarshaller(); m.marshal(inputSet, new File(path));
4. 前问我们提到,在浏览器中打开xml文件,呈现树状浏览, 于是想要用xsl来改变xml显示方式
如同css来美化html文件一样,xsl(eXtensible Style sheet Language)就是xml的样式语言
4.1 定义xsl文件
要定义xsl文件,需要使用xpath来定位、遍历xml中的节点
<?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- http://www.jenitennison.com/xslt/grouping/muenchian.html --> <xsl:template match="inputSet"> <html> <head> <mce:style><!-- table { border-collapse:collapse; border-spacing:0; border-left:1px solid #000; border-top:1px solid #000; font-size:12px; font-family:sans-serif,Arial; } th,td{ border-right:1px solid#000; border-bottom:1px solid #000; padding:5px 15px; } th {font-weight:bold;background:#ccc; } --></mce:style><style mce_bogus="1"> table { border-collapse:collapse; border-spacing:0; border-left:1px solid #000; border-top:1px solid #000; font-size:12px; font-family:sans-serif,Arial; } th,td{ border-right:1px solid#000; border-bottom:1px solid #000; padding:5px 15px; } th {font-weight:bold;background:#ccc; } </style> </head> <body> <h3>Mileage Test Case</h3> <table> <!-- header --> <xsl:for-each select="testCase[position()=1]"> <tr> <th> testCaseId </th> <xsl:for-each select="attribute::*"> <xsl:if test="not(position()=last())"> <th> <xsl:value-of select="name()" /> </th> </xsl:if> </xsl:for-each> <xsl:for-each select="child::*"> <th> <xsl:value-of select="name()" /> </th> </xsl:for-each> </tr> </xsl:for-each> <!-- data --> <xsl:for-each select="testCase"> <!-- <xsl:sort select="testCaseId" /> --> <tr> <th> <xsl:value-of select="attribute::testCaseId" /> </th> <xsl:for-each select="attribute::*"> <xsl:if test="not(position()=last())"> <td> <xsl:value-of select="." /> </td> </xsl:if> </xsl:for-each> <xsl:for-each select="child::*"> <td> <xsl:value-of select="." /> </td> </xsl:for-each> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
4.2 关联xsl文件
在xml中加入xml-stylesheet声明即可
<?xml version='1.0'?> <?xml-stylesheet type='text/xsl' href='TestResult.xsl' ?>
由于我们的TestResult xml文件是由java class marshall自动生成,为了避免生成xml文件后再手工修改,我们使用了Marshaller.JAXB_FRAGMENT, Boolean.TRUE 选项
Marshaller m = TestCaseService.getMarshaller(); m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); StringWriter sw = new StringWriter(); sw.append("<?xml version='1.0'?>"); sw.append("<?xml-stylesheet type='text/xsl' href='TestResult.xsl' ?>"); ResultSet set = new ResultSet(); set.testRecord = records; m.marshal(set, sw); File f = new File(path); System.out.println("save test result to: " + f.getAbsolutePath()); FileWriter fw = new FileWriter(f); fw.write(sw.getBuffer().toString()); fw.close();
5. 在使用xsl时,我们遇到了一个问题,就是向xsl传入参数。我们使用google chart生成了如下图片,并希望把这个图片和xml中的数据一起显示在一个页面中。由于图片的url是动态生成,所以只能作为参数传入xsl
5.1 借助java xml transformat api来向xsl传入参数
java xml transformat 可以将xml文件根据指定的xsl文件,转换成一个目标文件(往往是html文件)
TransformerFactory transformerFactory = TransformerFactory .newInstance(); StreamSource source = new StreamSource(xmlFile); StreamResult result = new StreamResult(htmlFile); StreamSource style = new StreamSource(TestCaseService .getTestResultXslFile()); Transformer transformer = transformerFactory.newTransformer(style); for (int i = 0; i < parameter.length / 2; i++) { transformer.setParameter(parameter[i * 2], parameter[i * 2 + 1]); } transformer.transform(source, result);
5.2 在xsl文件中使用<xsl:param>声明并使用此变量
<?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:param name="chart"> no chart </xsl:param> <xsl:key name="record-by-algo" match="testRecord" use="algoName" /> <xsl:key name="record-by-tid" match="testRecord" use="testCaseId" /> <!-- http://www.jenitennison.com/xslt/grouping/muenchian.html --> <xsl:template match="resultSet"> <html> <head> <mce:style><!-- table { border-collapse:collapse; border-spacing:0; border-left:1px solid #000; border-top:1px solid #000; font-size:12px; font-family:sans-serif,Arial; } th,td{ border-right:1px solid#000; border-bottom:1px solid #000; padding:5px 15px; } th {font-weight:bold;background:#ccc; } --></mce:style><style mce_bogus="1"> table { border-collapse:collapse; border-spacing:0; border-left:1px solid #000; border-top:1px solid #000; font-size:12px; font-family:sans-serif,Arial; } th,td{ border-right:1px solid#000; border-bottom:1px solid #000; padding:5px 15px; } th {font-weight:bold;background:#ccc; } </style> </head> <body> <h3>Mileage Algo Compare</h3> <xsl:value-of select='$chart' disable-output-escaping="yes" /> <br /> <table> <!-- header --> <tr> <th>caseId</th> <xsl:for-each select="testRecord[generate-id() = generate-id(key('record-by-algo', algoName)[1])]"> <!-- <xsl:sort select="position()" order="descending" /> --> <th> <xsl:value-of select="algoName" /> </th> </xsl:for-each> </tr> <!-- data --> <xsl:for-each select="testRecord[count(. | key('record-by-tid', testCaseId)[1]) = 1]"> <!-- <xsl:sort select="testCaseId" /> --> <tr> <th> <xsl:value-of select="testCaseId" /> </th> <xsl:for-each select="key('record-by-tid', testCaseId)"> <xsl:sort select="algoName" /> <td> <xsl:value-of select="mileage" /> </td> </xsl:for-each> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
6. 生成html文件后,似乎万事大吉,可以直接在浏览器里面查看google chart和表格数据
但表格数据字体以及边框太丑,于是乎又需要用到css来美化html
<head> <mce:style><!-- body { font-size: 8px; font-family: sans-serif, Arial; } .texta { width: 200px; } td { text-align: left; } tr.option { font-style: italic; } --></mce:style><style mce_bogus="1">body { font-size: 8px; font-family: sans-serif, Arial; } .texta { width: 200px; } td { text-align: left; } tr.option { font-style: italic; }</style> </head>
7. 由于我们的应用是部署在tomcat中(的axis2应用),servlet 相关:
7.1 redirection
response.sendRedirect(response.encodeRedirectURL("list"));
7.2 relative path absolute path
relative path 是相对于当前url的路径
absolute path 是根路径
absolute path
context path
8. 小结
xml 小结
xpath 小结
xsl 小结
css 小结