XSLT是一種用來轉換XML文件格式的文件,配合.NET可以快速的開發簡單的文件轉換程式。較早之前就有遇到朋友用XSLT轉換文件成為Excel可讀的文件,但我暫時沒有打算用XSLT轉換出OOXML,因為這樣做很複雜很花時間,用SDK會比較有效率。不過XSLT在很多地方還是很有用,能夠快速地把資料格式化成你想要的樣子。例如從網路上找到的結構化資料,把它們轉換成我們想要的格式,但有時候來源資料並不是我們預期中結構化,所以這裡就引入MSXSL腳本。
XSLT是一種用來轉換XML文件格式的文件,配合.NET可以快速的開發簡單的文件轉換程式。較早之前就有遇到朋友用XSLT轉換文件成為Excel可讀的文件,但我暫時沒有打算用XSLT轉換出OOXML,因為這樣做很複雜很花時間,用SDK會比較有效率。不過XSLT在很多地方還是很有用,能夠快速地把資料格式化成你想要的樣子。例如從網路上找到的結構化資料,把它們轉換成我們想要的格式,但有時候來源資料並不是我們預期中結構化,所以這裡就引入MSXSL腳本。
這個範例我會把從網路上得到的貨幣資料轉換成我要的XML格式,準備用來做XMLDataProvider。
<?xml version="1.0" encoding="utf-8" ?> <root> <option value="ARS">Argentine Peso (ARS AR$)</option> <option value="AUD">Australian Dollar (AUD A$)</option> <option value="BOB">Bolivian Boliviano (BOB Bs)</option> <option value="BRL">Brazilian Real (BRL R$)</option> <option value="GBP">British Pound Sterling (GBP UK£)</option> <option value="BGN">Bulgarian Lev (BGN)</option> <option value="CAD">Canadian Dollar (CAD CA$)</option> <option value="CLP">Chilean Peso (CLP CL$)</option> <option value="CNY">Chinese Yuan Renminbi (CNY CN¥)</option> <option value="COP">Colombian Peso (COP CO$)</option> <option value="HRK">Croatian Kuna (HRK kn)</option> <option value="CZK">Czech Republic Koruna (CZK Kč)</option> <option value="DKK">Danish Krone (DKK Dkr)</option> <option value="EGP">Egyptian Pound (EGP EG£)</option> <option value="EEK">Estonian Kroon (EEK Ekr)</option> <option value="EUR">Euro (EUR €)</option> <option value="HKD">Hong Kong Dollar (HKD HK$)</option> <option value="HUF">Hungarian Forint (HUF Ft)</option> <option value="INR">Indian Rupee (INR Rs.)</option> <option value="IDR">Indonesian Rupiah (IDR Rp)</option> <option value="ILS">Israeli New Sheqel (ILS ₪)</option> <option value="JPY">Japanese Yen (JPY ¥)</option> <option value="LTL">Lithuanian Litas (LTL Lt)</option> <option value="MYR">Malaysian Ringgit (MYR RM)</option> <option value="MXN">Mexican Peso (MXN)</option> <option value="MAD">Moroccan Dirham (MAD)</option> <option value="TWD">New Taiwan Dollar (TWD NT$)</option> <option value="NZD">New Zealand Dollar (NZD NZ$)</option> <option value="NOK">Norwegian Krone (NOK Nkr)</option> <option value="PKR">Pakistani Rupee (PKR PKRs)</option> <option value="PEN">Peruvian Nuevo Sol (PEN S/.)</option> <option value="PHP">Philippine Peso (PHP Php)</option> <option value="PLN">Polish Zloty (PLN zł)</option> <option value="RON">Romanian Leu (RON)</option> <option value="RUB">Russian Ruble (RUB)</option> <option value="SAR">Saudi Riyal (SAR SR)</option> <option value="RSD">Serbian Dinar (RSD din.)</option> <option value="SGD">Singapore Dollar (SGD S$)</option> <option value="ZAR">South African Rand (ZAR R)</option> <option value="KRW">South Korean Won (KRW ₩)</option> <option value="SEK">Swedish Krona (SEK Skr)</option> <option value="CHF">Swiss Franc (CHF Fr.)</option> <option value="THB">Thai Baht (THB ฿)</option> <option value="TRY">Turkish Lira (TRY TL)</option> <option value="UAH">Ukrainian Hryvnia (UAH)</option> <option value="AED">United Arab Emirates Dirham (AED)</option> <option value="USD">US Dollar (USD $)</option> <option value="VEF">Venezuelan Bolívar Fuerte (VEF Bs.F.)</option> <option value="VND">Vietnamese Dong (VND ₫)</option> </root>
目標輸出這樣的XML。
<root> <Currency> <Code>ARS</Code> <Symbol>AR$</Symbol> <Name>Argentine Peso</Name> </Currency> ....... </root>
要做到這件事,首先了解幾個基本的xsl元素。
<xsl:template /> 輸出文件的樣板定義在這個元素裡。
<xsl:for-each/ select="xpath" />對用xpath取得的所有節點做迭代。
<xsl:value-of select="xpath" />用xpath取得內容。
其他元素定義可以到W3C查閱。
接下來簡單說一下怎樣取得你想要的內容,假設你已在一個節點的scope裡,要取得該節點的屬性,就用select="@attributeName",如果要取得元素中間的內容,即<element>內容</element>,就用select="."。
簡化版的XSLT定義如下
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" > <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <root> <xsl:for-each select="root/option"> <Currency> <Code> <xsl:value-of select="@value"/> </Code> <Name> <xsl:value-of select="."/> </Name> </Currency> </xsl:for-each> </root> </xsl:template> </xsl:stylesheet>
但這樣我們還有一個問題,要怎樣把內容裡的名字和幣值符號分離呢?方法有不止一個,XSLT 2.0裡有常規表示式的功能。但我決定用MSXSL腳本,更快的完成我要的功能,畢業用C#比去學XSLT 2.0的Regex要快得多。
要使用MSXSL Script,有幾個地方是必需要定義的。首先<xsl:stylesheet />裡要加入script的namespace。然後<msxsl:script implements-prefix="在stylesheet剛才加入的namespace">也一定要定義。接下來在裡面寫一般的C#程式。如果要引用using,可以在<msxsl:script />裡用<msxml:assembly />配合<msxsl:using />來達成,但我在這裡偷懶用長名稱。
至於傳參數至腳本,有兩種方法,一種是直接用xpath,跟<xsl:value-of select="xpath"/>的沒有不一樣,另一種是自己定義<xsl:variable />,再用$variableName去使用。下面的XSL文件兩種都會用到。
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="urn:my-scripts" > <msxsl:script language="C#" implements-prefix="user"> <![CDATA[ public string splitFullName(string innertext) { string m_inputPattern = @"^(?<FullName>.+)\s\((?<Symbol>.+)\)$"; System.Text.RegularExpressions.Regex l_parser = new System.Text.RegularExpressions.Regex(m_inputPattern); Match l_matchParser = l_parser.Match(innertext); if(l_matchParser.Success) { return l_matchParser.Result(@"${FullName}"); } else {return "match fail";} } public string splitSymbol(string innertext) { string m_inputPattern = @"^(?<FullName>.+)\s\((?<Symbol>.+)\)$"; System.Text.RegularExpressions.Regex l_parser = new System.Text.RegularExpressions.Regex(m_inputPattern); Match l_matchParser = l_parser.Match(innertext); if(l_matchParser.Success) { return l_matchParser.Result(@"${Symbol}"); } else {return "match fail";} } public string splitSymbol(string innertext, string shortName) { string m_inputPattern = @"^(?<FullName>.+)\s\((?<Symbol>.+)\)$"; System.Text.RegularExpressions.Regex l_parser = new System.Text.RegularExpressions.Regex(m_inputPattern); Match l_matchParser = l_parser.Match(innertext); if(l_matchParser.Success) { string l_raw = l_matchParser.Result(@"${Symbol}"); if(l_raw.StartsWith(shortName) && l_raw.Length > shortName.Length) { l_raw = l_raw.Remove(0, shortName.Length).Trim(); } return l_raw; } else {return "match fail";} } ]]> </msxsl:script> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <root> <xsl:for-each select="root/option"> <Currency> <Code> <xsl:value-of select="@value"/> </Code> <xsl:variable name="innertext" select="." /> <Symbol> <xsl:value-of select="user:splitSymbol($innertext, @value)"/> </Symbol> <Name> <xsl:value-of select="user:splitFullName($innertext)"/> </Name> </Currency> </xsl:for-each> </root> </xsl:template> </xsl:stylesheet>
XSL已準備好,接下來就要用程式對XML輸入做轉換。.NET提供了兩個類別XslTransform和XslCompiledTransform來處理XSL。後面再說明有甚麼差別,我這裡為求方便使用XslTransform類別。
XslCompiledTransform l_xsl = new XslCompiledTransform(); l_xsl.Load("XSLT文件路徑"); l_xsl.Transform("輸入文件路徑","輸出文件路徑");
就是這樣,成功把XML文件轉換成我想要的結構。至於為甚麼會提到XslCompiledTransform是因為用到MSXSL腳本的話,在伺服器環境下會造成Memory Leak,所以用XslCompiledTransform去自己控制腳本的編譯來防止Leak。其實XslTransform已過時,請使用XslCompiledTransform。Memory Leak這方面我還沒有實際去使用,有需要的人要自己處理了。如果有這方面經驗的人歡迎指教。感謝大家。