[转]XMLxsl

XPath & XSL

 

XML是一个完整的树状结构文档,从根节点到子节点一层一层往下。这有点类似于操作系统的文件的管理,从根目录到子目录,一层一层往下也形成树状结构(或许和Linux或是Unix相比较更相似一些,因为他们只定义了一个根目录,并没有很清晰的分区的概念,而Windows则出现了分区的概念,所以只能用一个盘里的目录结构来比较,不过其实也是可以在Windows的所有分区上层抽象出一个父节点作为根节点,这样,这种比较就更加直观了!)。正如我们需要在不同的目录中跳转以取得需要的文件和目录信息,所以有cddirls)等命令。在XML中,也需要在不同的节点跳转以获取节点的信息,并做一些查询工作,这样就诞生了XPath技术。XPathXML Path Language的缩写,它一种专门用来在XML文档中查询信息的语言。有人将XPath比作数据库中的SQL语句,也有人将XQuery比作数据库中的SQL语言,我没有具体的看过XQueryXPath有什么的区别,不过XQuery是基于XPath的,因此获取这两种比喻都是可以成立的,我不完整的理解是XQuery只是对XPath的一种封装,就像现在出现的很多新技术其实并不能谈上什么新技术,这些所谓的新技术只是对传统的旧的技术的方法框架的总结,然后用自己的方式去套一层框架,以自己的一套实现方式使编程变得更加的简单,美之名曰:新技术。这正是应了中国的一句俗语:换汤不换药。

什么是XPathw3school.com.cn给出的解释是:

· XPath 使用路径表达式在 XML 文档中进行导航

· XPath 包含一个标准函数库

· XPath XSLT 中的主要元素

· XPath 是一个 W3C 标准

个人感觉用第一句话就可以给出了XPath的本质意义了。XPath就是在XML文档中导航的技术,它借用了路径表达式的技术,所以取名为PathXPath是一种非常有用的技术,它实现了在XML文档的不同节点之间的导航,因而其他涉及到对XML文档节点处理的技术一般都会用到XPath技术,比如XSLXPointerXQuery等技术。

 

XPath数据类型

boolean函数的参数:

如果为数值型,且数值为0或是NaN,返回true,否则false

如果是空节点集合,返回true,否则为false

如果是空片段,返回true,否则为false

如果是是字符型,且字符长度为0,返回true,否则false

布尔表达式有:

=&lt;(<)&gt;(>)&lt;=(<=)&gt;=(>=)

XPath7种节点类型

其实这些节点核心都是围绕元素节点。如下图:

XPath表达式

       说了那么多,现在应该是切入主题的时候了。XPathXML文档中的导航主要是通过表达式和函数来实现的。一个表达式返回的结果可以是一个单独的节点、一组节点、一个布尔值、一个浮点数或是一个字符串。如在以下的XML文档中:

<?xml version="1.0" encoding="ISO-8859-1"?>

<bookstore>

<book>

 <title lang="en">Harry Potter</title>

 <author>J K. Rowling</author>

 <year>2005</year>

 <price>29.99</price>

</book>

</bookstore>

/bookstore/book 表示选择元素book

注:XPath表达式不能在文档中定位XML声明(因为XML声明不是XML文档结构的一部分)。它也不能定位嵌入的DTD声明或是CDATA块。

定位节点

XPath表达式中主要用两个符号来表达节点的定位:

“/” 当前“目录”下的元素节点,它是一种绝对路径的表达方式,因而要从根目录开始。

       如果是 ”/” 则表示选取根节点。

“//” 当前“目录”下的所有相应的元素节点。它是一种相对路径的表达方式,会搜索当前路

       径下的所有相应的元素。如 ”//book” 表示在当前文档下的所有含book元素的节点。

另外可以用 “*” 符号表达一些未知的元素,如 “//*” 表达选择当前文档下所有元素。

例如:

/bookstore//year     表示bookstore元素下的所有year元素

