由 excel 转换为 markdown,及收获

1 问题

构建之法(现代软件工程)东北师大站[http://www.cnblogs.com/younggift/]的每周学生作业成绩,执行教学团队[https://home.cnblogs.com/u/xinz]要求,发布在 cnblogs 上。作业中包括每位同学在作业单项中取得的分数、累加、按比例分配、线性映射等数据,本学期约17次作业成绩或排序统计,产生约70个表格。成绩公布后学生申诉教师修改成绩时,这些表格需要重新计算,再次发布。每次作业申诉数量大约5次左右。

Emacs是我平时的主要工作环境,所以优选熟悉的工具。上学期第一次成绩发布使用了 org-mode 中的表格,发布为html源代码,粘贴到 cnblogs上。成绩累加、变更后的重新计算非常麻烦,org-mode主要是大纲写作工具,不是电子表格。使用的感觉类似于在word的表格中计算。

Excel适合记录、计算、数据变更后再计算,cnblogs使用纯文本、html 或markdown格式。其中 markdown 格式语法简洁,支持大纲式写作和表格,所以适合成绩发布和变更后再次发布。因此,上学期除最初一两次,以及本学期大部分作业成绩发布,先在excel下记录和计算,然后转换为 markdown 格式。

本文回顾我使用的三种方法,由 excel 文件转为 markdown,及收获。

2 方案1,FFL 的 exceltk.exe

推荐使用此方案。在本学期大多发布中,我都使用了这一工具。没有使用的几次是在等待升级,采用了临时方案。

exceltk 最初是小牛同学拷给我的,说这个非常方便。后来 FLL 老师做过升级,其中对公式的支持、支持移动设备上查看cnblogs上的表头不变形、小数点保留位数这几次升级都很有帮助。

FFL 老师对 exceltk.exe 的介绍在

[http://www.cnblogs.com/math/p/exceltk.html]。

源码:https://github.com/fanfeilong/exceltk
下载:http://files.cnblogs.com/files/math/exceltk0.0.9.7

我的使用方法类似

exceltk.exe -t md -p 2 -xls 构建之法作业成绩debug.xls

把excel中的每个 sheet 导出成 markdown,小数点保留两位精度。

3 方案2,sed

exceltk有一段时间不支持 excel 公式计算结果,我换用了临时方案,等待exceltk升级后切换回来。

3.1 为什么需要公式

我的excel中使用了 vlookup, match 等函数,以方便学生申诉以后的成绩变更。

比如个人作业单项变更,需要因此变更的字段有 个人作业总和、个人作业映射到占本周总成绩20%、本周成绩总和、数周累积、数周累积排序、数周累积去除负分同学排序、数周累积映射到[50,100];再如团队成绩单项变更,需要因此变更的字段有 该团队总分、该团队总分映射到本周总成绩的30%、该团队所有成员的团队成绩、该团队所有成员的本周总成绩、该团队所有成员的数周累积以及排序和映射到[50,100]。诸如此类。由每周作业的单项数目不同,所以公式不宜用固定列的序号,比如"=SUM(B4:L4)",而采用了vlookup & match 函数对字段寻址。

vlookup & match 函数类似这样:

=VLOOKUP(F4,小组!$A:$W,MATCH("合计",小组!$1:$1,0)+1,FALSE)

含义是

(1) F4单元格所在列是"所属小组",每行一人,随行变化。此例中的值为

"=VLOOKUP(A4,组员归属!A:B,2,FALSE)",求值结果 "飞天小女警"。

(2) 取"飞天小女警"的"合计。"取 名为"小组"的工作表 中,表头 (第一行)写

作"合计"的那列的数据,要求 A列的值为 F4的那行,即"飞天小女警"。

(3) 总结,姓名 -> 组员归属 -> 小组成绩.

这样,当小组成绩变更以后,该团队所有成员的小组成绩、本周总成绩、数据累积等都会自动变化。我只要修改变更的单项,然后再把excel导出成 markdown发布就行了。不使用公式,每次变更需要顺序修改、复制粘贴若干次,时间长工作量大,每个单项都要消耗30分钟左右,还担心出错。使用公式后成绩变更一次几分钟。

3.2 excel -> csv -> markdown

sed 是 perl 的灵感来源之一,另一个是 awk。它们专门辅助 shell 脚本,awk做计算,sed做文本替换。

我用的临时方案脚本,在这里[https://coding.net/u/younggift/p/xls2md/]。

3.2.1 excel -> csv, vba

我把 excel 导出为 csv 格式,这样完成了公式的计算,也成为了文本格式,sed才能处理。

使用了 stackoverflow 上的 vbs 脚本,稍作修改,按数据表名导出。

----脚本开始

if WScript.Arguments.Count < 2 Then

WScript.Echo "Please specify the source and the destination files. Usage: ExcelToCsv <xls/xlsx source file> "

Wscript.Quit

End If

csv_format = 6

Set objFSO = CreateObject("Scripting.FileSystemObject")

src_file = objFSO.GetAbsolutePathName(Wscript.Arguments.Item(0))

dest_file = objFSO.GetAbsolutePathName(WScript.Arguments.Item(1))

rem msgbox(dest_file)

Dim oExcel

Set oExcel = CreateObject("Excel.Application")

Dim oBook

Set oBook = oExcel.Workbooks.Open(src_file)

oBook.Worksheets(1).Activate

oBook.SaveAs dest_file+"\个人", csv_format

oBook.Worksheets(2).Activate

oBook.SaveAs dest_file+"\结对", csv_format

oBook.Worksheets(3).Activate

oBook.SaveAs dest_file+"\小组", csv_format

oBook.Worksheets(5).Activate

oBook.SaveAs dest_file+"\本周", csv_format

oBook.Worksheets(8).Activate

oBook.SaveAs dest_file+"\数周排序-去除负分", csv_format

oBook.Worksheets(9).Activate

oBook.SaveAs dest_file+"\数周累积负分", csv_format

oBook.Close False

oExcel.Quit

----脚本结束

调用的时候,在bat中,如下。

----bat片段开始

chcp 936

set filename=构建之法作业成绩beta-review.xls

xls2csv.vbs %filename% .

----bat片段结束

3.2.2 cvs -> mark down, sed

根据不同数据表的格式不同,我写了不同的 sed 脚本。"应该"把某些 sed 脚本抽象合并到同一个文件中,不过考虑到复用次数不多、可预见的复用增长不大、以及懒,所以就复制粘贴,然后分别修改了。

所以 shell 脚本看起来这样,里面的 c1_head 与 c1, c2_head 与 c2 长得很像,抽象优化强迫症患者可能感觉不好。

----shell脚本片段开始

sed -f c1_head.sed 本周.csv | sed -f c2_head.sed > 本周.md

sed -f c1.sed 数周排序-去除负分.csv | sed -f c2.sed > 数周排序-去除负分.md

----shell脚本片段开始

每行分成两个sed执行,用管道连接,重定向到指定名称的md即markdown文件中。分成两个sed执行是必要的,因为 sed 不支持对刚刚粘贴来的行通过引用行号编辑。或者是因为我没有做出足够好的正则表达式 (@典同学,@marverick@柳园bbs) ,考虑sed/正则表达式的处理能力,此处应该不涉及类似括号匹配的上下文无关文法。

3.3 sed解读

3.3.1 测试用例

(1) cvs的前几行

列之间用","分隔。在我的临时sed脚本中,没有处理转义","的情况,解决的方案是在xls中避免使用半角逗号。


,20160901,20160908,20160922,20160929,20161013,20161020,20161027,20161103,20161110,累积,映射至[100,60],映射至[100,50]

,,,pre-α,α-1,α-2,α-review,β-1,β-2,β-review,,,

[黄兴](http://www.cnblogs.com/huangxman),72.00 ,80.00 ,68.60 ,5.15 ,41.06 ,63.93 ,60.60 ,69.60 ,78.03 ,538.97 ,100.00 ,100.00

[李俞寰](http://www.cnblogs.com/li-yuhuan/),85.00 ,86.00 ,69.53 ,35.75 ,44.35 ,64.53 ,41.20 ,31.20 ,78.63 ,536.19 ,99.79 ,99.73

[张金生](https://www.cnblogs.com/jx8zjs/),93.00 ,94.00 ,72.47 ,-1.00 ,66.06 ,-1.53 ,39.60 ,63.88 ,88.41 ,514.89 ,98.14 ,97.67

[程媛媛](https://www.cnblogs.com/yuanyuancheng/),61.00 ,76.00 ,-7.60 ,13.27 ,41.23 ,94.67 ,69.40 ,75.20 ,86.51 ,509.68 ,97.73 ,97.17

(2) markdown的前几行

形如"|:--|"的文字,用于分隔出表头。


||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|

|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|

|[黄兴](http://www.cnblogs.com/huangxman)|72.00 |80.00 |68.60 |5.15 |41.06 |63.93 |60.60 |69.60 |78.03 |538.97 |100.00 |100.00 |

|[李俞寰](http://www.cnblogs.com/li-yuhuan/)|85.00 |86.00 |69.53 |35.75 |44.35 |64.53 |41.20 |31.20 |78.63 |536.19 |99.79 |99.73 |

|[张金生](https://www.cnblogs.com/jx8zjs/)|93.00 |94.00 |72.47 |-1.00 |66.06 |-1.53 |39.60 |63.88 |88.41 |514.89 |98.14 |97.67 |

|[程媛媛](https://www.cnblogs.com/yuanyuancheng/)|61.00 |76.00 |-7.60 |13.27 |41.23 |94.67 |69.40 |75.20 |86.51 |509.68 |97.73 |97.17 |

|[张政](https://www.cnblogs.com/regretless/)|90.00 |98.00 |71.27 |5.07 |65.48 |-2.73 |20.00 |68.38 |92.91 |508.38 |97.63 |97.04 |

3.3.2 把 , 转成 |

# , => |
s/,/|/g;
s/^/|/g;
s/$/|/g;

(1) s是substitute.

(2) s / 原来的文字 / 替换成的文字 / 全局

(3) ^表示行首,$表示行尾。

总的效果是,把所有逗号换成竖线,行首行尾各加一条竖线。

3.3.3 表头 |:--|

数据流是这样的 (cvs) -> c1_head -> c2_head -> (md),其中括号里的是产物,没括号的是加工。

在 c1_head.sed 中:

# table head, copy & paste
1h
1G

在 c2_head.sed 中:

2s/[^|]//g
2s/|/|:--/g
2s/|:--$/|/g

(1) 1h 复制第1行,1G粘贴在当前位置。得到

||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|
||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|

(2) c2_head.sed中几行的作用,是对只转换第2行,不是对全局影响。

(3) 2s/[^|]//g,除了竖线以外,去除所有字符。

||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|
||||||||||||||

(4) 2s/|/|:--/g,把第2行的所有竖线,转换为 |:--

||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--

(5) 2s/|:--$/|/g,把第2行行尾前的 :-- 转换为 竖线。

||20160901|20160908|20160922|20160929|20161013|20161020|20161027|20161103|20161110|累积|映射至[100,60]|映射至[100,50]|<br>
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|<br>

之所以采用复制粘贴-替换的方法,是因为sed不会计数。

3.3.4 空行 ||||||||||||||

原始 cvs 形如:

---cvs片段开始<br>
姓名,继续迭代,PSP,进度条,代码堆积图,博客字数堆积图,beta发布评论,加分事项,加分分值,合计,占比20%
满分分值,,5,5,5,5,5,,,25,20.00
---此行空白
[程媛媛](https://www.cnblogs.com/yuanyuancheng/),,5,5,5,5,5,,,25,20.00
[杜桥](http://www.cnblogs.com/duq11/),,5,5,5,5,5,,,25,20.00
---cvs片段结束

期待修改为形如下面的样子。"||||||||||||||"一行,用于建立空行,目的是造成两行表头的效果。

||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|满分分值|25.00 |20.00 |10|20||35.00 |30.00 |5*N|30.00 |||100.00 |
||||||||||||||
|[程媛媛](https://www.cnblogs.com/yuanyuancheng/)|25.00 |20.00 |0|0|飞天小女警|37.00 |31.71 |5.80 |34.80 |||86.51 |
|[杜桥](http://www.cnblogs.com/duq11/)|25.00 |20.00 |0|0|奋斗吧兄弟|32.00 |27.43 |5.00 |30.00 |||77.43 |
|[杜月](http://www.cnblogs.com/qianhuihui/)|24.00 |19.20 |0|0|金州勇士|48.00 |41.14 |5.12 |30.72 |||91.06 |
|[宫成荣](http://www.cnblogs.com/gongcr/)|25.00 |20.00 |0|0|新蜂|19.00 |16.29 |6.00 |36.00 |||72.29 |

在 c1_head.sed 中:

# table head, copy & paste
1h
1G
----此行空白
# blank line, copy & paste
3G

在 c2_head.sed 中:

4d
5s/[^|]//g

(1) 在 c1_head中 复制第1行,另粘贴到第3行一份。此时文本文件仍维持原有

的行号,新粘贴的文字不能使用行号引用,因此不能进一步编辑。

||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|满分分值|25.00 |20.00 |10|20||35.00 |30.00 |5*N|30.00 |||100.00 |
||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
----此行空白
[程媛媛](https://www.cnblogs.com/yuanyuancheng/),,5,5,5,5,5,,,25,20.00
[杜桥](http://www.cnblogs.com/duq11/),,5,5,5,5,5,,,25,20.00

(2) c2_head.sed中的4d,删除第4行空白行 (在c1_head中的第3行) 。

||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|满分分值|25.00 |20.00 |10|20||35.00 |30.00 |5*N|30.00 |||100.00 |
||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
[程媛媛](https://www.cnblogs.com/yuanyuancheng/),,5,5,5,5,5,,,25,20.00
[杜桥](http://www.cnblogs.com/duq11/),,5,5,5,5,5,,,25,20.00

(3) 5s/[^|]//g,把原第5行 (删除第4行后显示为第4行,仍计数第5行)改为 ||||||||||||||

||个人作业|占比20%|结对|占比20%|所属小组|小组成绩|占比30%|贡献系数(4人分配4*20)|占比30%|特别加分事由|特别加分数值|本周得分|
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|满分分值|25.00 |20.00 |10|20||35.00 |30.00 |5*N|30.00 |||100.00 |
||||||||||||||
|[程媛媛](https://www.cnblogs.com/yuanyuancheng/)|25.00 |20.00 |0|0|飞天小女警|37.00 |31.71 |5.80 |34.80 |||86.51 |
|[杜桥](http://www.cnblogs.com/duq11/)|25.00 |20.00 |0|0|奋斗吧兄弟|32.00 |27.43 |5.00 |30.00 |||77.43 |
|[杜月](http://www.cnblogs.com/qianhuihui/)|24.00 |19.20 |0|0|金州勇士|48.00 |41.14 |5.12 |30.72 |||91.06 |
|[宫成荣](http://www.cnblogs.com/gongcr/)|25.00 |20.00 |0|0|新蜂|19.00 |16.29 |6.00 |36.00 |||72.29 |

4 方案3,emacs elisp

Emacs是我平时使用的工具,所以本学期最初的转换,当需要公式,因此由 cvs转成 markdow 还没有被 ffl 支持时,自然地想到用 elisp 作为临时方案。

elisp是上下文无关文法 (或者更强?)的语言,因此可以计数,得以避免使用复制粘贴-修改这样的手段生成表头行。col-count用于存储列的数量。

(defun cvs2md-table ()
"replace cvs format to markdown talbe."
(interactive)
; , -> |
(goto-char (point-min))
(replace-string "," "|")
(goto-char (point-min))
(replace-regexp "^" "|")
(goto-char (point-min))
(replace-regexp "$" "|")
; table head
(setq col-count 0)
(goto-char (point-min))
(setq col-count (count-matches "|" (line-beginning-position) (line-end-position)))
(goto-line 2)
(setq head-count 0)
(while (< head-count col-count)
(insert "|:--")
(setq head-count (1+ head-count))
)
(insert "|")
(open-line 1)
; delete "||" in the last line
(goto-char (point-max))
(beginning-of-line)
(kill-line)
)

5 收获

一个技术方案是否能被别人采用,因此具有更帮助更多的人而不仅是自己,取决于多个方面。比如,emacs这类相对小众之下开发的代码,sed这种需要运行环境和只能命令行操作的脚本,对于很多人不算友好。ffl的工具可以复制粘贴,也支持命令行,基于.net运行环境在当今不再是个问题,友好得多。

语言的能力越强,越接近于图灵机,实现通用功能,比如计数、插入某个特定数量的字符,就越容易。所以在DSL中要小心配置文件容易迅速成长为上下文无关文法,然后图灵等价,成为新的语言,专用特性的优势就消失了。能力弱一些的语言,也不见得不能实现,比如把插入特定数量的字符等价为 (事实上这才是原始的需求)复制粘贴再修改某一行。还是要确定自己的问题到底是什么,计算模型和数学模型的选择,然后才是代码。

要清楚--尽可能第一时间发现--语言或工具的限制,比如sed不为刚插入和文本编排行号,因此不能基于行编辑。


博客会手工同步到以下地址:

[http://zhuanlan.zhihu.com/younggift]

[https://younggift.net/]

[http://blog.csdn.net/younggift]

[http://giftdotyoung.blogspot.com]

由 excel 转换为 markdown,及收获的更多相关文章

  1. HTML table表格转换为Markdown table表格[转]

    举个栗子,当我想要把这个页面的第一个表格转换成Markdown Table时,怎么做更快,效率更高? 只需简单三步,请看示例: 第一步:复制包含HTML table标签的代码 复制table代码(HT ...

  2. Excel.Application SaveAs 把excel转换为html

    Excel.Application SaveAs 中的第二个参数的值: 可以直接用 10 进制的值代替左边的这些 xl 类型 . 例如:把excel转换为html的js: var oWB = oXL. ...

  3. C#, VB.NET如何将Excel转换为PDF

    在日常工作中,我们经常需要把Excel文档转换为PDF文档.你是否在苦恼如何以C#, VB.NET编程的方式将Excel文档转换为PDF文档呢?你是否查阅了许多资料,运用了大量的代码,但转换后的效果依 ...

  4. C#,VB.NET 如何将Excel转换为Text

    在工作中,有时我们需要转换文档的格式,之前已经跟大家介绍过了如何将Excel转换为PDF.今天将与大家分享如何将Excel转换为Text.这次我使用的依然是免费版的Spire.XLS for .NET ...

  5. C# 将Excel转换为PDF

    C# 将Excel转换为PDF 转换场景 将Excel转换为PDF是一个很常用的功能,常见的转换场景有以下三种: 转换整个Excel文档到PDF转换Excel文档的某一个工作表到PDF转换Excel文 ...

  6. Java中excel转换为jpg/png图片 采用aspose-cells-18.6.jar

    一  Java中excel转换为jpg/png图片 package com.thinkgem.jeesite.modules.task.util; import com.aspose.cells.Im ...

  7. C# Excel转换为Json

    demo:https://files.cnblogs.com/files/guxingy/Excel%E8%BD%AC%E6%8D%A2%E4%B8%BAJson%E5%AF%B9%E8%B1%A1. ...

  8. Word转换为markdown

    Word转换为markdown 首先你的电脑要有office word 1   安装pandoc https://github.com/jgm/pandoc/releases,可以找到最新的pando ...

  9. 【转载】C#, VB.NET如何将Excel转换为PDF

    在日常工作中,我们经常需要把Excel文档转换为PDF文档.你是否在苦恼如何以C#, VB.NET编程的方式将Excel文档转换为PDF文档呢?你是否查阅了许多资料,运用了大量的代码,但转换后的效果依 ...

随机推荐

  1. 使用Fiddler对Android或者iOS设备进行抓包

    1.PC端Fiddler配置 Tools->HTTPS->选中“Decrpt HTTPS traffic”,“Ignore server certificate errors” Tools ...

  2. SCCM 客户端的修复

    1. Stopping the SMS Agent Host service (net stop ccmexec) 2. Stopping the WMI service (net stop winm ...

  3. php基础系列:PHP连接MySQL数据库用到的三种API

    参考自php手册.本文没有太大意义,仅为方便自己上网查阅. 1.PHP的MySQL扩展2.PHP的mysqli扩展3.PHP数据对象(PDO) MySQL扩展函数 这是设计开发允许PHP应用与MySQ ...

  4. MiniProfiler 兼容 Entity Framework 6

    一直以来都是在用MiniProfiler配合ASP.NET MVC做请求的监控. 在某项目升级Entity Framework 6之后,在执行查询时报错误: --------------无法将类型为“ ...

  5. [转]在EntityFramework6中执行SQL语句

    本文转自:http://www.cnblogs.com/wujingtao/p/5412329.html 在上一节中我介绍了如何使用EF6对数据库实现CRDU以及事务,我们没有写一句SQL就完成了所有 ...

  6. Maven系列一pom.xml 配置详解

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...

  7. luogu[1135]奇怪的电梯

    题目描述 呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯.大楼的每一层楼都可以停电梯,而且第i层楼(1<=i<=N)上有一个数字Ki(0<=Ki<=N).电梯只有四个按钮:开 ...

  8. java 继承(下)

    1.抽象方法一定要写在抽象类中. 2.抽象类只在描述该事事务应该具备的东西. 3.抽象只能是抽象类和抽象方法. 4,抽象类没有任何抽象方法,这种类是不让创建对象. private  static

  9. AASM rule of scoring sleep stages using EEG signal

    Reference: AASM (2007). The AASM Manual for the Scoring of Sleep and Associated Events: Rules, Termi ...

  10. C# 多重overide

    overide 是覆盖的意思,用在且仅用在虚函数上,虚函数可以是virtual或abstract修饰的,或者是overide修饰的. 文档大概是这么说的. 由此知道,由overide修饰的函数都是虚函 ...