本文章是由機器翻譯。

Windows PowerShell

建置容易使用的 XML 介面與 Windows PowerShell 互動

Joe Leibowitz

下載代碼示例

Windows PowerShell 指令碼語言不會你想要一個命令列工具,要做的一切 — — 和多得多 — — 它可能最終取代 VBScript 等技術。Windows PowerShell 是關於什麼好一般說明和使用它的基本操作,請參閱 bit.ly/LE4SU6bit.ly/eBucBI

Windows PowerShell 徹底與集成 Microsoft.net 框架,並因此深深地連接到 XML 資料交換使用結構化的文本的檔的當前國際標準。有關 XML 的一般資訊,請參閱 bit.ly/JHfzw

本文介紹 Windows PowerShell 現狀和操作,目標是創造一個相對簡單的使用者介面,用於讀取和編輯 XML 檔的 XML 資料的能力。這個想法是檔的使這更容易、 更方便使用的演算法"理解"對於任何給定,即使沒有悲傷的架構的同級和父-子關係。我還會檢查 Windows 表單在 Windows PowerShell、 XPath 查詢和其它相關的技術的使用。擬議的應用程式可以消化的 XML 檔,併發出自己的 XPath 查詢。

讓我們看看如何可以分析 Windows PowerShell 中的任何 XML 檔並沒有先進的技術技能的人可以使用的格式顯示它。圖 1 顯示的圖形化使用者介面,您可以創建的類型的預覽。

Preliminary View of the GUI
圖 1 初步視圖的圖形化使用者介面

實現這一目標的關鍵是要使 Windows 電源外殼應用程式來分析和理解任何無人指導或預知其架構的 XML 檔。經過研究,現有的技術,自動分析的 XML 檔,我決定開發解析引擎為此特定的目的,因為我是能夠找到並沒有完全解決,需要瞭解無人觀看的 XML 文檔。目前,應用程式似乎普遍假定開發人員或使用者是擔當的元素、 屬性和整體任何的架構給定的 XML 文檔。但一些 — — 可能是很多 — — 在真實世界的情況不屬於這個範例。例如,在許多資料消費者誰不是 XML 專家,但那些需要訪問 XML 資料來源的各種方案中,現存的范式的熟悉程度假設將失敗。同樣,即使受過培訓的專家或兩個工作人員,如果一個組織面臨的數百個或數千個不同結構化 XML 檔,人類處理可能很容易會不堪重負。

因此,所需要的是培訓的一個解析的引擎,它會讀取任何 XML 檔併發出 Xpath,普通使用者而言,只有最少,可以使用搜索和編輯任何給定的 XML 檔。

XML 語法分析引擎

要符合 XML,文檔的結束和開放括弧必須匹配。例如,如果一個元素 <ABC> 存在,那裡還必須存在於同一檔的元素中有些遲點 </ABC>。之間這些左、 右角括弧,幾乎任何東西都可以從理論上發生。使用 XML 的這一基本原則,我帶你如何自動構建一系列全面的 XPath 查詢,這樣即使相對缺乏經驗的 XML 資料的消費者可以迅速把它們要用於查找和處理 XML 檔中的資料。

第一,建立一套的陣列,以容納所有括弧和右括弧中的 XML 檔:

 

[int[]]$leading_brackets = @()
[int[]]$closing_brackets = @()
[string[]]$leading_value = @()
[string[]]$closing_value = @()

若要生成為強型別的 Windows PowerShell 中的未知大小陣列,三個元素是必要的:[類型 []] 領先的一部分 ; $名稱某部分 ; 與陣列的未知的大小,() @ 符號。 Windows PowerShell 中的變數以美元作為其前導字元。 這些特定的陣列蓋打開和關閉角括弧中的 XML 文檔,以及與這些支架相關聯的元素名稱的字串值的索引的位置。 例如,在 XML 中線 <PS1> 文本值 </PS1>、 領先的方括弧的整數索引將是 0 和右括弧的索引將是 15。 領導和收盤值在這種情況下會 PS1。

我們的目標 XML 進入記憶體,我們使用以下代碼:

 

$xdoc = New-Object System.Xml.XmlDocument
       $xdoc.Load("C:\temp\XMLSample.xml")

圖 2 是正在使用的實際 XML 檔的局部視圖。