/*/price                 表示所有在第二层元素中含有price元素的元素。

选择分支

XPath表达式中可以用[]表达选择分支。如:

/bookstore/book[@lang]               表示/bookstore/book中所有含有lang属性的book元素

/bookstore/book[price=29.99]              表示/bookstore/book中所有price=29.99book元素

/bookstore/book[price=29.99]/price表示/bookstore/book中所有price=29.99price元素

/bookstore/book[1]                      表示/bookstore下的第一个book元素(从1而非0开始索引)

/bookstore/book[last()]                表示/bookstore下的最后一个book元素

通过 | 选择多个路径

XPath中可以使用 | 操作符来选择多个路径。如:

//title | //@lang       表达所有含所有title元素以及所有lang属性。

选择属性

XPath中,属性以@开头,其他的操作类似于元素的选择于定位。

//book[@lang]        选择所有含有lang属性的book元素

//@lang                 选择所有lang属性

//book[@*]            选择所有book元素

一些常用的操作符和专用字符

已经提到过的有 “/” “//” “*”等。

另外还有:

.             表示当前上下文(“目录”)

..            表示父节点(“父目录”)

:             表示从元素或属性中分离命名空间的前缀

()            表示组合操作

[]            引用一个过滤模式

+ - * div mod 表示 操作

XPath定位路径

一个XPath的定位路径将返回一个节点集。这就是节点定位的另一种表达方式。可以有绝对路径(/step/step/…)和相对路径(step/step/…)的表达。定位路径语法为:

轴名::节点测试[预测]

轴为定位路径和当前节点的关系。

节点测试为当前要选择的类型和名称。节点测试可以是子节点名、属性名、函数等。

预测相当于节点定位的选择分支,它提供一些过滤条件。

如:

child::price             表示当前节点中的子节点中的所有price元素

attribute::src           表示当前节点的src属性

child::text()            表示当前节点的所有文本子节点

child::*/child::price 表示当前节点的所有孙子节点中的所有price元素

child::book[position()=1]       表示当前节点第一个book子元素

parent::book[attribute::type=”string”] 表示当前节点中typestring的所有book父节点

轴:

ancestor                      当前节点的所有祖先节点

ancestor-of-self      当前节点或是所有祖先节点

attribute                 当前节点的所有属性

child                            当前节点的所有子节点

descendant            当前节点的所有后代节点

descendant-or-self 当前节点或是所有它的后代节点

following              文档中当前节点结束标记后的所有节点

following-sibling     文档中当前节点后的所有同层节点

namespace            当前节点中的所有含有命名空间的节点

parent                   当前节点的父节点

preceding              文档中当前节点标记开始前的所有节点

preceding-sibling    文档中当前节点钱的所有同层节点

self                       当前节点本身

一些常用定位路径的缩写

直接省略       表示child::           pricechild::price的缩写

@                  表示attribute::       @type=”string”attribute::type=”string”的缩写

.                    表示self::node()    

..                   表示parent::node()

//                   表示/descendant-or-self::node()/

XPath标准库函数

XPath库函数主要有:节点函数,字符串函数,数值函数以及布尔函数。

节点函数有:position, last(), count(), name(), id(string), idref(), key(), doc(), docref(), local-part(), namespace(), qname(), generate-id()

字符串函数:concat(), contains(), normalize-space(), starts-with(), string(), substring(), substring-after(), substring-before(), translate()

数值函数:ceiling(), floor(), number(), round(), sum()

布尔函数:boolean(), false(), not(), true()

具体可以参看:http://www.w3school.com.cn/xpath/xpath_functions.asp

XSL概述

XSLExtensible Stylesheet Language,扩展样式表语言)是一种基于XML的语言,它用来转换XML文档到另一种文档。在功能上,它类似于CSS,但是它是专门为XML设计的样式显示文档,而CSS是专门为HTML设计的,因而在显示功能上,它表达能力更强,也更加符合XML的规范,比如XSL不仅可以决定XML文档的显示方式,而且可以想添加或是移动元素,它还能重新排列或是索引数据等。不过它也比CSS更加的复杂。XSL是通过将XML文档转换为其他文档(如HTML文档)来控制它的显示效果,同时它也提供了一些格式化的操作。因而XSL包含了两部分:一部分用来转换XML文档,一部分用来格式化XML文档。对于这两种不同的需求,出现了三种技术:XSLTExtensible Stylesheet Language Transformations):一种用于转换XMl文档的语言,XPathXML Path Language):一种用于在XML文档中导航的语言,XSL-FO(Extensible Stylesheet Language Formatting Objects):一种用于格式化XML文档的语言。

