Thao tác với File cấu hình trong Qt - INI, JSON và XML

Ghi chú: Nội dung chương này mang tính tham khảo, nếu cần nghiên cứu sâu hơn vui lòng tìm kiếm tài liệu bổ sung.

01 Thao tác với file INI

Giới thiệu về file INI:

File INI (Initialization File) là định dạng file cấu hình được sử dụng rộng rãi trong hệ thống Windows để lưu trữ các thông số cấu hình.

Phần mở rộng: tên_file.ini. Ví dụ: database_config.ini - file cấu hình chung cho cả dự án như thông tin kết nối database.

(1) Cấu trúc file INI bao gồm 3 thành phần chính:

Phần [section]

Tham số (key=value)

name=value;

Lưu ý: Ký tự dấu chấm phẩy (;) biểu thị comment, tất cả nội dung phía sau cho đến hết dòng sẽ được coi là chú thích.

Ví dụ:

[sectionname]

keyname=value

(2) Đọc và ghi file INI trong Qt

Qt sử dụng lớp QSettings để thao tác với file INI.

Các hàm API thường dùng:

QSettings::beginGroup: Thiết lập group index để truy xuất giá trị tương ứng.

QSettings::endGroup: Đặt lại trạng thái group về trạng thái trước đó.

QSettings::setValue: Ghi dữ liệu vào file INI dưới dạng cặp key-value, nếu key đã tồn tại sẽ ghi đè.

QSettings::value: Đọc giá trị từ file INI theo key, trả về giá trị mặc định nếu key không tồn tại.

QSettings::group: Trả về tên group hiện tại.

QSettings::beginWriteArray: Thêm prefix vào group hiện tại và bắt đầu ghi mảng, kích thước được xác định tự động theo số phần tử.

QSettings::beginReadArray: Thêm prefix vào group hiện tại và đọc kích thước mảng.

QSettings::endArray: Kết thúc thao tác với mảng (dùng cho cả đọc và ghi).

QSettings::NativeFormat: Sử dụng định dạng phù hợp nhất với hệ điều hành hiện tại - Windows sử dụng Registry, Unix sử dụng file text dạng INI.

QSettings::IniFormat: Lưu trữ trong file INI.

QSettings::InvalidFormat: Trả về giá trị không hợp lệ.

Trên hệ thống Unix, NativeFormat/IniFormat khác nhau ở phần mở rộng file: .conf/.ini.

Ví dụ thực hành: Đọc ghi file INI

(1) Tạo project Qt Console Application

(2) Thêm header file

(3) Thêm source file

(4) Chỉnh sửa file "configmanager.h"

#ifndef CONFIGMANAGER_H
#define CONFIGMANAGER_H

void WriteIniFile();    //Ghi file cấu hình
void ReadIniFile();     //Đọc file cấu hình

void ReadAllIniKeys();             //Đọc toàn bộ keys一次性读取文件


#endif // CONFIGMANAGER_H

(5) Chỉnh sửa file "configmanager.cpp"

#include "configmanager.h"
#include <QSettings>
#include <QDebug>

void WriteIniFile()    //Ghi file cấu hình
{
    //Sử dụng lớp QSettings để ghi file INI
    QSettings *configIni = new QSettings("DatabaseConfig.ini", QSettings::IniFormat);

    //Ghi dữ liệu vào file INI
    configIni->setValue("/database/ip", "192.168.12.189");
    configIni->setValue("/database/port", "3308");
    configIni->setValue("/database/user", "root");
    configIni->setValue("/database/password", "123456");

    configIni->setValue("/app/version", "6.4");
    configIni->setValue("/app/timestamp", "2024.07.11 22:06");

    //Giải phóng bộ nhớ sau khi ghi xong
    delete configIni;
}