圖 2 局部視圖的示例 XML 檔

<?xml version="1.0" encoding="utf-8"?>
<Sciences>
  <Chemistry>
    <Organic ID="C1" origination="Ancient Greece" age="2800 yrs">
      <branch ID="C1a">
        <size>300</size>
        <price>1000</price>
        <degree>easy&gt;</degree>
        <origin>Athens</origin>
        // Text for organic chem here
      </branch>
      <branch name="early" ID="C1b" source="Egypt" number="14">
        <size>100</size>
        <price>3000</price>
        <degree>hard&gt;</degree>
        <origin>Alexandria</origin>
        // Text for original Egyptian science
      </branch>
    </Organic>
  </Chemistry>
<Physics></Physics>
<Biology ID="B" origination="17th century" >
.
.
.
      <Trees4a name="trees4a" age="40000000">
        <type ID="Tda1">oakda</type>
        <type ID="Tda2">elmda</type>
        <type ID="Tda3">oakd3a</type>
      </Trees4a>
    </Plants>
  </Biology>
</Sciences>

載入操作之後,此 XML 資料是在記憶體中。 為操作和分析 XML,使用現在 $xdoc 變數中具現化的文件物件模型 (但我還需要幾個特殊的用途,如本文中稍後介紹指出使用 XPathNavigator 技術):

# Create an XPath navigator (comments in PowerShell code take the \"#\" leading character)
$nav = $xdoc.CreateNavigator()

Windows PowerShell 的最有趣的功能之一是內建函數或 Cmdlet,稱為 Get 成員,使您可以檢查的方法和在 Windows PowerShell 權在 IDE 中的任何物件的屬性,隨著你的發展。 圖 3 包括對公正的 $導航物件上創建的此 Cmdlet 的調用和圖 4 顯示獲取説明調用時顯示在 Windows PowerShell 綜合編寫腳本的環境 (ISE) 的結果。

圖 3 Get 成員調用的結果

Get-Member -InputObject $nav
                      TypeName: System.Xml.DocumentXPathNavigator
