Xử lý văn bản hoặc tạo ra văn bản là công việc thường xuyên trong lập trình web. Trong bài này chúng ta sẽ tìm hiểu những kiến thức cơ bản về xử lý văn bản trước khi tìm hiểu các phần khác trong lập trình web. Có 3 nội dung được trình bày trong bài này:
- XML
- JSON
- Mẫu HTML
XML
XML (eXtend Markup Language) được định dạng theo kiểu ngôn ngữ đánh dấu (markup language) có khả năng mô tả nhiều loại dữ liệu khác nhau. Mục đích chính của XML là đơn giản hóa việc chia sẻ dữ liệu giữa các hệ thống khác nhau, đặc biệt là các hệ thống được kết nối với Internet. XML giúp phân chia thông tin theo thứ bậc thông qua các thẻ (tag) nhờ đó có thể tạo ra một hệ thống lưu trữ dữ liệu. Ví dụ để lưu trữ thông tin địa điểm, chúng ta có thể dùng XML như sau:
<?xml version="1.0" encoding="utf-8"?>
<location version="1">
<loc>
<name>ATM Techcombank</name>
<adr>Chung cư K26, Dương Quảng Hàm, P.7, Gò Vấp</adr>
<lat>10.832052230834961</lat>
<lon>106.68563842773438</lon>
<type>Tiện ích</type>
</loc>
<loc>
<name>CGV PVT</name>
<adr>Tầng 5 Vincom Plaza 12 Phan Văn Trị, P7, Gò Vấp</adr>
<lat>10.827040672302246</lat>
<lon>106.68864440917969</lon>
<type>Giải trí</type>
</loc>
</location>
Nhìn vào định dạng file XML, ta thấy có thể chuyển đổi XML thành struct trong Go. Go cung cấp các hàm thuộc package encoding/xml để đóng gói struct thành XML và ngược lại như sau:
XML->struct:
func Unmarshal(data []byte, v interface{}) error
Dữ liệu đầu vào là nội dung XML được chuyển thành slice byte dữ liệu. Dữ liệu trả về là interface{} phù hợp với bất kỳ kiểu struct nào.
struct->XML:
func Marshal(v interface{}) ([]byte, error)
Dữ liệu đầu vào là struct dữ liệu cần chuyển sang XML. Kết quả gọi hàm Marshal là slice byte chứa thông tin theo định dạng XML.
Mặc dù XML được đề xuất là định dạng sử dụng trong trao đổi trên môi trường mạng nhưng việc sử dụng các cặp thẻ đã khiến cho kích thước file XML phình to khi lưu trữ nhiều dữ liệu và việc duyệt cấu trúc XML lúc này cũng mất nhiều thời gian nên một định dạng khác đang dần thay thế XML là JSON.
JSON
JSON (JavaScript Object Notation) là định dạng file nhỏ gọn, tiện trong trao đổi dữ liệu nhưng vẫn có khả năng tự mô tả. Mặc dù xuất xứ từ JavaScript nhưng JSON có định dạng khác và trở thành ngôn ngữ độc lập. So với XML, JSON cho kích thước file nhỏ gọn hơn, khả năng duyệt nội dung nhanh hơn nên ngày càng phổ biến hơn và đang là định dạng file chính dùng trong trao đổi dữ liệu trên môi trường internet. Nội dung mô tả địa điểm như ví dụ ở phần XML được lưu dưới định dạng JSON như sau:
{
"loc":[
{
"name":"ATM Techcombank",
"adr":"Chung cư K26, Dương Quảng Hàm, P.7, Gò Vấp",
"lat":10.832052230834961,
"lon":106.68563842773438,
"type":"Tiện ích"
},
{
"name":"CGV PVT",
"adr":"Tầng 5 Vincom Plaza 12 Phan Văn Trị, P7, Gò Vấp",
"lat":10.827040672302246,
"lon":106.68864440917969,
"type":"Giải trí"
}
]
}
Phân tích dữ liệu JSON: Go cung cấp hàm thuộc package encoding/json để phân tích JSON như sau:
func Unmarshal(data []byte, v interface{}) error
Trong trường hợp đã xác định được cấu trúc JSON, ta có thể tạo struct tương ứng rồi truyền biến kiểu struct này làm tham số thứ 2 ở hàm Unmarshal để nhận dữ liệu từ slice chứa dữ liệu JSON ở tham số thứ 1. Ví dụ sau là phần phân tích dữ liệu địa điểm dưới định dạng JSON thành cấu trúc lưu thông tin địa điểm:
package main
import (
"encoding/json"
"fmt"
)
type Location struct {
Name string `json:"name"`
Address string `json:"adr"`
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
Type string `json:"type"`
}
type Locations struct {
Locs []Location `json:"loc"`
}
func main() {
var locs Locations
jsonStr := `{"loc":[{"name":"ATM Techcombank", "adr":"Chung cư K26, Dương Quảng Hàm, P.7, Gò Vấp","lat":10.832052230834961,"lon":106.68563842773438,"type":"Tiện ích"},{"name":"CGV PVT","adr":"Tầng 5 Vincom Plaza 12 Phan Văn Trị, P7, Gò Vấp","lat":10.827040672302246,"lon":106.68864440917969,"type":"Giải trí"}]}`
json.Unmarshal([]byte(jsonStr), &locs)
fmt.Println(locs)
}
Kết quả in ra màn hình là:
{[{ATM Techcombank Chung cư K26, Dương Quảng Hàm, P.7, Gò Vấp 10.832052230834961 106.68563842773438 Tiện ích}
{CGV PVT Tầng 5 Vincom Plaza 12 Phan Văn Trị, P7, Gò Vấp 10.827040672302246 106.68864440917969 Giải trí}]}
Việc mô tả struct kèm với tên tương ứng trong JSON sẽ giúp cho quá trình phân tích JSON dễ dàng và nhanh chóng. Trong trường hợp không có mô tả, hàm phân tích sẽ tìm kiếm sự tương đồng trong tên giữa JSON và struct để ánh xạ. Lưu ý là các trường struct phải khai báo với chữ cái đầu viết hoa để bên ngoài package đọc được. Nếu không, việc phân tích JSON ra struct sẽ thất bại.
Trong rất nhiều trường hợp, chúng ta không rõ hoặc khó xác định cấu trúc tương ứng với dữ liệu JSON thì việc tạo ra struct tương ứng là không thể dẫn đến việc phân tích sẽ khá khó khăn nhưng không phải không làm được. Kiểu interface rỗng interface{} có thể ứng với mọi kiểu dữ liệu nên ta có thể dùng nó để chứa dữ liệu JSON phân tích được.
var f interface{}
err := json.Unmarshal(b, &f)
Khi không có struct tương ứng, Go sẽ phân tích đối tượng JSON về map[string]interface{} và mảng đối tượng JSON về []interface{}:
m = f.(map[string]interface{})
Sau đó áp dụng tiếp cách ép kiểu interface như trên, chúng ta có thể tiếp cận đến dữ liệu mong muốn. Trong trường hợp tổng quát cần đọc hết dữ liệu chúng ta buộc phải xác định kiểu dữ liệu phù hợp mới có thể đọc đúng được. Việc này có thể dùng switch cho kiểu dữ liệu để xác định kiểu dữ liệu của từng thành phần (xem thêm phần này ở bài về interface):
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "là chuỗi", vv)
case int:
fmt.Println(k, "là số nguyên", vv)
case float64:
fmt.Println(k, "là số thực", vv)
case boolean:
fmt.Println(k, "thuộc kiểu luận lý", vv)
case []interface{}:
fmt.Println(k, "là mảng:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "thuộc loại dữ liệu không xử lý được")
}
}
Ánh xạ giữa kiểu dữ liệu trong JSON và Go được thể hiện trong bảng sau:
- JSON boolean -> Go bool
- JSON Number -> Go Int, Float64
- JSON string -> Go string
- JSON null -> Go nil
- JSON object -> Go interface{}
Như vậy là chúng ta có thể sử dụng các công cụ do Go hỗ trợ để xử lý mọi dữ liệu JSON nhưng việc ép kiểu interface hay dùng switch kiểu dữ liệu như trên là khá phức tạp. Bitly có cung cấp một package giúp chúng ta việc này là simplejson. Để sử dụng simplejson chúng ta theo các bước sau:
- Lấy package simplejson qua go get github.com/bitly/go-simplejson rồi import package này.
- Tạo đối tượng struct Json thông qua một trong hai hàm:
func NewJson(body []byte) (*Json, error)
func NewFromReader(r io.Reader) (*Json, error)
- Chúng ta có thể sử dụng phương thức
func (j *Json) Get(key string) *Json
để lấy dữ liệu ở các cấp con và cuối cùng sử dụng các phương thức Array(), Bool, Byte(), Int(), v.v... để lấy dữ liệu tương ứng.
Đóng gói dữ liệu JSON: Việc này khá đơn giản nếu chúng ta đã có struct chứa sẵn dữ liệu. Hàm
func Marshal(v interface{}) ([]byte, error)
với struct được đưa vào tham số và dữ liệu trả về là mảng byte JSON.
Mẫu HTML
Mặc dù loạt bài này nhắm đến việc xây dựng hệ thống back-end cung cấp API nhưng nhu cầu phản hồi trang web về cho trình duyệt là vẫn có. Trong tình huống đó việc kết hợp dữ liệu và thông tin trong định dạng HTML là một công việc không hề đơn giản. Thông thường nội dung trả về của cùng một đường dẫn trên server là hầu như không đổi, chỉ có dữ liệu là thay đổi và thường chiếm rất ít nội dung trang web. Do đó nếu mỗi lần phản hồi lại thực hiện việc đóng gói tất cả vào định dạng HTML thì mất nhiều thời gian và chi phí xử lý của hệ thống. Thường việc này được xử lý bằng cách tạo ra mẫu HTML chứa các thành phần không đổi và các ký hiệu đặc biệt ứng với thành phần thay đổi sẽ giúp cho việc tạo nội dung phản hồi nhanh gọn hơn rất nhiều.
Go cung cấp các package text/template và html/template giúp xử lý việc tạo mẫu này. Cả 2 package template này đều cung cấp các hàm xử lý như nhau, đều sử dụng các hàm Parse, ParseFile hay Execute để nạp mẫu từ chuỗi hoặc từ file rồi xử lý ghép với dữ liệu để tạo ra nội dung hoàn chỉnh. Sự khác biệt ở html/template so với text/template là nó xử lý mẫu tạo ra định dạng html an toàn (loại bỏ dữ liệu nguy hại cho trang web và hệ thống), còn text/template xử lý mẫu văn bản thông thường.
Để đưa dữ liệu động vào mẫu HTML ở Go, chúng ta cần tạo các ngữ cảnh thay thế tương ứng trong mẫu. Các ngữ cảnh này được mở đầu và kết thúc bởi 2 cặp ngoặc nhọn: {{<dữ liệu ngữ cảnh>}}. Khi thực thi mẫu, các dữ liệu ngữ cảnh sẽ tham chiếu đến cấu trúc dữ liệu động và khớp giá trị dữ liệu tương ứng. Các bước sử dụng như sau:
- Khai báo 1 mẫu: có thể chứa trong một biến kiểu string hoặc 1 file lưu sẵn. Nên tạo sẵn các file mẫu nhất là với các trang web có định dạng HTML phức tạp. Các file này nên lưu trữ trong một thư mục con.
- Tạo đối tượng con trỏ *Template bằng cách gọi hàm template.New:
func New(name string) *Template
- Phân tích mẫu với phương thức Parse với mẫu lưu trong biến string hoặc ParseFile với mẫu lưu trong file:
func (t *Template) Parse(text string) (*Template, error)
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
- Thực hiện xử lý dữ liệu ghép vào mẫu với phương thức Execute:
func (t *Template) Execute(wr io.Writer, data interface{}) error
Một số ngữ cảnh thường dùng:
- {{.}}: giá trị của chính dữ liệu đưa vào ở biến data phương thức Execute bên trên. Ví dụ:
// Tạo mẫu
const tmpl = `Địa điểm: {{.}}`
// Tạo đối tượng template
t, err := template.New("loc").Parse(tmpl)
if err != nil {
log.Fatal("Lỗi parse: ", err)
return
}
// Xử lý ghép văn bản
if err := t.Execute(os.Stdout, "CGV PVT"); err != nil {
log.Fatal("Lỗi Execute: ", err)
return
}
// Kết quả xuất ra màn hình: Địa điểm: CGV PVT
- {{.<tên trường/khóa>}}: giá trị trường tương ứng của struct hoặc phần tử của mảng ứng với khóa.
- {{with <đối tượng dữ liệu>}} ...{{.}}... {{end}}: Giá trị của đối tượng dữ liệu sẽ được thay thế ở {{.}}. Nếu bạn đã từng sử dụng With trong Visual Basic bạn sẽ thấy ở đây giống như vậy.
- {{range <đối tượng dữ liệu có nhiều phần tử>}} ...{{.}} {{end}}: Giá trị từng phần tử sẽ được đưa tương ứng vào {{.}}.
- {{with $x := <đối tượng dữ liệu>}} ... {{$x}} ...{{end}}: Biến x được khai báo có thể nhận giá trị trực tiếp hoặc giá trị của đối tượng dữ liệu động được khai báo và có thể dùng sau đó trong đoạn được giới hạn bởi cặp with và end.
- {{if <giá trị bool: true/false>}}...{{else}}...{{end}}: Nếu giá trị ở ngữ cảnh if đúng, phần nội dung ... sau đó sẽ được xử lý đưa vào. Ngược lại phần nội dung sau else sẽ được xử lý.
- {{... | ...}}: Dấu | đại diện cho khái niệm pineline trong Linux. Đầu ra của phần trước sẽ là đầu vào cho phần sau dấu | .
package main
import (
"html/template"
"log"
"os"
)
type LocType struct {
Type string
}
type Location struct {
Name string
Address string
Latitude float64
Longitude float64
Type []*LocType
}
const loctmpl = `
Địa điểm: {{.Name}}
Địa chỉ: {{.Address}}
Tọa độ: {{.Latitude}};{{.Longitude}}
Loại địa điểm:{{with .Type}}{{range .}} {{.Type}}{{end}}{{end}}
Bán cầu: {{with $x := .Latitude}}{{if gt $x 0.0 }} Bắc bán cầu {{ else }} Nam bán cầu {{end}}{{end}}
{{. | printf "%v"}}`
func main() {
t, err := template.New("location").Parse(loctmpl)
if err != nil {
log.Fatal("Lỗi Parse: ", err)
return
}
t0 := LocType{Type: "Ăn uống"}
t1 := LocType{Type: "Tiện ích"}
t2 := LocType{Type: "Giải trí"}
l := Location{Name: "Vincom PVT", Address: "12 Phan Văn Trị, P.7, Gò Vấp, TP.HCM, Việt Nam", Latitude: 10.827040672302246, Longitude: 106.68864440917969, Type: []*LocType{&t0, &t1, &t2}}
if err := t.Execute(os.Stdout, l); err != nil {
log.Fatal("Lỗi Execute: ", err)
return
}
}
// Kết quả:
Địa điểm: Vincom PVT
Địa chỉ: 12 Phan Văn Trị, P.7, Gò Vấp, TP.HCM, Việt Nam
Tọa độ: 10.827040672302246;106.68864440917969
Loại địa điểm: Ăn uống Tiện ích Giải trí
Bán cầu: Bắc bán cầu
{Vincom PVT 12 Phan Văn Trị, P.7, Gò Vấp, TP.HCM, Việt Nam 10.827040672302246 106.68864440917969 [0xc04200a5a0 0xc04200a5b0 0xc04200a5c0]}
- {{define "T"}} ...{{.}} {{end}}: Giá trị ứng với T sẽ được thay thế ở {{.}}. Để sử dụng mẫu này, chúng ta cần dùng phương thức ExecuteTemplate thay vì Execute:
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
t, _ := template.New("test").Parse(`{{define "T"}}Chào, {{.}}!{{end}}`)Các bạn thử viết lại phần xử lý POST trong bài trước ở ví dụ về địa điểm mới để làm sao kết quả trả về là một bảng gồm các cột: STT, Tên, Địa chỉ, Tọa độ, Loại địa điểm.
t.ExecuteTemplate(os.Stdout, "T", "thế giới Golang")
// Kết quả: Chào thế giới Golang!
Trong bài tới chúng ta sẽ tìm hiểu về cách xử lý các loại cơ sở dữ liệu.
Tóm tắt:
- XML là một định dạng dùng trong trao đổi dữ liệu trên mạng, xây dựng cấu trúc thông tin qua các thẻ (tag). XML dễ dàng chuyển đổi qua kiểu struct trong Go với 2 hàm Marshall và Unmarshall.
- JSON sử dụng cấu trúc khóa:giá trị để tạo cấu trúc dữ liệu. So với XML, JSON cho cấu trúc nhỏ gọn hơn, khả năng truy xuất cũng nhanh hơn. Chuyển đổi giữa JSON và struct cũng dùng hàm Marshall và Unmarshall thuộc package encoding/json.
- Trường hợp không thể chuyển về struct do khó xác định cấu trúc, có thể truy xuất dữ liệu bằng cách phân giải JSON về map[string]interface{} hoặc []interface{}. Đơn giản hơn có thể dùng simplejson.
- Sử dụng text/template và html/template để có thể nhanh chóng tạo ra các nội dung trang web phản hồi về cho client.
No comments:
Post a Comment