Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

godot_excel_reader

An asset by Johnnash2017
The page banner background of a mountain and forest
godot_excel_reader hero image

Quick Information

0 ratings
godot_excel_reader icon image
Johnnash2017
godot_excel_reader

解析 xlsx 文件并返回一个字典Analyze xlsx files and return a dictionary

Supported Engine Version
4.4
Version String
1.0.1
License Version
MIT
Support Level
community
Modified Date
1 month ago
Git URL
Issue URL

README 297964da-b6c8-44ba-a4c7-224865842589

godot_excel_reader 解析.xlsx文件并返回一个字典 | Analyze. xlsx files and return a dictionary

表格 | Table

Sheet1 README image

Sheet2 README image

测试结果 | Test results

Sheet: ["Sheet1", "Sheet2"]
"Sheet1"
{
    "1": {
        "1": "one",
        "2": "嘿嘿嘿"
    },
    "2": {
        "1": "B",
        "2": "#VALUE!"
    },
    "3": {
        "1": 3,
        "2": "3④"
    },
    "4": {
        "1": "④",
        "2": 777
    }
}
"Sheet2"
{
    "1": {
        "1": 1,
        "2": "A"
    },
    "2": {
        "1": 2,
        "2": "B"
    },
    "3": {
        "1": 3,
        "2": "C"
    },
    "4": {
        "1": 4,
        "2": "D"
    }
}

Test code

extends Node
const EXCEL_PATH = "res://table.xlsx"

func _ready():
    var excel = ExcelReader.ExcelFile.open(EXCEL_PATH)
    var workbook = excel.get_workbook()
    print("Sheet: ", workbook.get_sheet_names())
    
    var sheet_data = workbook.get_sheet_by_name("Sheet1")
    print(JSON.stringify(sheet_data["name"], "\t"))
    print(JSON.stringify(sheet_data["data"], "\t"))
    var sheet_data2 = workbook.get_sheet_by_name("Sheet2")
    
    print(JSON.stringify(sheet_data2["name"], "\t"))
    print(JSON.stringify(sheet_data2["data"], "\t"))

Core code

# Excel 文件读取器主类 | Main Excel Reader Class
class_name ExcelReader extends RefCounted

# Excel 文件操作类 | Excel File Handler Class
class ExcelFile:
    extends RefCounted  # 引用计数基类 | Reference counted base class
    
    var _zip_reader: ZIPReader        # ZIP 读取器实例 | ZIP reader instance
    var _workbook: ExcelWorkbook = null  # 工作簿缓存 | Workbook cache
    var _path:String
    
    # 构造函数 - 打开Excel文件(本质是ZIP包) | Constructor - Open Excel file (which is a ZIP package)
    func _init(path: String):
        _path=path
        _zip_reader = ZIPReader.new()
        if _zip_reader.open(_path) != OK:
            push_error("Excel文件打开失败: " + _path)  # 错误处理 | Error handling
        _zip_reader.close()
        
    # 获取工作簿(延迟初始化)| Get workbook (lazy initialization)
    func get_workbook() -> ExcelWorkbook:
        _zip_reader.open(_path)
        if not _workbook:
            _workbook = ExcelWorkbook.new(_zip_reader)
        _zip_reader.close()
        return _workbook
    
    # 静态方法打开Excel文件 | Static method to open Excel file
    static func open(path: String) -> ExcelFile:
        return ExcelFile.new(path) if FileAccess.file_exists(path) else null