Name                 MemberType Definition
----                 ---------- ----------
AppendChild          Method     System.Xml.XmlWriter AppendChild(), System.V...
AppendChildElement   Method     System.Void AppendChildElement(string prefix...
CheckValidity        Method     bool CheckValidity(System.Xml.Schema.XmlSche...
Clone                Method     System.Xml.XPath.XPathNavigator Clone()
ComparePosition      Method     System.Xml.XmlNodeOrder ComparePosition(Syst...
Compile              Method     System.Xml.XPath.XPathExpression Compile(str...
CreateAttribute      Method     System.Void CreateAttribute(string prefix, s...
CreateAttributes     Method     System.Xml.XmlWriter CreateAttributes()
CreateNavigator      Method     System.Xml.XPath.XPathNavigator CreateNaviga...
DeleteRange          Method     System.Void DeleteRange(System.Xml.XPath.XPa...
DeleteSelf           Method     System.Void DeleteSelf()
Equals               Method     bool Equals(System.Object obj)
Evaluate             Method     System.Object Evaluate(string xpath), System...
GetAttribute         Method     string GetAttribute(string localName, string...
GetHashCode          Method     int GetHashCode()
TypeName: System.Xml.DocumentXPathNavigator
.
.
.
.
.
Value                Property   System.String Value {get;}
ValueAsBoolean       Property   System.Boolean ValueAsBoolean {get;}
ValueAsDateTime      Property   System.DateTime ValueAsDateTime {get;}
ValueAsDouble        Property   System.Double ValueAsDouble {get;}
ValueAsInt           Property   System.Int32 ValueAsInt {get;}
ValueAsLong          Property   System.Int64 ValueAsLong {get;}
ValueType            Property   System.Type ValueType {get;}
XmlLang              Property   System.String XmlLang {get;}
XmlType              Property   System.Xml.Schema.XmlSchemaType XmlType {get;}

Results of Get-Help in Windows PowerShell
圖 4 結果的 Windows PowerShell 中獲取説明

雖然得到成員將經常把你正確的軌道上 Windows PowerShell 開發過程中,您還可以找到相關聯的獲取説明 Cmdlet 方便在此過程中。

如果您在命令列中,鍵入獲取説明 xml 中所示圖 4,你會在這裡顯示的輸出:

getName                 Category  Synopsis
----                 --------  --------
Export-Clixml        Cmdlet    Creates an XML-based representation of an object or...
Import-Clixml        Cmdlet    Imports a CLIXML file and creates corresponding obj...
ConvertTo-XML        Cmdlet    Creates an XML-based representation of an object.     
Select-XML           Cmdlet    Finds text in an XML string or document.             
about_format.ps1xml  HelpFile  The Format.ps1xml files in Windows PowerShell defin...
about_types.ps1xml   HelpFile  Explains how the Types.ps1xml files let you extend ...

如果您鍵入時獲取説明 about_types.ps1xml,您將看到所示的結果圖 5

圖 5 獲得與 Types.ps1xml 檔的説明

TOPIC
    about_Types.ps1xml
SHORT DESCRIPTION
    Explains how the Types.ps1xml files let you extend the Microsoft .NET Framework types of the objects that are used in Windows PowerShell.
LONG DESCRIPTION
    The Types.ps1xml file in the Windows PowerShell installation directory ($pshome) is an XML-based text file that lets you add properties and methods to the objects that are used in Windows PowerShell. Windows PowerShell has a built-in Types.ps1xml file that adds several elements to the .NET Framework types, but you can create additional Types.ps1xml files to further extend the types.
SEE ALSO
    about_Signing
    Copy-Item
    Get-Member
    Update-TypeData

Windows PowerShell 集成系統的研究語法是全面和相對簡單易用。 這是一個值得自己文章的主題。

要獲取 XML 分析就緒狀態,請使用 XpathNavigator 的選擇方法:

$nav.Select("/") | % { $ouxml = $_.OuterXml }

此語句的第一部分,在調用。選擇簡單的 XPath 查詢"/",給整個 XML 內容。 在第二部分,Windows PowerShell 符號後 |我謹此陳其物件管道做 foreach,由別名 %; Foreach,而不是別名,我可能已經過去。 迴圈裡,建立工作 XML 字串資料變數 $ouxml 從。在迴圈中正在處理的物件的 OuterXML 屬性。 回指圖 3。OuterXML 是一個 XPathNavigator 物件的屬性。 此屬性提供了一套完整的在 XML 檔中,這是為解析引擎正常工作所需的所有角括弧。

請注意對於一條管道穿過物件,$_ 的符號的特定實例,帶點標記法,用於獲取每個實例的屬性和方法。 管道中的每個物件所致,或使用 $_ 符號引用。要獲取的 $_ 物件的屬性,請使用,例如,$_。名稱 (如果名稱的特定物件的成員屬性)。 一切都通過一個 Windows PowerShell 管道是具有的屬性和方法的物件。

最後的準備階段之前解析是"合法化"的 XML 文本處理任何特殊的情況下,看起來就像 <ShortNode/>。 解析引擎而將看到另一種格式中同樣的資訊:<ShortNode> </ShortNode>。 下面的代碼會啟動此轉換使用正則運算式和尋找匹配值:

$ms = $ouxml | select-string -pattern "<([a-zA-Z0-9]*)\b[^>]*/>"   -allmatches
foreach($m in $ms.Matches){ ‘regularize’ to the longer format }

你現在可以看此應用程式的主要分析代碼:解析引擎,將填充到前面列出的四個陣列。 圖 6 顯示測試檔案,打開方括弧內的代碼。

圖 6 測試開放方括弧內的檔

# if you run out of “<” you’re done, so use the “$found_bracket” Boolean variable to test for presence of “<”
$found_bracket = $true
while($found_bracket -eq $true)
{
  # Special case of first, or root element, of the XML document;
  # here the script-level variable $ctr equals zero.
    if($Script:ctr -eq 0)
    {
    #to handle the top-level root
    $leading_brackets += $ouxml.IndexOf("<",$leading_brackets[$Script:ctr])
    $leading_value += $ouxml.Substring(0,$ind)
    $closing_brackets += $ouxml.IndexOf("</" + $leading_value[0].Substring(1))
    $Script:ctr+=1
    }
}

中的代碼圖 6 處理 XML 文檔的根項目的特殊情況。 XML 的另一個基本規則是每個架構應包含單個根整套角括弧 ; 在這些封閉符號,XML 資料可以在結構符合相匹配的規則,剛才提到,就是為每個"<ABC>"存在的任何方式"< / ABC。"

請注意使用 + = 語法來添加到一個陣列中的項或元素。 後來後被填充的元素,, 這類陣列可以通過訪問索引,如 $leading_brackets [3]。

在 IndexOf 參數中,注意的搜索,在方法中,第二個參數所代表的起始位置顯示 $腳本的引用: ctr。 在 Windows PowerShell 中的變數具有不同的作用域,請按照創建它們是從。 因為這裡的變數 $ctr 創建任何函數的範圍之外,有否考慮腳本級和一個腳本級變數不能更改從函數內部,不涉及 $腳本。 內迴圈,而不是函數內部的 $腳本引用可能不是必需的但它是一個好的習慣,都要記住範圍倍。

編碼時,很好的線索,對範圍的侵犯是一個變數,應更改的值,但不是 ; 通常,這是因為它超出了範圍,並且需要據此首碼。

一旦處理的根項目,所有其他元素的處理一個別的塊內:

else
{
# Check for more \"<\"
$check = $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1)
if($check -eq - 1)
{
break
}

第一件要做的是要檢查是否已到達檔的末尾 ; 該事件的標準是沒有進一步的 < 符號。 前面的代碼執行此操作。 如果沒有更多 < 符號,被稱為休息。

下一段代碼的區分 < 例和 < / 例:

#eliminate "</" cases of "<"
if($ouxml.IndexOf("</",$leading_brackets[$Script:ctr-1]+1) -ne `
  $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1))

因為你想積累了所有開放角括弧,您想要只知道這些在現階段的解析引擎的操作。 注意在比較中的"不平等"的 Windows PowerShell 語法:-ne。 有關營辦商包括-eq,-lt 和-gt. 此外,在 Visual Basic 中 (但不同于 C#),您需要一個換行字元,該字元是背勾選符號 ('),繼續的程式碼。

如果測試成功,填充 $leading_brackets 陣列的一個新的元素:

$leading_brackets += $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1)

領先角括弧建立的最新小版本下, 一個任務是元素的分離的相關聯的名稱。 這項任務,注意初始通車後 < 和元素的名稱,< ElementName,有一個或多個屬性,或一個空格或括弧內關閉,請在以下兩種情況:

<ElementName attribute1="X" attribute2 = "Y">, or
<ElementName>

單獨這兩例用下面的代碼,看看哪個先出現,一個空格或 > 符號:

$indx_space = $ouxml.IndexOf(" ",$leading_brackets[$Script:ctr])
  $indx_close = $ouxml.IndexOf(">",$leading_brackets[$Script:ctr])
  if($indx_space -lt $indx_close)
  {
  $indx_to_use = $indx_space
  }
  else
  {
  $indx_to_use = $indx_close
  }

一旦建立了適當的結束點,聘請 $indx_to_use,以説明隔離與現在處於焦點的領先的角括弧關聯的字串:

$leading_value += $ouxml.Substring($leading_brackets[$Script:ctr],($indx_to_use -
  $leading_brackets[$Script:ctr]))

實際上,行距值是字串開頭 < 一個空間,或者結束或 >。

階段被設置為撿起通過查找字串結束角括弧的相關 < / ElementName:

$closing_brackets += $ouxml.IndexOf("</" + $leading_value[$Script:ctr].Substring(1),`
  $leading_brackets[$Script:ctr]+1)
$Script:ctr+=1

最後,在案例之間的區別 < 和 < / 不是滿足,遞增的陣列元素,並繼續:

else
{
$leading_brackets[$Script:ctr-1] +=1
}

在此過程結束時,三個數組看上去像他們的資料的以下部分演示文稿:

$leading_brackets:
0 18 62 109 179 207 241 360 375 447 475 509 625 639 681 713 741 775 808 844 900 915 948 976 1012 1044 1077 1142 1154 1203 1292 1329 1344 1426 1475 1490 1616 1687 1701 1743 1810 1842 1890 1904 1941 1979 2031 2046 2085 2138 2153 2186 2235 2250 2315 2362 2378 2442 2476 2524 2539 2607 2643 2718
$leading_value:
<Sciences <Chemistry <Organic <branch <size <price <degree <origin <branch <size <price <degree <origin <Physics <Biology
$closing_brackets:
2718 1687 625 360 179 207 241 273 612 447 475 509 541 1142 900 713 741 775 808 844 882 1129 948 976 1012 1044 1077 1

建立節點的關係

現在它是解析引擎操作中的第二階段的時間。 在此更複雜的階段,$leading_brackets $closing_brackets 的序列建立父母子女及兄弟姐妹之間的關係的所有節點正在分析的 xml。 第一,確立的變數的數目如下:

# These variables will be used to build an automatic list of XPath queries
$xpaths = @()
$xpaths_sorted = @()
$xpath = @()
[string]$xpath2 = $null

下一步,第一次配對連續的領導和右括弧被固定:

$first_open = $leading_brackets[0]
$first_closed = $closing_brackets[0]
$second_open = $leading_brackets[1]
$second_closed = $closing_brackets[1]

並創建一些迴圈計數器:

$loop_ctr = 1
$loop_ctr3 = 0

該引擎將反復解析沒有更多時間比第一階段遞增的 $ctr 變數的值時建築的 $leading_brackets 和其他陣列 (以下如果語句是有關設立的 XML 節點結構的試金石):

if($second_closed -lt $first_closed)

如果 $second_closed 值小於 (-lt) $first_closed 的值,子關係建立:

<ElementOneName>text for this element
  <ChildElementName>this one closes up before its parent does</ChildElementName>
</ElementOneName>

檢測到的子節點,變數重置為打開關閉角括弧的下兩個相鄰對、 計數器遞增和重要 $xpath 陣列填充了一個新的元素:

$first_open = $leading_brackets[$loop_ctr]
$first_closed = $closing_brackets[$loop_ctr]
$second_open = $leading_brackets[$loop_ctr + 1]
$second_closed = $closing_brackets[$loop_ctr + 1]
$loop_ctr2 +=1
#if($loop_ctr2 -gt $depth){$loop_ctr2 -= 1}
$depth_trial+=1
$xpath += '/' + $leading_value[$loop_ctr-1]
$loop_ctr+=1

您現在已經達到解析引擎的關鍵處理階段:怎樣做時不再持有的父-子關係。

一個初步的問題是要消除重複解析引擎操作的過程中將會出現的。 要做到這一點,持有的 XPath 查詢 (這是建造的解析引擎的金鑰值) 的整個陣列的變數是審查的元素的元素,以確保它不會已經包含 $xpath,而此時是 $xpath,設立了第八屆中的程式碼中的當前值列入新的建議的候選人圖 7

檢查重複 Xpath 的圖 7

$is_dupe = $false
  foreach($xp in $xpaths)
  {
  $depth = $xpath.Length
  $xp = $xp.Replace('/////','')
  $xpath2 = $xpath
  $xpath2 = $xpath2.Replace(" ","")
  $xpath2 = $xpath2.Replace("<","")
  if($xp -eq $xpath2)
  {
  $is_dupe = $true
  #write-host 'DUPE!!!'
  break
}

如果 $xpath 的當前值不是一份複本,它將追加到 $xpath 陣列和 $xpath 重新成為一個空陣列為其下一次的使用:

if($is_dupe -eq $false){$xpaths += ($xpath2 + '/////');}
$xpath = @()
$xpath2 = $null

解析引擎用於反覆運算繼續通過 XML 的基本設備是重建陣列,每個 itera 突變。 若要實現此目的,第一步是創建新的臨時陣列物件,作為過渡的設備:

$replacement_array_values = @()
$replacement_array_opens = @()
$replacement_array_closes = @()
$finished = $false
$item_ct = 0

通過 $leading_value 陣列和篩選掉不僅僅是當前的一個發動機迴圈:

foreach($item in $leading_value)
{
if($item -eq $leading_value[$loop_ctr - 1] -and $finished -eq $false)
{
$finished = $true
$item_ct+=1
continue  #because this one should be filtered out
}

未篩選的值來填充到臨時陣列。 所有三個數組填充通過協會中,因為對應其索引的開啟和關閉的角括弧陣列中的元素名稱值的陣列:

$replacement_array_values += $item
$replacement_array_opens += $leading_brackets[$item_ct]
$replacement_array_closes += $closing_brackets[$item_ct]
$item_ct +=1

當三個臨時陣列都完成後時,三個永久陣列分配它們的新值:

$leading_value = $replacement_array_values
  $opening_brackets = $replacement_array_opens
  $closing_brackets = $replacement_array_closes
  $loop_ctr+=1

解析引擎的第一階段的下一次反覆運算是通過初始化角括弧連續的第一次對準備好:

$first_open = $leading_brackets[0]
$first_closed = $closing_brackets[0] 
$second_open = $leading_brackets[1] 
$second_closed = $closing_brackets[1] 
$loop_ctr = 1
$loop_ctr2 = 1
continue  # Sends the engine back to the top of the loop

最後,要完成的 XPath 查詢集,您生成的前面描述的過程可能不包括的短路徑。 例如,在當前示例中,無額外最後這一步,XPath \Sciences\Chemistry 不會包括在內。 要測試的每個 XPath 查詢每個短版本還存在,無重複的基本邏輯。 執行此步驟的函數是 AddMissingShortPaths,你可以看到這篇文章的代碼下載內容中 (archive.msdn.microsoft.com/mag201208PowerShell)。

與手中的自動的 XPath 查詢的所有,你準備好建立一個 Windows 表單應用程式的使用者。 在此期間,將只產生的 XPath 查詢放入檔通過 Windows PowerShell C:\PowerShell\XPATHS.txt >> 輸出的語法。

構建的 Windows 表單應用程式

因為 Windows PowerShell 承載.net 庫和類,可以編寫下面的代碼,從而使可供您應用 Windows 表單和.net 的繪圖類:

[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

與地方這些基本的構造塊,您可以生成表單和控制項,如下:

$form= New-Object Windows.Forms.Form
$form.Height = 1000
$form.Width = 1500
$drawinfo = 'System.Drawing'
$button_get_data = New-Object Windows.Forms.button
$button_get_data.Enabled = $false
$text_box = New-Object Windows.Forms.Textbox
$button_get_data.Text = "get data"
$button_get_data.add_Click({ShowDataFromXMLXPathFilter})

值得注意的,add_Click 將一個事件附加到控制項的 Windows PowerShell 語法 — — 在這種情況下,將附加到該按鈕的 click 事件的函式呼叫。 中的代碼圖 8 添加按鈕和文字方塊。

圖 8 添加按鈕和文字方塊

$pointA = New-Object System.Drawing.Point 
$listbox = New-Object Windows.Forms.Listbox 
$form.Controls.Add($listbox)
$listbox.add_SelectedIndexChanged({PopulateTextBox})
$form.Controls.Add($button_get_data)
$form.Controls.Add($text_box)
$pointA.X = 800 
$pointA.Y = 100 
$button_get_data.Location = $pointA
$button_get_data.Width = 100
$button_get_data.Height = 50
$pointA.X = 400 
$pointA.Y = 50 
$text_box.Location = $pointA
$text_box.Width = 800

來填充您收集的 XPath 查詢 $ 清單方塊,執行以下操作:

foreach($item in $xpaths)
{
$listbox.Items.Add($item.Substring(0,$item.Length - 5))
# Each row in the listbox should be separated by a blank row
$listbox.Items.Add('     ')
}

使用者介面

圖 9 由顯示在左側,其中之一由使用者選擇的工具生成的 XPath 查詢顯示使用者介面。

Selecting an XPath Query
圖 9 選擇 XPath 查詢

最後一步,在使用者按下 GetXMLData 按鈕,並產生中所示的結果圖 10

The Results Window
圖 10 結果視窗

那裡你有 — — 一個簡單的使用者介面,用於讀取和編輯 XML 檔,完全使用 Windows PowerShell 創建的。 在即將到來的 MSDN 雜誌線上文章,我將繼續在這個問題上向你展示如何處理 XML 檔,使用命名空間,以及說明如何使用此處顯示允許編輯 XML 檔通過介面的技術。

Joe Leibowitz 是一位從事基礎設施專案顧問。他可以在達到 joe.leibowitz@bridgewaresolutions.com

由於下面的技術專家,檢討這篇文章:湯瑪斯 · Petchel