4.11 آموزش کار با csv

4.11 آموزش کار با csv

4.11.1 مقدمه #

CSV یکی از فرمت‌های متداول برای ذخیره داده‌های جدولی است. خروجی اکثر برنامه‌های قابلیت دستکاری داده، همراه با نرم‌افزار‌های آفیس، به فرمت CSV تولید می‌شود. فرمت CSV چندین ستون در یک ردیف را با استفاده از کاما (,) جدا کرده و هر ردیف را با استفاده از عبارت جدید (newline) جدا می‌کند.

در زبان برنامه‌نویسی Go نیز پکیج encoding/csv وجود دارد که در آن، توابع مربوط به خواندن و نوشتن داده‌های CSV به صورت دستی یا از طریق پرونده‌ها فراهم شده است. با استفاده از این پکیج، می‌توان داده‌های CSV را به داده‌های جدولی تبدیل کرد و برعکس.

به عنوان مثال، در ادامه یک فایل CSV به نام “data.csv” حاوی اطلاعات چند شخص را در نظر بگیرید:

Name,Age,City
John,25,New York
Jane,30,San Francisco
Bob,40,Los Angeles

4.11.2 نحوه خواندن فایل csv #

برای خواندن فایل csv می‌توان با استفاده از پکیج encoding/csv پرونده CSV را باز کرد:

package main

import (
    "encoding/csv"
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("data.csv")
    if err != nil {
        panic(err)
    }

    r := csv.NewReader(f)
    records, err := r.ReadAll()
    if err != nil {
        panic(err)
    }

    for _, row := range records {
        for _, col := range row {
            fmt.Print(col, "\t")
        }
        fmt.Println()
    }
}

در این کد، تابع os.Open برای باز کردن پرونده CSV استفاده می‌شود. یک رابط csv.Reader ایجاد شده و یک رشته ساختارمند، پرونده CSV را می‌خواند. سپس با استفاده از یک حلقه، داده‌های جدولی چاپ می‌شود.

فراداده‌های CSV بسیار گسترده هستند و می‌توانند شامل شماره دسته، توضیحات، یادداشت‌های شخصی و غیره باشند. برای کار با این نوع داده‌ها، پکیج encoding/csv امکاناتی مانند تنظیمات csv.Reader را فراهم می‌کند، که در آن، می‌توانیم تنظیماتی مانند علامت‌گذاری مناسب فایل CSV و دیگر علامت‌گذاری‌ها را بهبود ببخشیم.

4.11.3 ReadAll فایل csv #

تابع ReadAll تمام رکوردهای باقی مانده را از reader می خواند. هر رکورد یک قسمتی از fieldها است.

first_name,last_name,occupation
John,Doe,gardener
Lucy,Smith,teacher
Brian,Bethamy,programmer

نام این فایل users.csv است. خط اول نام ستون ها هستند.

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "os"
)

type User struct {
    firstName  string
    lastName   string
    occupation string
}

func main() {

    records, err := readData("users.csv")

    if err != nil {
        log.Fatal(err)
    }

    for _, record := range records {

        user := User{
            firstName:  record[0],
            lastName:   record[1],
            occupation: record[2],
        }

        fmt.Printf("%s %s is a %s\n", user.firstName, user.lastName,
            user.occupation)
    }
}

func readData(fileName string) ([][]string, error) {

    f, err := os.Open(fileName)

    if err != nil {
        return [][]string{}, err
    }

    defer f.Close()

    r := csv.NewReader(f)

    // skip first line
    if _, err := r.Read(); err != nil {
        return [][]string{}, err
    }

    records, err := r.ReadAll()

    if err != nil {
        return [][]string{}, err
    }

    return records, nil
}

اسم فایل بالا read_all.go می‌باشد و این مثال فایل users.csv را می خواند. هر line به یک User type را بر می‌گرداند.

// skip first line
if _, err := r.Read(); err != nil {
    return [][]string{}, err
}

در اینجا از خط اول که شامل نام ستون هاست می گذریم.

records, err := r.ReadAll()

در نهایت همه رکوردها را یک جا با ReadAll دریافت می کنیم.

$ go run read_all.go
John Doe is a gardener
Lucy Smith is a teacher
Brian Bethamy is a programmer

4.11.4 delimiter CSV دلخواه #

علیرغم نام CSV ، CSV ممکن است دارای جداکننده های دیگری غیر از کاما باشد. این به دلیل استاندارد نبودن قالب CSV است.

# user.csv
# this is users.csv file

John;Doe;gardener
Lucy;Smith;teacher
Brian;Bethamy;programmer

در فایل users.csv فیلدها با نقطه ویرگول از هم جدا شده اند. این فایل حاوی یک comment نیز می‌باشد.

//different_delimiter.go//

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "os"
)

func main() {

    f, err := os.Open("users.csv")

    if err != nil {

        log.Fatal(err)
    }

    r := csv.NewReader(f)
    r.Comma = ';'
    r.Comment = '#'

    records, err := r.ReadAll()

    if err != nil {
        log.Fatal(err)
    }

    fmt.Print(records)
}

این مثال تمام داده های این فایل را می خواند.

r := csv.NewReader(f)
r.Comma = ';'
r.Comment = '#'

در اینجا separator و کاراکتر comment را تنظیم می کنیم تا package بداند چگونه فایل را parse یا تجریه تحلیل کند.

4.11.5 نوشتن CSV #

تابع Write یک رکورد CSV را برای writer می نویسد. رکورد برشی از strings است که هر string یک فیلد است. write ها buffer شده هستند، بنابراین باید Flush فراخوانی شود تا اطمینان حاصل شود که رکورد برای writer اصلی نوشته شده است.

//write_fun.go//
package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {

    records := [][]string{
        {"first_name", "last_name", "occupation"},
        {"John", "Doe", "gardener"},
        {"Lucy", "Smith", "teacher"},
        {"Brian", "Bethamy", "programmer"},
    }

    f, err := os.Create("users.csv")
    defer f.Close()

    if err != nil {

        log.Fatalln("failed to open file", err)
    }

    w := csv.NewWriter(f)
    defer w.Flush()

    for _, record := range records {
        if err := w.Write(record); err != nil {
            log.Fatalln("error writing record to file", err)
        }
    }
}

در مثال بالا، چند رکورد را با تابع Write در فایل users.csv نوشتیم.

4.11.6 نوشتن WriteAll CSV #

تابع WriteAll چندین رکورد CSV را با استفاده از Write برای writer می‌نویسد و سپس Flush را فراخوانی می‌کند.

//write_all.go//

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {

    records := [][]string{
        {"first_name", "last_name", "occupation"},
        {"John", "Doe", "gardener"},
        {"Lucy", "Smith", "teacher"},
        {"Brian", "Bethamy", "programmer"},
    }

    f, err := os.Create("users.csv")
    defer f.Close()

    if err != nil {

        log.Fatalln("failed to open file", err)
    }

    w := csv.NewWriter(f)
    err = w.WriteAll(records) // calls Flush internally

    if err != nil {
        log.Fatal(err)
    }
}

در نهایت ما چند رکورد را در یک لحظه با WriteAll می نویسیم.