void ReadIniFile()     //Đọc file cấu hình
{
    //Sử dụng lớp QSettings để đọc file INI
    QSettings *configIni = new QSettings("DatabaseConfig.ini", QSettings::IniFormat);
    
    QString dbIp = configIni->value("database/ip").toString();
    QString dbPort = configIni->value("database/port").toString();
    QString dbUser = configIni->value("database/user").toString();
    QString dbPass = configIni->value("database/password").toString();

    QString appVersion = configIni->value("/app/version").toString();
    QString appTime = configIni->value("/app/timestamp").toString();
    
    //Xuất thông tin cấu hình ra console
    qDebug() << "Thong so cau hinh INI:";
    qDebug() << "Database IP:" << dbIp.toUtf8().data();
    qDebug() << "Database Port:" << dbPort.toUtf8().data();
    qDebug() << "Database User:" << dbUser.toUtf8().data();
    qDebug() << "Database Pass:" << dbPass.toUtf8().data();
    qDebug() << "App Version:" << appVersion.toUtf8().data();
    qDebug() << "App Time:" << appTime.toUtf8().data();
}


void ReadAllIniKeys()             //Đọc toàn bộ keys一次性读取文件
{
    QSettings settings("DatabaseConfig.ini", QSettings::IniFormat);
    QStringList allKeys = settings.allKeys();
    for(const QString &key : allKeys)
    {
        qDebug() << key.toUtf8().data() << "," 
                 << settings.value(key).toString().toUtf8().data();
    }
}

(6) Chỉnh sửa file "main.cpp"

#include <QCoreApplication>
#include "configmanager.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
//    WriteIniFile();
//    ReadIniFile();            
    ReadAllIniKeys();         
    return a.exec();
}

02 Thao tác với file JSON

001 Giới thiệu về JSON

JSON (JavaScript Object Notation) là định dạng trao đổi dữ liệu nhẹ (lightweight data interchange format). Dễ đọc và viết, có thể trao đổi dữ liệu giữa nhiều ngôn ngữ lập trình khác nhau. Đồng thời dễ dàng được machine parse và generate, nâng cao hiệu suất truyền tải qua mạng. JSON sử dụng định dạng văn bản độc lập với ngôn ngữ lập trình để lưu trữ và biểu diễn dữ liệu.

Định dạng file JSON có phần mở rộng là .json.

Ứng dụng của JSON: File cấu hình, serialization, định nghĩa interface, v.v.

JSON dùng để lưu trữ và trao đổi thông tin văn bản, tương tự XML nhưng nhỏ hơn, nhanh hơn và dễ parse hơn.

002 Quy tắc cú pháp JSON

JSON là một chuỗi các ký tự đánh dấu (marker). Bộ ký tự này gồm 6 ký tự cấu trúc (structural characters), chuỗi (strings), số (numbers) và 3 literal names.

JSON là chuỗi biểu diễn một object hoặc array đã được serialize.

(1) Sáu ký tự cấu trúc:

**Ký tự cấu trúc** **Ký hiệu** Ý nghĩa
**begin-array=ws %x5B ws;** **[** Dấu ngoặc vuông trái
**begin-object=ws %x7B ws;** **{** Dấu ngoặc nhọn trái
**end-array=ws %x5D ws;** **]** Dấu ngoặc vuông phải
**end-object=ws %x7D ws;** **}** Dấu ngoặc nhọn phải
**name-separator=ws %x3A ws;** **:** Dấu hai chấm
**value-separator=ws %x2C ws;** **,** Dấu phẩy

(2) Cho phép tồn tại whitespace (ws) trước hoặc sau 6 ký tự cấu trúc:

**Ký hiệu** **Ý nghĩa**
**ws = \*(%x20 /** Khoảng trắng
**%x09 /** Tab ngang
**%x0A /** Xuống dòng
**%x0D)** Carriage return

(3) Giá trị trong JSON:

  1. Cấu tạo JSON: ws value ws.

  2. Giá trị có thể là object, array, number, string hoặc một trong 3 literal (false, null, true). Các literal phải viết bằng chữ thường.

Object được bao bọc bởi dấu ngoặc nhọn, các thành phần được phân cách bằng dấu phẩy, mỗi thành phần là cặp key-value:

{"name": "John Doe", "age": 18, "address": 
{"country" : "china", "zip-code": "10000"}}

Object trong JSON có thể chứa nhiều cặp key-value và cả cấu trúc array.

Array là tập hợp các giá trị được bao bọc bởi dấu ngoặc vuông:

[3, 1, 4, 1, 5, 9, 2, 6]

String tương tự như trong C hoặc Java. String được bao bọc bởi dấu ngoặc kép, có thể chứa các ký tự Unicode, sử dụng dấu gạch chéo ngược để escape.

Number cũng tương tự như trong C hoặc Java. Không hỗ trợ octal và hexadecimal.

Một số ví dụ JSON hợp lệ:

{"a": 1, "b": [1, 2, 3]}
[1, 2, "3", {"a": 4}]
3.14
"plain_text"

003 Quan hệ giữa JSON và object JS

JSON là biểu diễn dạng chuỗi của object JS, sử dụng văn bản để thể hiện thông tin object JS, về bản chất là một chuỗi.

var obj = {a: 'Hello', b: 'World'}; //Đây là object, tên key có thể có hoặc không có ngoặc kép
var json = '{"a": "Hello", "b": "World"}'; //Đây là chuỗi JSON, về bản chất là một chuỗi

004 Chuyển đổi giữa JSON và object JS

Để chuyển từ chuỗi JSON sang object JS, sử dụng phương thức JSON.parse():

var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //Kết quả: {a: 'Hello', b: 'World'}

Để chuyển từ object JS sang chuỗi JSON, sử dụng phương thức JSON.stringify():

var json = JSON.stringify({a: 'Hello', b: 'World'}); //Kết quả: '{"a": "Hello", "b": "World"}'

005 Các kiểu dữ liệu thường dùng

Bất kỳ kiểu dữ liệu nào cũng có thể được biểu diễn bằng JSON, ví dụ: string, number, object, array, v.v. Tuy nhiên object và array là hai kiểu đặc biệt và thường được sử dụng nhất.

Object: Trong JS, object là nội dung được bao bọc bởi dấu ngoặc nhọn {}, cấu trúc dạng {key1: value1, key2: value2, ...}. Trong ngôn ngữ hướng đối tượng, key là thuộc tính của object, value là giá trị tương ứng. Tên key có thể là số nguyên hoặc chuỗi. Giá trị có thể thuộc bất kỳ kiểu nào.

Array: Trong JS, array là nội dung được bao bọc bởi dấu ngoặc vuông [], cấu trúc dạng ["java", "javascript", "vb", ...]. Trong JS, array là kiểu dữ liệu đặc biệt, có thể sử dụng như object với cặp key-value, nhưng thường sử dụng index. Giá trị có thể thuộc bất kỳ kiểu nào.

006 Phân tích ví dụ cơ bản

JSON có thể chuyển đổi dữ liệu trong object JavaScript thành chuỗi, sau đó có thể dễ dàng truyền chuỗi này qua mạng hoặc giữa các chương trình, và khi cần có thể chuyển đổi ngược lại thành các định dạng dữ liệu mà ngôn ngữ lập trình hỗ trợ. Ví dụ trong PHP, có thể chuyển JSON thành array hoặc object. Khi sử dụng AJAX với array, cần chuyển array thành chuỗi JSON.

Biểu diễn Object

Object là tập hợp các cặp "name/value" không có thứ tự. Bắt đầu bằng dấu { và kết thúc bằng dấu }. Sau mỗi "name" là dấu : , các cặp "name/value" phân cách bằng dấu , .

{"firstName": "Brett", "lastName": "McLaughlin"} 

Biểu diễn Array

Array trong JSON cũng sử dụng dấu ngoặc vuông [] tương tự như array trong JS.

{ 
"people":[ 
{
"firstName": "Brett",            
"lastName":"McLaughlin"        
},      
{        
"firstName":"Jason",
"lastName":"Hunter"
}
]
}

Trong ví dụ này, có một biến tên là people, giá trị là một array chứa hai phần tử, mỗi phần tử là thông tin một người với họ và tên. Có thể sử dụng cú pháp tương tự để biểu diễn nhiều giá trị hơn.

Khi xử lý dữ liệu JSON, không có ràng buộc định trước. Vì vậy, trong cùng một cấu trúc dữ liệu, có thể thay đổi cách biểu diễn hoặc sử dụng cách khác nhau để biểu diễn cùng một nội dung.

Như đã đề cập, ngoài object và array, có thể đơn giản sử dụng string hoặc number để lưu trữ dữ liệu đơn giản, nhưng điều này không có nhiều ý nghĩa.

Ví dụ thực hành:

(1) Tạo file "Dialog" (có ui file)

(2) Chỉnh sửa UI file

(3) Chỉnh sửa header file

#ifndef JSONHANDLER_H
#define JSONHANDLER_H

#include <QDialog>
#include <QMessageBox>

QT_BEGIN_NAMESPACE
namespace Ui { class JsonHandler; }
QT_END_NAMESPACE

class JsonHandler : public QDialog
{
    Q_OBJECT

public:
    JsonHandler(QWidget *parent = nullptr);
    ~JsonHandler();

private slots:
    void on_saveButton_clicked();

    void on_loadButton_clicked();

private:
    Ui::JsonHandler *ui;
};
#endif // JSONHANDLER_H

(4) Chỉnh sửa source file

#include "jsonhandler.h"
#include "ui_jsonhandler.h"

#include <QMessageBox>
#include <QDebug>
#include <QFile>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>

JsonHandler::JsonHandler(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::JsonHandler)
{
    ui->setupUi(this);
}

JsonHandler::~JsonHandler()
{
    delete ui;
}

// Ghi dữ liệu vào file JSON
void JsonHandler::on_saveButton_clicked()
{
    // 1: Tạo đối tượng JSON
    QJsonObject dbConfig;

    dbConfig.insert("ip", "192.168.0.125");
    dbConfig.insert("port", 3308);
    dbConfig.insert("user", "root");
    dbConfig.insert("password", "123456");

    QJsonObject responseData;
    responseData.insert("code", 1);
    responseData.insert("message", "Thong so cau hinh Database");
    responseData.insert("data", dbConfig);

    // 2: Tạo JSON Document
    QJsonDocument jsonDoc;
    jsonDoc.setObject(responseData);

    // 3: Ghi vào file
    QFile outputFile("./db_config.json");
    if(outputFile.open(QIODevice::WriteOnly))
    {
        outputFile.write(jsonDoc.toJson());
        outputFile.close();
        qDebug() << "Ghi file JSON thanh cong!";
    }
    QMessageBox::information(this, "Thanh cong", "Du lieu JSON da duoc ghi!");
}

void JsonHandler::on_loadButton_clicked()
{
    QString jsonString;
    QString resultMessage;

    QFile inputFile("./db_config.json");
    if(inputFile.open(QIODevice::ReadOnly))
    {
        jsonString = inputFile.readAll();
        inputFile.close();
    }

    QJsonParseError parseError;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8(), &parseError);

    if(!jsonDoc.isEmpty() && (parseError.error == QJsonParseError::NoError))
    {
        // Chuyển đổi thành JSON object
        QJsonObject jsonObj = jsonDoc.object();
        QJsonValue code = jsonObj.value("code");
        QJsonValue data = jsonObj.value("data");

        // Kiểm tra dữ liệu
        if(code.isUndefined() || code.toDouble() != 1 || data.isUndefined() || !data.isObject())
        {
            qDebug() << "Loi chuyen doi JSON, vui long kiem tra lai?";
            QMessageBox::critical(this, "Loi", "Loi chuyen doi JSON, vui long kiem tra lai?");
            exit(100);
        }

        // Đọc dữ liệu từ data
        QJsonObject dbInfo = data.toObject();

        QJsonValue dbIp = dbInfo.value("ip");
        QJsonValue dbPort = dbInfo.value("port");
        QJsonValue dbUser = dbInfo.value("user");
        QJsonValue dbPassword = dbInfo.value("password");

        // Kiểm tra key
        if(dbIp.isUndefined() ||
           dbPort.isUndefined() ||
           dbUser.isUndefined() ||
           dbPassword.isUndefined())
        {
            qDebug() << "Loi cu phap, vui long kiem tra lai?";
            QMessageBox::critical(this, "Loi", "Loi cu phap, vui long kiem tra lai?");
            exit(100);
        }

        QString ipAddress = dbIp.toString();
        int portNumber = dbPort.toInt();
        QString username = dbUser.toString();
        QString password = dbPassword.toString();

        // Kiểm tra dữ liệu rỗng
        if(ipAddress.isEmpty() || username.isEmpty() || password.isEmpty())
        {
            qDebug() << "Du lieu khong duoc de trong, vui long kiem tra lai?";
            QMessageBox::critical(this, "Loi", "Du lieu khong duoc de trong, vui long kiem tra lai?");
            exit(100);
        }

        qDebug() << "Database IP:" << ipAddress;
        qDebug() << "Database Port:" << portNumber;
        qDebug() << "Database User:" << username;
        qDebug() << "Database Password:" << password;

        // Ghép chuỗi hiển thị
        resultMessage += "【Thong so JSON】";
        resultMessage += "\nDatabase IP:" + ipAddress;
        resultMessage += "\nDatabase Port:" + QString::number(portNumber, 10);
        resultMessage += "\nDatabase User:" + username;
        resultMessage += "\nDatabase Password:" + password;
    }

    QMessageBox::information(this, "Thanh cong", resultMessage, QMessageBox::Yes);
}

(5) Kết quả minh họa

03 Thao tác với file XML

Cơ bản về XML: XML (Extensible Markup Language) là tập con của SGML (Standard Generalized Markup Language), được dùng để đánh dấu dữ liệu và định nghĩa kiểu dữ liệu. XML cho phép người dùng tự định nghĩa ngôn ngữ đánh dấu riêng. XML có những ưu điểm như: khả năng mở rộng tốt, tách rời nội dung và hình thức, tuân thủ ngữ pháp chặt chẽ, khả năng bảo quản tốt.

XML có thể tách dữ liệu khỏi HTML và lưu trữ trong file XML riêng biệt, nhà phát triển có thể tập trung vào bố cục file HTML và đảm bảo khi dữ liệu thay đổi sẽ không ảnh hưởng đến file HTML.

XML có thể dùng để trao đổi dữ liệu, chia sẻ dữ liệu, tận dụng tối đa dữ liệu. XML không phụ thuộc vào phần mềm hay phần cứng, dữ liệu có thể được sử dụng bởi nhiều người dùng hoặc thiết bị, không chỉ giới hạn ở định dạng trình duyệt HTML.

XML có thể dùng để tạo ngôn ngữ mới, chủ yếu cho các ứng dụng Web.

Trong tài liệu XML, chữ hoa chữ thường có phân biệt, tạo ra các thẻ khác nhau. Khi viết các phần tử, nên đảm bảo tính nhất quán giữa chữ hoa chữ thường. Tài liệu XML chỉ có duy nhất một phần tử gốc (root element), giá trị thuộc tính phải sử dụng dấu ngoặc kép, tất cả các thẻ phải có thẻ đóng tương ứng, tất cả các thẻ rỗng (thẻ không có nội dung) cũng phải được đóng.

Thẻ: Qt QSettings INI JSON QJsonDocument

Đăng vào ngày 30 tháng 6 lúc 23:44