# 工作簿解析类 | Workbook Parser Class
class ExcelWorkbook:
    extends RefCounted
    
    var _zip: ZIPReader                     # ZIP 读取器引用 | ZIP reader reference
    var _sheets: Array[ExcelSheet] = []     # 工作表列表 | List of worksheets
    var _shared_strings: PackedStringArray = []  # 共享字符串池 | Shared strings pool
    
    # 初始化并加载数据 | Initialize and load data
    func _init(zip: ZIPReader):
        _zip = zip
        _load_shared_strings()    # 加载共享字符串 | Load shared strings
        _discover_sheets()        # 发现所有工作表 | Discover all worksheets
    
    # 获取所有工作表名称 | Get all sheet names
    func get_sheet_names() -> PackedStringArray:
        var names: PackedStringArray = []
        for sheet in _sheets:
            names.append(sheet.name)
        return names
    
    # 通过名称获取工作表数据 | Get sheet data by name
    func get_sheet_by_name(sheet_name: String) -> Dictionary:
        var target_name = sheet_name.replace(" ", "").to_lower()  # 标准化名称比较 | Normalized name comparison
        for sheet in _sheets:
            if sheet.normalized_name == target_name:
                return {
                    "name": sheet.name,
                    "data": _convert_to_continuous_grid(sheet.rows)  # 转换为连续网格 | Convert to continuous grid
                }
        return {}
    
    # --------- 私有方法 | Private Methods ---------
    
    # 加载共享字符串 | Load shared strings
    func _load_shared_strings():
        if not _zip.file_exists("xl/sharedStrings.xml"):
            return
        
        var data = _zip.read_file("xl/sharedStrings.xml")
        var parser = XMLParser.new()
        parser.open_buffer(data)
        
        var current_string = ""
        var in_text = false  # 文本标记状态 | Text flag status
        
        # XML 解析循环 | XML parsing loop
        while parser.read() == OK:
            match parser.get_node_type():
                XMLParser.NODE_ELEMENT:
                    if parser.get_node_name().to_lower() == "t":
                        in_text = true  # 进入文本节点 | Enter text node
                XMLParser.NODE_TEXT:
                    if in_text:
                        current_string += parser.get_node_data().strip_edges()  # 收集文本内容 | Collect text content
                XMLParser.NODE_ELEMENT_END:
                    if parser.get_node_name().to_lower() == "t":
                        in_text = false  # 退出文本节点 | Exit text node
                    elif parser.get_node_name().to_lower() == "si":
                        _shared_strings.append(current_string)  # 保存完成字符串 | Save completed string
                        current_string = ""

    # 发现工作表 | Discover worksheets
    func _discover_sheets():
        var sheet_map = _parse_workbook_relations()  # 解析关系映射 | Parse relationship map
        _parse_workbook_sheets(sheet_map)             # 解析具体工作表 | Parse actual worksheets

    # 解析工作簿关系 | Parse workbook relationships
    func _parse_workbook_relations() -> Dictionary:
        var relations = {}
        var rels_data = _zip.read_file("xl/_rels/workbook.xml.rels")
        if rels_data.is_empty():
            return relations
        
        var parser = XMLParser.new()
        parser.open_buffer(rels_data)
        
        # 解析XML关系 | Parse XML relationships
        while parser.read() == OK:
            if parser.get_node_type() == XMLParser.NODE_ELEMENT:
                var node_name = parser.get_node_name().to_lower()
                if node_name == "relationship":
                    var rel_type = _get_attribute(parser, "type").to_lower()
                    if "worksheet" in rel_type:
                        var rid = _get_attribute(parser, "id")
                        var target = "xl/" + _get_attribute(parser, "target").replace("\\", "/").lstrip("xl/")
                        relations[rid] = target  # 记录关系映射 | Record relationship mapping
        return relations

    # 解析工作簿中的工作表 | Parse worksheets in workbook
    func _parse_workbook_sheets(sheet_map: Dictionary):
        var wb_data = _zip.read_file("xl/workbook.xml")
        if wb_data.is_empty():
            return
        
        var parser = XMLParser.new()
        parser.open_buffer(wb_data)
        
        # 解析工作表条目 | Parse sheet entries
        while parser.read() == OK:
            if parser.get_node_type() == XMLParser.NODE_ELEMENT:
                var node_name = parser.get_node_name().to_lower()
                if node_name == "sheet":
                    var sheet_name = _get_attribute(parser, "name")
                    var rid = _get_attribute(parser, "r:id", "id")
                    var sheet_path = sheet_map.get(rid, "")
                    if sheet_path and _zip.file_exists(sheet_path):
                        _sheets.append(_parse_sheet(sheet_path, sheet_name))  # 添加解析好的工作表 | Add parsed sheet

    # 解析单个工作表 | Parse individual worksheet
    func _parse_sheet(path: String, name: String) -> ExcelSheet:
        var sheet_data = _zip.read_file(path)
        if sheet_data.is_empty():
            return ExcelSheet.new(name, [])
        
        var parser = XMLParser.new()
        parser.open_buffer(sheet_data)
        
        # 解析状态变量 | Parsing state variables
        var rows = []
        var current_row = {}
        var current_col = 0
        var max_col = 0
        var in_row = false
        var _in_cell = false
        var in_value = false
        var current_cell_type = ""
        
        # 主解析循环 | Main parsing loop
        while parser.read() == OK:
            match parser.get_node_type():
                XMLParser.NODE_ELEMENT:
                    var node_name = parser.get_node_name().to_lower()
                    match node_name:
                        "row":
                            in_row = true
                            current_row = {}
                            max_col = 0
                        "c":
                            _in_cell = true
                            current_cell_type = _get_attribute(parser, "t", "")
                            # 解析列字母(例如"A" -> 1)| Parse column letters (e.g. "A" -> 1)
                            var r = _get_attribute(parser, "r", "")
                            var col_str = ""
                            for c in r:
                                if c.is_valid_int(): break
                                col_str += c
                            current_col = _column_to_index(col_str)
                            if current_col > max_col:
                                max_col = current_col
                        "v":
                            in_value = true
                
                XMLParser.NODE_TEXT:
                    if in_value:
                        var value = parser.get_node_data().strip_edges()
                        var parsed_value = _parse_cell_value(value, current_cell_type)  # 解析单元格值 | Parse cell value
                        current_row[current_col] = parsed_value
                
                XMLParser.NODE_ELEMENT_END:
                    var node_name = parser.get_node_name().to_lower()
                    match node_name:
                        "row":
                            if in_row:
                                # 填充空单元格 | Fill empty cells
                                var filled_row = {}
                                for col in range(1, max_col + 1):
                                    filled_row[col] = current_row.get(col, "")
                                rows.append(filled_row)
                                in_row = false
                        "c":
                            _in_cell = false
                            current_cell_type = ""
                        "v":
                            in_value = false
        
        return ExcelSheet.new(name, rows)

    # 解析单元格值 | Parse cell value
    func _parse_cell_value(value: String, type: String):
        if type == "s":  # 共享字符串类型 | Shared string type
            if value.is_valid_int():
                var index = value.to_int()
                return _shared_strings[index] if index < _shared_strings.size() else ""
            return value
        else:
            # 合并数值解析逻辑
            if value.is_valid_float():
                var num = value.to_float()
                # 检查是否为整数 | Check if it's an integer
                if num == int(num):
                    return int(num)  # 返回整数 | Return integer
                else:
                    return num      # 返回浮点数 | Return float
            else:
                return value        # 无法解析时返回原值 | Fallback

    # XML属性获取工具方法 | XML attribute getter utility
    func _get_attribute(parser: XMLParser, name: String, fallback: String = "") -> String:
        for i in range(parser.get_attribute_count()):
            if parser.get_attribute_name(i).to_lower() == name.to_lower():
                return parser.get_attribute_value(i)
        return fallback

    # Excel列字母转数字 | Convert Excel column letters to numbers
    func _column_to_index(col: String) -> int:
        var index = 0
        for c in col.to_upper():
            index = index * 26 + (c.unicode_at(0) - 64)  # A=65 -> 1
        return index

    # 转换为连续行列号 | Convert to continuous grid numbering
    func _convert_to_continuous_grid(rows: Array) -> Dictionary:
        var grid = {}
        for row_idx in range(rows.size()):
            var row_num = row_idx + 1  # 行号从1开始 | Row numbers start from 1
            grid[row_num] = rows[row_idx]
        return grid


# 工作表数据容器类 | Worksheet Data Container
class ExcelSheet:
    extends RefCounted
    
    var name: String                   # 原始工作表名称 | Original sheet name
    var normalized_name: String        # 标准化名称(小写无空格)| Normalized name (lowercase no spaces)
    var rows: Array                    # 行数据存储 | Row data storage
    
    func _init(sheet_name: String, data: Array):
        name = sheet_name
        normalized_name = name.replace(" ", "").to_lower()
        rows = data

解析 xlsx 文件并返回一个字典
Analyze xlsx files and return a dictionary

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
godot_excel_reader icon image
Johnnash2017
godot_excel_reader

解析 xlsx 文件并返回一个字典Analyze xlsx files and return a dictionary

Supported Engine Version
4.4
Version String
1.0.1
License Version
MIT
Support Level
community
Modified Date
1 month ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers