当前位置: 首页 > news >正文

使用Go语言生成Excel任务表依赖图(Markdown文件mermaid图)

一、前言

在游戏中,任务是非常常见的玩法,可能会有主线任务,支线任务以及其它一些类型的任务,各任务可能还会有前置任务,即需要完成某个任务之后,才能做当前任务。在游戏开发中,配置表可以使用Excel来编辑,如果是任务表,可能会是如下配置方式:

TaskIDTaskTitlePreTask
10任务100
20任务200
11任务1110
21任务2120

当任务比较多的时候,它们的依赖关系将变得不直观,很容易出错,出错也不容易发现。

有没比较直观的方式进行查看,排错呢?笔者想到了目前非常流程的Markdown文件,它可以简单地通过文本的方式输入然后输出强大的各种图表。这里就可以使用mermaid图来直观展现。

关于mermaid图可以去官网https://mermaid.js.org/intro/查看用例。

下图为生成后的效果图:

0:无效任务ID
21:任务21
20:任务20
11:任务11
10:任务10

注意:mermaid图在渲染时,如果不设置subgraph则可能会出现乱序问题,即不是按代码中出现的顺序渲染。

二、实现

为了方便Go读取Excel,需要使用相关的Excel库,笔者使用excelize库。

根据前面的效果图,可以知道,这其实就是一个深度优先的树,实现方式有两种,一种是使用递归的方式来实现,这种方式实现起来简单,但是如果层次很深,那可能会出现栈溢出;另一种方式就是使用栈的方式来实现,将每一层节点先压栈,然后从栈顶取出一个节点然后再将其所有子节点入栈,再从栈顶取出一个节点处理,依此类推,直到栈中所有节点处理完毕。

下面列出使用递归方式实现的版本:

/*
MIT License# Copyright (c) 2023 WittonBellPermission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package mainimport ("flag""fmt""os""path/filepath""strings""github.com/xuri/excelize/v2"
)var taskIDField string
var taskTitleField string
var preTaskField string
var noCaseSensitive bool // 是否不区分大小写
var fieldNameRow uint    // 字段名所在行号
var dataStartRow uint    // 数据开始行号type node struct {taskID    stringtaskTitle string
}type multiMap map[string][]*nodefunc (slf multiMap) Add(key string, nd *node) {if len(slf) == 0 {slf[key] = []*node{nd}} else {slf[key] = append(slf[key], nd)}
}func (slf multiMap) Get(key string) []*node {if slf == nil {return nil}return slf[key]
}func (slf multiMap) Del(key string) {delete(slf, key)
}func searchKeyCol(rows *excelize.Rows) (TaskIDCol, PreTaskIDCol, TitleCol int) {row, err := rows.Columns()if err != nil {fmt.Println(err.Error())}for i, col := range row {name := colif noCaseSensitive {name = strings.ToLower(col)}if name == preTaskField {PreTaskIDCol = i + 1} else if name == taskIDField {TaskIDCol = i + 1} else if name == taskTitleField {TitleCol = i + 1}}return
}func readExcel(filePath string) multiMap {fd, err := excelize.OpenFile(filePath)if err != nil {fmt.Printf("读取文件`%s`失败", filePath)return nil}defer func() {fd.Close()}()TaskIDCol, PreTaskIDCol, TitleCol := -1, -1, -1sheetName := fd.GetSheetName(0)rows, err := fd.Rows(sheetName)if err != nil {return nil}defer func() {rows.Close()}()m := multiMap{}for i := 1; rows.Next(); i++ {if i == int(fieldNameRow) {TaskIDCol, PreTaskIDCol, TitleCol = searchKeyCol(rows)isOk := trueif TaskIDCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", taskIDField)}if PreTaskIDCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", preTaskField)}if TitleCol < 0 {isOk = falsefmt.Printf("要求字段名:%s\n", taskTitleField)}if !isOk {return nil}}if i < int(dataStartRow) {continue}TaskIDCell, err := excelize.CoordinatesToCellName(TaskIDCol, i)if err != nil {continue}PreTaskIDCell, err := excelize.CoordinatesToCellName(PreTaskIDCol, i)if err != nil {continue}TitleColCell, err := excelize.CoordinatesToCellName(TitleCol, i)if err != nil {continue}TaskID, err := fd.GetCellValue(sheetName, TaskIDCell)if err != nil || TaskID == "" {continue}Title, err := fd.GetCellValue(sheetName, TitleColCell)if err != nil || Title == "" {continue}PreTaskID, err := fd.GetCellValue(sheetName, PreTaskIDCell)if err != nil {continue}if PreTaskID == "" {PreTaskID = "0"}m.Add(PreTaskID, &node{taskID: TaskID, taskTitle: Title})}return m
}func usage() {w := flag.CommandLine.Output()fmt.Fprintf(w, "%s 应用程序是将Excel任务表中的关系转换成Markdown的mermaid图,方便使用Markdown工具直观地查看任务依赖。", filepath.Base(os.Args[0]))fmt.Fprintln(w)fmt.Fprintf(w, "命令格式:%s -hr [字段所在行号] -dr [数据起始行号] [-nc] -id [任务ID字段名] -t [任务标题字段名] -pid [前置任务ID字段名] -o <输出文件> <Excel文件路径>", filepath.Base(os.Args[0]))fmt.Fprintln(w)flag.CommandLine.PrintDefaults()fmt.Fprintln(w, "  -h")fmt.Fprintln(w, "    \t显示此帮助")
}func main() {var outputFile stringflag.CommandLine.Usage = usageflag.BoolVar(&noCaseSensitive, "nc", false, "字段名不区分大小写")flag.UintVar(&fieldNameRow, "hr", 1, "字段所在行号")flag.UintVar(&dataStartRow, "dr", 2, "数据起始行号")flag.StringVar(&taskIDField, "id", "ID", "-id [任务ID字段名]")flag.StringVar(&taskTitleField, "t", "Title", "-t [任务标题字段名]")flag.StringVar(&preTaskField, "pid", "PreTask", "-pid [前置任务ID字段名]")flag.StringVar(&outputFile, "o", "任务图.md", "-o <输出文件>")flag.Parse()if flag.NArg() < 1 {usage()return}if noCaseSensitive {taskIDField = strings.ToLower(taskIDField)taskTitleField = strings.ToLower(taskTitleField)preTaskField = strings.ToLower(preTaskField)}mapTask := readExcel(flag.Arg(0))buildGraph(mapTask, outputFile)
}func buildGraph(mapTask multiMap, outputFile string) {graph := "```mermaid\ngraph TB\n"graph += "subgraph  \n"root := mapTask.Get("0")for _, v := range root {graph += visit(rootNodeName, v, mapTask)}graph += "end\n"graph += "```"os.WriteFile(outputFile, []byte(graph), os.ModePerm)fmt.Println("完成")
}func visit(parent string, nd *node, mapTask multiMap) string {slice := mapTask.Get(nd.taskID)graph := fmt.Sprintf("%s --> %s:%s\n", parent, nd.taskID, nd.taskTitle)if parent == rootNodeName {graph += "subgraph  \n"}for _, x := range slice {graph += visit(fmt.Sprintf("%s:%s", nd.taskID, nd.taskTitle), x, mapTask)}mapTask.Del(nd.taskID)if parent == rootNodeName {graph += "end\n"}return graph
}

使用栈实现的版本笔者放在excelTask2md了。

相关文章:

使用Go语言生成Excel任务表依赖图(Markdown文件mermaid图)

一、前言 在游戏中&#xff0c;任务是非常常见的玩法&#xff0c;可能会有主线任务&#xff0c;支线任务以及其它一些类型的任务&#xff0c;各任务可能还会有前置任务&#xff0c;即需要完成某个任务之后&#xff0c;才能做当前任务。在游戏开发中&#xff0c;配置表可以使用…...

C语言和C++的区别在哪?如何自学C++?

C语言和C是两种不同的编程语言&#xff0c;它们在语法、特性和用途上有一些区别。以下是C语言和C的一些主要区别&#xff1a; 面向对象编程&#xff1a;C是一种支持面向对象编程的语言&#xff0c;它在C语言的基础上添加了类、对象、继承、多态等面向对象的特性。而C语言是一种…...

功能强大的开源数据中台系统 DataCap 1.13.0 发布

推荐一套基于 SpringBoot 开发的简单、易用的开源权限管理平台&#xff0c;建议下载使用: https://github.com/devlive-community/authx 推荐一套为 Java 开发人员提供方便易用的 SDK 来与 OpenAI 的 API 进行交互组件&#xff1a;https://github.com/devlive-community/openai…...

JTS Self-intersection异常TopologyException: side location conflict解决办法

JTS Self-intersection异常TopologyException: side location conflict解决办法 举例&#xff1a;问题围栏 MULTIPOLYGON (((114.0905685 32.1120567, 114.0905685 32.112957, 114.0905685 32.1138535, 114.0905685 32.1147537, 114.0905685 32.115654, 114.0905685 32.11655…...

Maven: No compiler is provided in this environment.

在Eclipse中运行Maven项目&#xff0c;报错&#xff1a; No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? 解决方法&#xff1a; Windows > Preferences > Java > Installed JREs > Add > Standard VM,…...

.NET-10. 其他-VSTO+VBA

VSTOVBA 前言VSTO 外接程序介绍&#xff1a;VSTO参考链接&#xff1a;VSTO 例子&#xff1a; VBA:参考链接&#xff1a; 前言 主要用于Excel插件。 VSTO 外接程序介绍&#xff1a; Excel、Word、PowerPoint、Project、Visio等等Office应用程序 相对简单 VSTO参考链接&#x…...

相机传感器格式与镜头光圈参数

相机靶面大小 CCD/CMOS图像传感器尺寸&#xff08;sensor format&#xff09;1/2’‘、1/3’‘、1/4’实际是多大 1英寸——靶面尺寸为宽12.7mm*高9.6mm&#xff0c;对角线16mm。 2/3英寸——靶面尺寸为宽8.8mm*高6.6mm&#xff0c;对角线11mm。 1/2英寸——靶面尺寸为宽6.…...

Android 设置头像(拍照获取、相册获取、裁剪照片)

在Android原生态开发过程中&#xff0c;往往会设计到用户头像的设置问题&#xff0c;一般来讲设置头像需要用到拍照、获取照片、存储照片、裁剪照片、显示照片等问题&#xff0c;本文将一步一步的进行说明讲解。 首先需要强调几点我在开发过程中遇到的问题。 权限问题&#xf…...

android开发之Android 自定义滑动解锁View

自定义滑动解锁View 需求如下&#xff1a; 近期需要做一个类似屏幕滑动解锁的功能&#xff0c;右划开始&#xff0c;左划暂停。 需求效果图如下 实现效果展示 自定义view如下 /** Desc 自定义滑动解锁View Author ZY Mail sunnyfor98gmail.com Date 2021/5/17 11:52 *…...

CAD绘制法兰、添加光源、材质并渲染

首先绘制两个圆柱体&#xff0c;相互嵌套 在顶部继续绘制圆柱体&#xff0c;这是之后要挖掉的部分 在中央位置绘制正方形 用圆角工具&#xff1a; 将矩形的四个角分别处理&#xff0c;效果&#xff1a; 用拉伸工具 向上拉伸到和之前绘制的圆柱体高度齐平 绘制一个圆柱体&#…...

ChatGPT访问流量下降的原因分析

​自从OpenAI的ChatGPT于11月问世以来&#xff0c;这款聪明的人工智能聊天机器人就席卷了全世界&#xff0c;人们在试用该工具的同时也好奇该技术到底将如何改变我们的工作和生活。 但近期Similarweb表示&#xff0c;自去ChatGPT上线以来&#xff0c;该网站的访问量首次出现下…...

干货 | 详述 Elasticsearch 向量检索发展史

1. 引言 向量检索已经成为现代搜索和推荐系统的核心组件。 通过将复杂的对象&#xff08;例如文本、图像或声音&#xff09;转换为数值向量&#xff0c;并在多维空间中进行相似性搜索&#xff0c;它能够实现高效的查询匹配和推荐。 图片来自&#xff1a;向量数据库技术鉴赏【上…...

mysql常见面试题,高频题目放送

互联网的产品架构是包含这接入层&#xff0c;逻辑处理以及储存层的&#xff0c;其中储存层承载着较多的数据以及持久化的任务&#xff0c;而说到储存层&#xff0c;避免不了说到数据库&#xff0c;在我们面试的时候&#xff0c;数据库的知识题目占比是非常多的&#xff1a; 1.…...

使用 PowerShell 将 Excel 中的每个工作表单独另存为独立的文件

导语&#xff1a;在日常工作中&#xff0c;我们经常需要处理 Excel 文件。本文介绍了如何使用 PowerShell 脚本将一个 Excel 文件中的每个工作表单独另存为独立的 Excel 文件&#xff0c;以提高工作效率。 1. 准备工作 在开始之前&#xff0c;请确保已经安装了 Microsoft Exc…...

python提取pdf图片

import fitz import re import osdef save_pdf_img(path, save_path):path: pdf的路径save_path : 图片存储的路径# 使用正则表达式来查找图片checkXO r"/Type(? */XObject)"checkIM r"/Subtype(? */Image)"# 打开pdfdoc fitz.open(path)# 图片计数im…...

Vue3 表单输入绑定简单应用

去官网学习→表单输入绑定 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 表单输入绑定</h1><input type"text" placeholder"输入框" v-model"msg"…...

如何解决 Elasticsearch 查询缓慢的问题以获得更好的用户体验

作者&#xff1a;Philipp Kahr Elasticsearch Service 用户的重要注意事项&#xff1a;目前&#xff0c;本文中描述的 Kibana 设置更改仅限于 Cloud 控制台&#xff0c;如果没有我们支持团队的手动干预&#xff0c;则无法进行配置。 我们的工程团队正在努力消除对这些设置的限制…...

近期学习练习

练习&#xff1a; 或&#xff1a; //sqrt是数学库函数&#xff0c;开平方 //头文件为math.h 或...

平台安全之中间件安全

理解中间件 一次web访问的顺序&#xff0c;web浏览器->web服务器&#xff08;狭义&#xff09;->web容器->应用服务器->数据库服务器 web服务器 广义&#xff1a;提供广义web服务的软件或主机 狭义&#xff1a;提供w3服务的软件或主机&#xff0c;即Web服务器软件…...

芒果 TV 基于 Flink 的实时数仓建设实践

公司简介&#xff1a;芒果 TV 作为湖南广电旗下互联网视频平台&#xff0c;在“一云多屏&#xff0c;多元一体”的战略指导下&#xff0c;通过内容自制&#xff0c;培植核心竞争力&#xff0c;从独播、独特走向独创&#xff0c;并通过市场化运作完成 A 轮、B 轮融资&#xff0c…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...