可以将XSL理解成:

l         一种将XML转换成HTML的语言。

l         一种可以过滤和分类XML数据的语言。

l         一种可以对一个XML文档部分进行寻址的语言。

l         一种可以基于数值格式化XML数据的语言。(如用红色显示负数)

l         一种向不同设备输出XML数据的语言。(如屏幕、纸张等)

 

使用XSL样式表将XML文档转换成其他不同格式的文档(如HTML)有三种主要处理方式:

1.         XML文件和XSL样式表文件都发送到客户端,客户端根据XSL样式表文件来转换XML文件,并将他们呈现给用户。这种方式服务器的处理相对简单,但是要传输两个文件,而且不利于数据的保密,会被某些不法用户得到一些服务器内部信息。但是由于它不用在服务器端处理,因而相应速度会好一些。

2.         服务器将XSL样式表引用于XML文档,并把转换后的文档发送给客户端。这种方式会加重服务器的负担。只能用于处理数据量不大的情况下。

3.         第三方应用程序将原XML文档转换为某种形式的文档(一般为HTML),然后将此文档放到服务器上。服务器把转换后的文档发送给客户端。这种方式可以减轻服务器的负担,数据保密良好,唯一的要求就是需要一个第三方转换程序。这种方式和前面一种方式一样因为要在服务器端处理转换,所以反应速度会相对减慢。

XSL样式表对XML文档的转换过程:

1.       根据XML文档构造一个源树,XSL处理器根据XSL样式表文件的指示对这个源树进行排序、复制、过滤、删除、选择、运算等操作后产生一颗结果树。这种转换协议通过XSLT来完成。

2.       在生成结果树后,对其进行解释以产生一种合适显示、打印或播放的文件,即格式化。

XSL文档结构

XSL文档的标准格式如下:

<?xml version=”1.0” encoding=”gb2312”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

模板规则

输出模板

</xsl:stylesheet>

模板规则分为模式(Pattern)和模板(Template)。模式用来定义模板规则所适用的树状结构,模板用来与此模式匹配时进行输出。

XML文档中对XSL的引用:

<?xml-stylesheet type=”text/xsl” href=”要引用的xsl文件的路径及文件名”?>

有一下XML文件:

<?xml version="1.0" encoding="ISO-8859-1"?>

<?xml-stylesheet type=”text/xsl” href=”tableFormat.xsl”?>

<bookstore>

<book category="COOKING">

 <title lang="en">Everyday Italian</title>

 <author>Giada De Laurentiis</author>

 <year>2005</year>

  <price>30.00</price>

</book>

<book category="CHILDREN">

 <title lang="en">Harry Potter</title>

 <author>J K. Rowling</author>

 <year>2005</year>

 <price>29.99</price>

</book>

<book category="WEB">

 <title lang="en">XQuery Kick Start</title>

 <author>James McGovern</author>

 <author>Per Bothner</author>

 <author>Kurt Cagle</author>

 <author>James Linn</author>

 <author>Vaidyanathan Nagarajan</author>

 <year>2003</year>

 <price>49.99</price>

</book>

<book category="WEB">

 <title lang="en">Learning XML</title>

 <author>Erik T. Ray</author>

 <year>2003</year>

 <price>39.95</price>

</book>

</bookstore>

XSL文件tableFormat.xsl定义如下:

<?xml version=”1.0” encoding=”gb2312”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

<xsl:template match=”/”>

       <html>

       <head><title>购书中心数据显示表</title></head>

       <body>

              <h2 align=”center”>购书中心数据显示表</h2>

              <table border=”1” cellpadding=”0” align=”center”>

                     <tr>

                            <th>书名</th>

                            <th>语言</th>

                            <th>作者</th>

                            <th>出版时间</th>

                            <th>价格</th>

                     </tr>

                     <xsl:for-each select=”/bookstore/book”>

                            <tr>

                                   <td><xsl:value-of select=”title” /></td>

                                   <td><xsl:value-of select=”@lang” /></td>

                                   <td><xsl:value-of select=”auther” /></td>

                                   <td><xsl:value-of select=”year” /></td>

                                   <td><xsl:value-of select=”price” /></td>

                            </tr>

                     </xsl:for-each>

              </table>

       </html>

</xsl:template>

</xsl:stylesheet>

XSLTXSLT模板)元素的语法

       XSLT用于将XML文档转换为其他形式的文档,如HTML文件、PDF文件、甚至一段声音。目前XSLT最主要的功能是将XML文档转换为HTML文档。

       XSLT由一个或多个模板构成。模板由两部分组成:匹配模式(Match Pattern)和执行。匹配模式定义XML文档中被模板处理的节点;执行定义输出的格式。这两部分分别对应的元素为:xsl:templatexsl:apply-templates

       对执行,感觉这个词用的不好,开始我就被弄的糊里糊涂的,不知道这次词在表达的是什么意思。因而有必要做一个具体的介绍。xsl:apply-templates元素是相当于子函数的调用,在xsl:template元素内调用这样一个“子函数”,而这个“子函数”的实现则在另外的地方时先,它的实现也是通过xsl:template元素来定义。并且xsl:apply-templates元素会为它的被调用地中的每个匹配元素调用一次它的实现模板,所以我们也看到了它的定义中的templates是复数形式的。调用和被调用之间的识别是通过匹配模式集来识别的。这样讲解比较抽象,以上面的例子为例,上面我们是用xsl:for-each元素来实现的,但是我们也可以用xsl:apply-templates元素来实现:

<?xml version=”1.0” encoding=”gb2312”?>

<xsl:stylesheet version=”1.0” xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>

<xsl:template match=”/”>

       <html>

       <head><title>购书中心数据显示表</title></head>

       <body>

              <h2 align=”center”>购书中心数据显示表</h2>

              <table border=”1” cellpadding=”0” align=”center”>

                     <tr>

                            <th>书名</th>

                            <th>语言</th>

                            <th>作者</th>

                            <th>出版时间</th>

                            <th>价格</th>

                     </tr>

                     <tr>

                            <xsl:apply-templates select=”/bookstore/book” />

                     </tr>

              </table>

       </html>

</xsl:template>

<xsl:template math=”book”>

<td><xsl:value-of select=”title” /></td>

       <td><xsl:value-of select=”@lang” /></td>

       <td><xsl:value-of select=”auther” /></td>

       <td><xsl:value-of select=”year” /></td>

       <td><xsl:value-of select=”price” /></td>

</xsl:template>

</xsl:stylesheet>

xsl:template元素

xsl:template元素是XSL样式表中最重要的元素。每一个模板都由一个xsl:template元素构成,即xsl:template元素是模板的根元素。其语法定义规则如下:

<xsl:template match=”模板应用于输入文档的节点集(XPath表达式返回的结果)”>

       模板的内容…..

</xsl:template>

模板的内容是要输出的文档的结构,如上例中的定义,输出一张以HTML格式定义的表格。

xsl:apply-templates元素

xsl:apply-templates元素指示处理器发现一个合适的模板,而且在每个选择的元素上执行指定的任务。可以将其理解为子函数的调用。定义语法如下:

<xsl:apply-templates select=”要处理元素的匹配模式”>

</xsl:apply-templates>

xsl:value-of元素获取节点值

XSL通过xsl:value-of元素来获取XML文件中被选择的元素或属性的内容。格式如下:

<xsl:value-of />

表示选取当前XML文件元素集中的所有元素或属性的内容。

<xsl:value-of select=”匹配模式” />

匹配模式由XPath表达式确定,因而它可以有许多中形式的定义。同XPath,属性名前需加@或是使用轴定义(attribute::)。

xsl:for-each元素

xsl:for-each元素循环被选择的节点集,并对每个节点进行一次处理。格式:

<xsl:for-each select=”匹配模式”>

       要处理输出的内容….

</xsl:for-each>

xsl:value-of元素只能用于获取确切的节点的内容,如果有多个节点,则xsl:value-of元素只处理第一项元素。因而对匹配模式存在多个元素的返回集中,可以用xsl:for-each元素和xsl:value-of元素合在一起使用,如上面的例子所示。

模板的默认规则

1.       元素默认规则

<xsl:template math=”* | /(任何元素节点或根节点)”>

<xsl:apply-templates />

</xsl:template>

2.       文本节点默认规则

<xsl:template math=”text()(所有文本节点)”>

        <xsl:value-of select=”.” />

</xls:template>

这两个规则同时使用,可以使一张空的XSL样式表,对于输入的XML文档,可以默认的输出该XML文档的所有内容字符。这种默认规则的优先级很低,因而只要有其他的定义,就可以覆盖他们的定义。

空的样式表定义如下:

<?xml version=”1.0”?>

<xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform” >

</xsl:stylesheet>

对输出元素排序

xsl:sort元素可以对输出元素按规则排序。xsl:sort元素可以作为xls:apply-templates元素或xsl:for-each元素的子元素出现。其定义如下:

<xsl:sort select=”元素名” order=”ascending | descending”

case-order=”upper-first | lower-first” data-type=”text | number | qname” />

data-type表示被排序的数据类型,可以是数值、文本或用户自定义,默认为文本。

select定义排序输出元素的关键字。

order表示升序还是降序排序。默认为ascending

case-order表示大小写的排序规则。即大写在前还是小写在前。

如:

<xsl:apply-templates select=”/bookstore/book”>

       <xsl:sort select=”price” data-type=”number” order=”descending” />

</xsl:apply-templates>

选择

XSL提供了两个元素,他们可以根据输入文档来改变输出内容。

1xsl:if元素:根据输入文档中存在的模式,决定是否输出给定的内容。

2xls:choose元素:根据文档中存在的模式,从几个可能的XML段中选出一个。

xsl:if元素定义:

<xsl:if test=”匹配模式”>

</xsl:if>

匹配模式可以任意的XPath表达式,因而这里可以测试元素名,元素内容,属性内容等。

如:

<xsl:if text=”price[.=10.0]”>

       <font color=”red”>

              <xsl:value-of select=”price” />

       </font>

</xsl:if>

xls:choose元素定义:

<xsl:choose>

       <xsl:when test=”匹配模式”>

              输出内容

       </xsl:when>

       <xsl:when test=”匹配模式”>

              输出内容….

       </xsl:when>

       ….

       <xsl:otherwise>

              输出内容….

       <xsl:otherwise>

</xsl:choose>

输出内容的扩展

之前的内容中,我们一直在做把XML文档转换为HTML文档。而且所做的都是将XML文档中元素的内容或属性内容转换为HTML文档中的内容。然后XSL定义的是将XML文档转换为另一种的文档,它可以是HTML文档,也可以是另一种的XML文档,因而不可避免的,有时候需要根据XML文档中的内容决定输出的内容,包括输出的元素标签值,元素的属性值,元素的内容,注释等。因而XSL样式表中定义了几种方式来实现这样的功能:属性值模板、xsl:element元素、xsl:attribute元素、xsl:pi元素、xsl:comment元素和xsl:text元素等。

属性值模板:

使用{}格式将输入的属性值或是元素值写入相应的输出流,多个{}可以一起使用,表示连接(如{id}{title}形式)。如:

对以下源XML文档:

<DVD id=”01”>

<title>wind</title>

<format>movie</format>

<genre>classic</genre>

</DVD>

可以通过以下XSL文档转换成<DVD id=”01” title=”wind” format=”movie”>classic</DVD>格式的XML文档:

<xsl:template math=”DVD”>

       <DVD id=”{@id}” title=”{title}” format=”{movie}”>

              <xsl:value-of select=”genre” />

       </DVD>

</xsl:template>

xsl:element元素

<xsl:element name=”要创建元素的名称” namespace=”所创建元素的命名空间

       use-attribute-sets=”属性集限定名” />

该元素实现动态的根据输入XML文档的值,在输出文档中创建新的元素。

如上例子的转换中可以将源XML文档转换成:

<wind format=”movie”>classic</wind>

通过以下的XSL文档的转换即可:

<xsl:element name=”{title}” >

       <xsl:attribute name=”format”>

              <xsl:value-of select=”format” />

       </xsl:attribute>

       <xsl:value-of select=”genre” />

</xsl:element>

xsl:attribute元素

<xsl:attribute name=”属性名” namespace=”创建属性的命名空间”>

       属性的值….

</xsl:attribute>

xsl:attribute元素一般作为xsl:element元素的子元素来使用,如上例。也可以作为xls:attribute-set元素的子元素。

xsl:attribute-set元素

<xsl:attribute-set name=”属性集限定名” use-attribute-sets=”已定义的属性值限定名”>

       <xsl:attribute ….. >…..</xsl:attribute>

       …….

</xsl:attribute-set>

注:属性集中定义的属性可以被使用的元素中自己的同名属性覆盖。

且属性集的定义可以在xsl:template元素之外。

xsl:comment元素和xsl:text元素

这两个元素都直接像输出文件中输出注释或文本,由于文本可以直接插入输出文件中,所以xsl:text元素只是在需要保留文本格式的时候,如一段代码,使用,因为改元素可以原封不动的保留定义的文本格式。如:

<xsl:comment> This is a comment! </xsl:comment>

<xsl:text> space    reserved     ! </xsl:text>

xsl:copy元素和xsl:copy-of元素

<xsl:copy use-attribute-sets=”属性集名”>

       要复制的内容

</xsl:copy>

xsl:copy中的内容输出到输出流。

<xsl:copy><xsl:apply-templates /><xsl:copy> 将当前元素的内容复制到输出流。

<xsl:copy><apply-templates select=”* | @* | comment() | pi() | text()” /><xsl:copy>

将当前元素原封不动的复制到输出流。

简单的,可以直接通过xsl:copy-of元素来复制匹配节点的所有元素(包括命名空间,子节点,节点属性等)。

<xsl:copy-of select=”匹配模式” />

如:<xsl:copy-of select=”price” />

xsl:number元素

xsl:number元素可以在输出文件中插入格式化整数。由value属性计算出数值,通过四舍五入转换成整数,然后根据format属性对整数进行格式化,最后插入该整数。语法如下:

<xsl:number level=”single | multiple | any” count=”pattern” from=”pattern” value=”number-expression” format={string} lang={nmtoken} letter-value={“alphabetic | traditional”} grouping-separator={char} grouping-size={number} />

这个元素比较复杂,我还没完全理解意思。先放下!

xsl:variable元素

定义全局变量或是局部变量,一定复制就不能改变。或许说常量更适合一些,因为它的定义的用处就和宏定义的用处是一样的。

<xsl:variable name=”变量限定名” select=”表达式”>

       变量值….

</xsl:variable>

若存在select属性,则表达式的值即为定义的变量的值,此时,变量的内容必须为空。

访问该变量只要在变量名钱加上$即可。

如:<xsl:value-of select=”$define” />

类似的功能和定义的有xsl:param元素。

<xsl:param name=”参数名” select=”表达式”>

       参数值….

</xsl:param>

这两个元素的定义和要求都是一样的,个人感觉用法也是一样的,不知道为什么会同时存在。

另有类似的定义有:xsl:with-param元素。

<xsl:with-param name=”参数名” select=”表达式”>

       参数值….

</xsl:with-param>

这些属性的要求都是一样的,这个元素主要是用于xsl:call-templatexsl:apply-templates元素中,作为传入的参数使用。参数的使用也是在参数名前加$符。

命名模板

xsl:template元素中加入name属性,就可以创建命名模板。这样就可以通过xsl:call-template元素在其他的xsl:template元素中用模板名调用改模板。

<xsl:call-template name=”templateName” />

关于空白节点

<xsl:strip-space/xsl:preserve-space element=”elementName” />

xsl:strip-space元素用于删除被选择节点中的空白节点

xsl:preserve-space元素用于保留内容中的任何空白

也可以在在xsl:stylesheet元素中设置indent-result属性为yes,以允许处理程序将多于的空白插入到输出文档中。

<xsl:stylesheet xmlns:xsl=” http://www.w3.org/TR/WD-xsl” ident-result=”yes”>

…..

</xsl:stylesheet>

导入其他样式表

可以通过xsl:importxsl:include元素导入另一个XSL文件。

<xsl:import/xsl:include href=”location” />

它们两个所不同的是:

xls:import只能直接包含在xsl:stylesheet之后,而xsl:include则可以在最后一个xsl:import之后的任意外置。

 

参考:

http://www.w3schools.com/XML

XML完全开发指南》 科学出版社 孙更新 裴红义 杨金龙 编著

http://en.wikipedia.org/wiki/XML

原文地址:https://www.cnblogs.com/bingzisky/p/1374669.html