iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
Software Development

Genero Packages - 可接替4GL的LowCode商業語言系列 第 15

[FGL] 服務簡單收 - IMPORT 3 利用http與XML套件取 Web資源

  • 分享至 

  • xImage
  •  

沒有人能一次做好所有的事情,也不可能有一套系統收盡所有資料。既然如此,如何適當且適時的抓取外部資料做為己用,是一件很重要的事情。

近年來除許多資料以 OPEN DATA形式提供公眾使用;編程也紛紛將程式打散,做得更細更專,推行『微服務』化。今天篇章先不論怎麼寫出服務,而先探討如何調取別人的服務。

一般外部資料提供有透過 API取用或實體檔案抓取,實體檔案可透過 wget 取回後用前述章節的channel解讀。API的部分,通常在回傳值還可區分為CSV / XML / JSON等等格式。今天範例取用日常生活可能會用到的『中油油價表』作為範例:
https://ithelp.ithome.com.tw/upload/images/20210916/20051169x4PadjhuM2.png
參考網址 https://data.gov.tw/dataset/6339

從範例中可得知,調用後會以 XML 格式回傳,因此依循Genero 方式拆解。所以為了閱讀方便,程式拆成兩段

  1. 取用Service的 FetchCPC 段落
  2. 取回資料拆解 XML 的段落(主程式)

取用Service應調用 HTTPRequest與HTTPResponse物件

Genero FGL要串接外部現成已經提供的服務並不困難,我們這次的目標是取回下列格式資料
https://ithelp.ithome.com.tw/upload/images/20210916/20051169bOoYFg1HeW.png

從最簡單的調用範例來解讀:

PUBLIC FUNCTION FetchCPCPrice()
  DEFINE ok       BOOLEAN
  DEFINE req      com.HttpRequest  #取用WebService主要物件
  DEFINE resp     com.HttpResponse #分析回傳資料使用
  DEFINE resptype STRING
  DEFINE doc      xml.DomDocument  #抓出的XML Document
  DEFINE node     xml.DomNode      #在Genero中基本轉為DomNode可用的method較多
  DEFINE str      STRING
  DEFINE ind      INTEGER
  DEFINE baseURL  STRING

  #中油牌價服務路徑
  LET baseURL = "https://vipmember.tmtd.cpc.com.tw/opendata/",
                        "ListPriceWebService.asmx/getCPCMainProdListPrice_XML"

  LET req = com.HttpRequest.Create(baseURL)      #準備連結
  CALL req.setMethod("GET")                      #使用 GET 方法 (還有POST.DELETE..等)
  CALL req.doRequest()                           #做
  LET resp = req.getResponse()                   #將回應寫入Response物件, 等待分析
  IF resp.getStatusCode()==200 THEN                 #狀態碼檢查
    LET resptype = resp.getHeader("Content-Type")   #準備確認一下形式是否正確
    IF resptype.getIndexOf("/xml",1) THEN           #檢查是否為 XML
       LET doc = resp.getXmlResponse()              #使用XML獲取資料 
       LET node = doc.getDocumentElement()          #轉換為Genero xml.Document格式
    END IF
    LET ok = TRUE
  ELSE
    LET ok = FALSE
  END IF
  RETURN ok, str, node
END FUNCTION

Genero為了不讓套件過大,將回傳分析的功能(method)放進了HTTPResponse,所以在取用 Web Service之後,再調取Response處理就好。

com.HTTPRequest

分類:使用前,在程式最前端需 IMPORT com
功能:創建連結物件,並依照提供服務端的要求,可對本次發起的服務做好行前準備。如告知使用方法、放入參數、設定封包表頭、設定TIMEOUT時間、與Cookie, proxy 等。接著就可依照不同的方式,做 Request(一般) / FileRquest / DataRequest / XmlRequest 等等。
Method List
特別注意:

  1. 由於範例中的『中油油價表』,發出時並未帶任何參數,所以適用於一般的 .doRequest
  2. 創建時要傳入URL,若帶入的方法需要附加參數,則須在此步驟前完成組裝所有的參數

com.HTTPResponse

分類:使用前,在程式最前端需 IMPORT com
功能:完成 WebService調用後,取回的狀態與成果分析使用
Method List
特別注意:

  1. 在HTTPRequest階段已完成 Web Service的連結調用並已斷開,此段落主要在進行回傳資料分析處理,不具備額外連線的作用
  2. 取回的資料,仍有對應資料格式的議題。範例中使用的是一般Response回傳。Method中的 XMLResponse係指串流式回復的XML格式(Streaming XML),必須在以 StaxReader物件進行分析處理,與範例不同

取回 xml.DomNode後使用 XML套件分析處理

先閱讀一下範例

IMPORT xml
IMPORT com
###需使用到的extension包###

MAIN
   DEFINE str,tagname,value STRING
   DEFINE ok,i,cnt          INTEGER
   DEFINE r,every,child  xml.DomNode
   DEFINE n              xml.DomNodeList

   CALL FetchCPCPrice() RETURNING ok, str, r    #前段WEB Service調用回傳
   IF ok THEN
      #  預期要處理的格式如下: 
      #<Table>
      #  <型別名稱>汽柴油零售</型別名稱>
      #  <產品名稱>92無鉛汽油</產品名稱>
      #  <參考牌價>28.2</參考牌價>
      #</Table>                       #思路: 找出Table節點, 再往下找出型別,產品,牌價等
   
      LET n = r.selectByXPath("Table",NULL)   #利用SELECT BY X(ML) PATH找符合條件的清單 
      LET cnt = n.getCount()                  #確認符合數
      FOR i=1 TO cnt
        LET every = n.getItem(i)              #取用個別的下層結構
        LET child = every.getFirstChild()     #往下一層進入
        WHILE (child IS NOT NULL)
           LET tagname = child.getNodeName()  #只處理對的子項目
           IF tagname = "型別名稱" OR tagname = "產品名稱" OR tagname = "參考牌價" THEN
              LET value = child.toString()
              LET value = value.subString(value.getIndexOf(">",1)+1,value.getIndexOf("</",1)-1)
              #無法處理中文TAG,所以將 TAGNAME 與 VALUE 剝離出來
              
              IF tagname = "型別名稱" THEN         #需求項目分析 (parser)
                 IF value <> "汽柴油零售" THEN
                    LET child = child.getNextSibling()  #此處捲回WHILE,先取下一個,避免重工
                    CONTINUE FOR
                 END IF
              END IF
              DISPLAY tagname,":" ,value.trim()   #呈現需求項目
           END IF
           LET child = child.getNextSibling()     #取用下一個符合的
        END WHILE
      END FOR
   END IF
END MAIN

這邊有一個狀況需要特別說明
**Genero在現行版本無法處理非英語文字的 TAG,要轉為 STRING **
**Genero在現行版本無法處理非英語文字的 TAG,要轉為 STRING **

這也是在上方案例中,『LET value = child.toString()』主要的用意。轉 TAG 為STRING來進行處理,
撥離 TAG 符號,判斷是否合規

xml.DomNode

分類:使用前,在程式最前端需 IMPORT xml
功能:處理任何類型的XML文檔操作
Method List
特別注意:

  1. 在 Genero系統中有兩組非常類似都可以處理 XML 結構的 DomNode,一組為 om.DomNode (內置built-in功能,不需要IMPORT可創建調用),另一組為 xml.DomNode。簡單來說 xml.DomNode對 XML的支援較為全面,後續若有功能異動也會以 xml.DomNode 為主。因此 Genero 也有在文件中提及『可以考慮轉移om.DomNode到xml.DomNode
  2. DomNode的處理只有 處理本階 Node + 打開下一階Node 的功能。所以『遞迴式的呼叫』在此段落勢不可免。需要特別注意 RETURN回到哪一個階層。
  3. 依據文件所述,這裡的資料屬性(attributes)為"@char" ,但因為呼叫不易,故許多Service大多已將值放入 TAG 的

xml.DomNodeList

分類:使用前,在程式最前端需 IMPORT xml
功能:一個可以承接 DomNode/DomDocument 查找出來節點(selectByXPath)存放的物件容器,方法很簡單:有幾個?是誰?
[Method List]
(https://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_gws_XmlDomNodeList_methods.html)

透過 XML 物件協助,就可以將取回的資料做好分析,在依據不同的需求進行處理即可。

最終程式運行結果

型別名稱:汽柴油零售
產品名稱:98無鉛汽油
參考牌價:31.6
型別名稱:汽柴油零售
產品名稱:95無鉛汽油
參考牌價:29.6
型別名稱:汽柴油零售
產品名稱:92無鉛汽油
參考牌價:28.1
型別名稱:汽柴油零售
產品名稱:酒精汽油
參考牌價:29.6

FGL對於外部 WEB 資料的處理,從 1.00版的一片荒漠,到2.00版的支援SOAP,3.00版支援XML/JSON後,終於在 3.20版逐漸的補滿補齊。後續的 Genero平台,將不再自外於網際網路,而可以取之於網路用之於網路。我們在後續的篇章中介紹『創造 FGL的微服務』


上一篇
[FGL] 吸星大法 - IMPORT之 2: 帶入JAVA或其他FGL套件
下一篇
[GDC] 基本的概念與操作
系列文
Genero Packages - 可接替4GL的LowCode商業語言32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
sjs
iT邦新手 5 級 ‧ 2022-10-26 15:11:30

感謝hjwang65, 受用良多, 範例的網址現在已更新為
http://vipmbr.cpc.com.tw/CPCSTN/ListPriceWebService.asmx/getCPCMainProdListPrice_XML
在測試過程中, 我發現URL若給定為"https", 則會在req.doRequest()報錯, status=-15553
sqlca.sqlcode = 0
sqlca.sqlerrm = "Configuration needed to perform HTTPS connection."
sqlca.sqlerrp = (null)
sqlca.sqlerrd = {0, 0, 0, 0, 0, 0}
sqlca.sqlawarn = ""
但將URL改回http就可以順利request/response
我想請教hjwang65, 這是來自client(request)解析的問題, 還是server(response)的問題?
再請不吝指導, 謝謝!

我要留言

立即登入留言