PBIDEA:用 uo_csv 读写 CSV 文件(UTF-8/BOM/引号规则与逗号错位坑,附与 SaveAs 对比)
PBIDEA:用 uo_csv 读写 CSV 文件(UTF-8/BOM/引号规则与逗号错位坑,附与 SaveAs 对比)阅读说明
1. 适用版本:PB 10 及以上(本文全部结论在 PB 12.5 + PBIDEA 最新版 实机跑通;uo_csv 属 PBIDEA 基础库 websuite.pbl,PB10 亦可编译,文中已给出 PB10 兼容包)
2. 支持数据库:SQL Server 2008 R2(仅第 8 节「DataStore 导出」用到,连接串 127.0.0.1,18433;其余示例不依赖数据库)
3. 操作系统与环境要求:Windows 7+;工程需引入 PBIDEA 的 websuite.pbl,运行目录需放 PbIdea.dll(FromUTF8 等全局函数也来自它);ToXLSX 还需 plugin\PbideaExcel.dll
4. 难度系数:★★☆☆☆
5. 其它阅读说明:第 8 节需要 DataWindow/DataStore 基础概念;所有示例数据由代码自建(建表、插入、清理全自动),读者无需手工准备
一、为什么需要一个 CSV 对象
CSV 是数据交换里最“没脾气”的格式:Excel 认、财务系统认、对账平台认、浏览器下载列表也认。在 PB 里做导出,很多人第一反应是 dw.SaveAs(file, CSV!, TRUE),但真到项目里会撞上几堵墙:
[*]编码:SaveAs 出来的是本机 ANSI(中文 GBK),发给用 Excel 在线版 / Mac / 网页解析的同事,中文经常变问号;
[*]编码方向反过来:别人给你一个 UTF-8 的 CSV,用 FileRead 读出来是乱码,得自己转;
[*]DataWindow 之外的导出:内存里算好的一批结果、接口返回的 JSON、日志统计,压根没有 DataWindow,用不了 SaveAs;
[*]读回来:ImportString(CSV!, ...) 只认 DataWindow 已定义的列,临时 CSV 根本灌不进去。
PBIDEA 的 uo_csv(在 websuite.pbl 里)就是补这几块空缺的:写文件三段式 Open → Write → Close,读文件一步 toJson 变成 uo_json 数组,JSON 还能再 toCSV 写回去,另外附赠 ds2csv / dw2csv 把 DataWindow 直接倒成 CSV,以及 ToXLSX 把 CSV 转 xlsx。
本文把它的 API 全貌、三种 Open 开关、toJson 三个重载、编码真相 一次性讲清,并把我实机踩出来的 14 条坑 逐条给出证据。
二、uo_csv 是什么、从哪来
uo_csv 是一个 nonvisualobject,方法分两类:
[*]DLL 导出(system library 方式):Open / Write / Close / toJson / toCSV / ToXLSX,声明形如 system library "pbidea.dll" alias for "csvOpen",由 PbIdea.dll 提供实现;
[*]纯 PowerScript 实现:ds2csv(datastore, file) 与 dw2csv(datawindow, file, usedisplayvalue),写在 uo_csv.sru 源码里,靠遍历 Object.DataWindow.Column.Count 拼行。
两个必须记住的前提:
[*]它不是 autoinstantiate 对象,必须 lcsv = create uo_csv,用完 destroy;
[*]运行目录要有 PbIdea.dll。把 websuite.pbl 加进 liblist 只是编译期,运行期缺 DLL 会在第一次调用时直接报错。
三、API 全表(对照 PBIDEA 导出源码核实)
方法作用返回值
Open(file)打开待写 CSV(覆盖式),默认 htmlEncode=false、double_quote=trueboolean
Open(file, htmlEncode)第二参:是否把 < > & 转成 HTML 实体boolean
Open(file, htmlEncode, double_quote)第三参:是否给每个字段加双引号boolean
Write(any data[])写一整行(传数组)boolean
Write(...)变参写法,一行几个值就写几个boolean
Close()关闭并真正落盘boolean
toJson(file, json)CSV → JSON,hasHeader 默认 trueboolean
toJson(file, json, hasHeader)首行是否当表头(false 时键名 column_N,且首行也算数据)boolean
toJson(file, json, hasHeader, recordNumber)是否额外加一列行号 recordset_numberboolean
toCSV(json, file)JSON 数组 → CSV 文件boolean
ToXLSX(uo_json opt)按 opt 描述把 CSV 合成 xlsx(本文实测见第 9 节)boolean
ds2csv(datastore, file)DataStore 一键导出,表头取列名boolean
dw2csv(datawindow, file, usedisplayvalue)DataWindow 导出,表头取列标题 _t.Text,可选显示值boolean
opt 的约定结构(源码注释):
{
"csv":{ "file": "demo1.csv", "name": "csv1" },
"xlsx": { "name": "demo.xlsx" }
}
多文件时 csv 写成对象数组即可。csv 的 name 即 xlsx 里的工作表名。
四、写 CSV:Open → Write → Close 三段式
前置:工程 liblist 已加入 PBIDEA 的 websuite.pbl,运行目录已放 PbIdea.dll。
步骤:1) 新建窗口 w_csv_demo,放一个按钮 cb_1;2) 把下面代码贴进 cb_1 的 clicked 事件;3) 运行后点按钮,程序目录下得到 demo1.csv,并弹窗显示写出结果与文件字节数。
// 示例输入:要写出的 CSV 文件名(相对当前工作目录)
string ls_file
ls_file = 'demo1.csv'
uo_csv lcsv
any la_row[]
boolean lb_ok
long ll_size
int li_f
blob lb_data
string ls_msg
lcsv = create uo_csv
lb_ok = lcsv.Open(ls_file)
// 表头行:用 any 数组整行写入
la_row = '姓名'
la_row = '部门'
la_row = '金额'
lcsv.Write(la_row[])
// 数据行一
la_row = '张三'
la_row = '财务部'
la_row = '1234.56'
lcsv.Write(la_row[])
// 数据行二:变参写法,一行几个值就写几个,不必先凑数组
lcsv.Write('李四', '技术部', '8800')
lcsv.Close()
// 读回文件长度,确认真的落盘了
li_f = FileOpen(ls_file, StreamMode!, Read!, LockRead!)
FileReadEx(li_f, lb_data)
FileClose(li_f)
ll_size = Len(lb_data)
ls_msg = 'Open=' + String(lb_ok) + '~r~n文件字节数=' + String(ll_size)
MessageBox('写出结果', ls_msg)
destroy lcsv
实测输出:Open=true、文件 127 字节(四条记录的完整版)。文件内容(十六进制实测,EF BB BF 是 UTF-8 BOM):
EF BB BF 22 E5 A7 93 E5 90 8D 22 2C ... "姓名","部门","金额"
"张三","财务部","1234.56"
"李四","含,逗号部","88"
"王五","含"引号"部","9.5"
三个一眼可见的事实:写了 BOM、每个字段都加了双引号、字段里的逗号和双引号没有被转义。这三条后面各自对应一个坑。
五、读 CSV:toJson 的三个重载
前置:demo1.csv 已由上一节生成(UTF-8 + BOM),首行是表头。
步骤:把下面代码贴进另一个按钮 cb_2 的 clicked 事件,运行后弹窗显示解析条数与第一行内容。
// 示例输入:要读取的 CSV 文件名
string ls_file
ls_file = 'demo1.csv'
uo_csv lcsv
uo_json lj
uo_json lrow
boolean lb_ok
long ll_n
string ls_msg
lcsv = create uo_csv
lj = create uo_json
// hasHeader 默认 true:首行作表头,键名就是表头文字
lb_ok = lcsv.toJson(ls_file, lj)
ll_n = lj.ArrCount()
// 取第 1 条记录(索引从 1 开始)
lj.Get(1, lrow)
ls_msg = 'toJson=' + String(lb_ok) + '~r~n条数=' + String(ll_n)
ls_msg = ls_msg + '~r~n第一行=' + lrow.GetString('姓名')
ls_msg = ls_msg + '/' + lrow.GetString('部门')
ls_msg = ls_msg + '/' + lrow.GetString('金额')
MessageBox('CSV 读回', ls_msg)
destroy lj
destroy lcsv
实测输出:toJson=true 条数=3、第一行=张三|财务部|1234.56(表头行不计入条数)。
5.1 hasHeader=false 与 recordNumber
前置:同上一节;下面代码演示另外两个重载。
// 示例输入:要读取的 CSV 文件名
string ls_file
ls_file = 'demo1.csv'
uo_csv lcsv
uo_json lj_nohead
uo_json lj_num
uo_json lrow
string ls_keys[]
string ls_msg
lcsv = create uo_csv
// hasHeader=false:不认表头,键名自动编号 column_1..N,且表头行也算一条数据
lj_nohead = create uo_json
lcsv.toJson(ls_file, lj_nohead, false)
lj_nohead.Get(1, lrow)
lrow.GetKeys(ls_keys)
ls_msg = 'hasHeader=false 首个键=' + ls_keys
ls_msg = ls_msg + ' 条数=' + String(lj_nohead.ArrCount())
// recordNumber=true:额外加一列行号
lj_num = create uo_json
lcsv.toJson(ls_file, lj_num, true, true)
lj_num.Get(1, lrow)
lrow.GetKeys(ls_keys)
ls_msg = ls_msg + '~r~nrecordNumber 首个键=' + ls_keys
MessageBox('两个读取重载', ls_msg)
destroy lj_num
destroy lj_nohead
destroy lcsv
实测输出:
hasHeader=false 首个键=column_1,string 条数=4
recordNumber 首个键=recordset_number,number
注意 Count 从 3 变成 4——hasHeader=false 时表头行也当数据处理。GetKeys 返回的是 "键名,类型" 形式的字符串,取键名要自己按逗号切。
六、写 CSV 的两个开关:htmlEncode 与 double_quote
这是 Open 的第二、第三参数,也是最容易望文生义的地方。实测对照(写入值依次为 含,逗号 / 含"引号" / 普通):
Open 写法实测产出的字节肉眼结果
Open(f) / Open(f, false)EFBBBF + "含,逗号","含"引号"","普通"带 BOM、每格加引号
Open(f, false, false)EFBBBF + 含,逗号,含"引号",普通带 BOM、不加引号
Open(f, false, true)EFBBBF + "含,逗号","含"引号"","普通"带 BOM、每格加引号
Open(f, true)EFBBBF + "<b>张三</b>","a&b","x"带 BOM 且做 HTML 转义
结论:
[*]double_quote 只控制 “要不要给每个字段套引号”,与“转义”毫无关系;
[*]无论哪种写法,字段内部的逗号和双引号都不会被转义(标准 CSV 应把 含"引号" 写成 "含""引号""");
[*]htmlEncode=true 的用途很明确:CSV 内容要贴进网页表格 / 富文本时先转义,避免被当成标签吃掉。
七、JSON 回写 CSV:toCSV
toCSV(json, file) 是 toJson 的逆操作,输入一个 uo_json 数组(元素为对象),输出 CSV 文件:
// 示例输入:源 CSV 与目标 CSV
string ls_src
string ls_dst
ls_src = 'demo1.csv'
ls_dst = 'demo_out.csv'
uo_csv lcsv
uo_json lj
boolean lb_ok
string ls_msg
lcsv = create uo_csv
lj = create uo_json
// 先把源 CSV 读成 JSON,再原样写回另一个文件
lcsv.toJson(ls_src, lj)
lb_ok = lcsv.toCSV(lj, ls_dst)
ls_msg = 'toCSV=' + String(lb_ok)
MessageBox('JSON 回写 CSV', ls_msg)
destroy lj
destroy lcsv
实测:toCSV=true,产出 137 字节,首字节就是 22(引号)——没有 BOM。这点和 Open/Write 产出带 BOM 不一样,务必区分。
八、DataWindow / DataStore 一条龙:ds2csv 与 dw2csv
前置:已连接 SQL Server 2008 R2(本文用 127.0.0.1,18433,sa 账号);工程 liblist 含 websuite.pbl。
步骤:1) 下面代码自建测试表 t_csv41 并插入 3 行;2) 用 SyntaxFromSQL 现场生成 DataWindow 语法并 Create 出 DataStore;3) 分别用 ds2csv 和原生 SaveAs(CSV!) 导出,弹窗显示两种方式的文件内容;4) 结束前自动删表。
// 示例输入:数据库连接信息
string ls_server
ls_server = '127.0.0.1,18433'
// 示例输入:登录账号与口令(本地测试库)
string ls_user
string ls_pwd
ls_user = 'sa'
ls_pwd = 'Zkkt@667788'
// 示例输入:导出文件名
string ls_out1
string ls_out2
ls_out1 = 'demo_ds.csv'
ls_out2 = 'demo_native.csv'
uo_csv lcsv
string ls_sql
string ls_err
string ls_syn
datastore lds
long ll_rows
long ll_n
boolean lb_ok
int li_f
blob lb1
blob lb2
string ls_msg
SQLCA.DBMS = 'SNC SQL Native Client(OLE DB)'
SQLCA.LogId = ls_user
SQLCA.LogPass = ls_pwd
SQLCA.ServerName = ls_server
SQLCA.Database = 'master'
SQLCA.AutoCommit = true
SQLCA.DBParm = "Provider='SQLNCLI10',DataSource='127.0.0.1,18433'"
CONNECT USING SQLCA;
// 自建测试数据
EXECUTE IMMEDIATE "if object_id('dbo.t_csv41') is not null drop table dbo.t_csv41" USING SQLCA;
ls_sql = "create table dbo.t_csv41(id int identity(1,1) primary key, emp_name nvarchar(50), dept nvarchar(50), amount decimal(12,2))"
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
ls_sql = "insert into dbo.t_csv41(emp_name,dept,amount) values('张三','财务部',1234.56)"
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
ls_sql = "insert into dbo.t_csv41(emp_name,dept,amount) values('李四','技术部',8800)"
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
ls_sql = "insert into dbo.t_csv41(emp_name,dept,amount) values('王五','含,逗号部',99.9)"
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
// 现场生成 DataStore
ls_sql = 'select id, emp_name, dept, amount from dbo.t_csv41 order by id'
ls_syn = SQLCA.SyntaxFromSQL(ls_sql, 'style(type=grid)', ls_err)
lds = create datastore
lds.Create(ls_syn, ls_err)
lds.SetTransObject(SQLCA)
ll_rows = lds.Retrieve()
lcsv = create uo_csv
lb_ok = lcsv.ds2csv(lds, ls_out1)
ll_n = lds.SaveAs(ls_out2, CSV!, true)
li_f = FileOpen(ls_out1, StreamMode!, Read!, LockRead!)
FileReadEx(li_f, lb1)
FileClose(li_f)
li_f = FileOpen(ls_out2, StreamMode!, Read!, LockRead!)
FileReadEx(li_f, lb2)
FileClose(li_f)
ls_msg = '行数=' + String(ll_rows) + ' ds2csv=' + String(lb_ok) + ' SaveAs=' + String(ll_n)
ls_msg = ls_msg + '~r~n--- uo_csv.ds2csv(UTF-8 解码后)---~r~n' + FromUTF8(lb1)
ls_msg = ls_msg + '~r~n--- 原生 SaveAs(CSV!)(ANSI 解码后)---~r~n' + String(lb2, EncodingANSI!)
MessageBox('两种导出对比', ls_msg)
destroy lds
destroy lcsv
EXECUTE IMMEDIATE "drop table dbo.t_csv41" USING SQLCA;
DISCONNECT USING SQLCA;
实测结果(文件字节级对照,这是全文最有价值的一张表):
对比项uo_csv.ds2csvdw.SaveAs(CSV!, TRUE)
编码UTF-8 + BOM(EF BB BF 开头)ANSI/GBK(中文 D5 C5 C8 FD 直接落盘)
引号每个字段都加只在必要时加(仅 含,逗号部 被包住)
转义不转义内部逗号/引号同样不转义(但靠“按需加引号”规避了逗号)
decimal 精度8800.00 → 8800、99.90 → 99.98800.00、99.90 原样保留
表头列名(id,emp_name,dept,amount)列名(同上)
依赖需要 PbIdea.dll纯 PB,无需任何外部库
需要 DataWindow是(ds2csv)/ 是(dw2csv)是
所以选型很直白:要 UTF-8 给外部系统 → uo_csv;要保精度、给本地 Excel 直接双击打开 → SaveAs。
dw2csv(datawindow, file, usedisplayvalue) 与 ds2csv 的差异是:表头取列标题 _t.Text(取不到才回退列名),且 usedisplayvalue=true 时会用 evaluate('lookupdisplay(xxx)', 行号) 取下拉框/复选框的显示值而不是存储值。它需要可视的 DataWindow 控件,本文的无界面环境无法构造,故此条为源码级核对、未实机运行。
九、ToXLSX:本环境实测跑不通,别依赖
ToXLSX(opt) 听起来很香——CSV 直接变 Excel。但我在 PB 12.5 实机上按源码注释的两种 opt 结构(对象式、数组式)、相对路径与绝对路径、并把 plugin\PbideaExcel.dll 同时放到 plugin\ 和运行目录,四种组合全部返回 false,未生成任何 xlsx 文件。
结论:该功能依赖的运行条件在当前 PBIDEA 版本下未能满足,生产上不要依赖它。真要出 xlsx,走 PBIDEA 的 uo_xlsx(libxl 引擎,不装 Office 也能写),或者干脆 SaveAs(Excel!)。这一条是负面结论,但同样是我实机验证出来的——比照抄文档靠谱。
十、编码:三条必须记住的规则
这是本文最容易踩坑、也最容易被忽略的部分。
规则一:uo_csv 写出来的永远是 UTF-8,Open/Write 带 BOM,toCSV 不带 BOM。
规则二:String(blob) 不是通用解码器——它按 UTF-16LE 解释字节。 实测证据:
目标写法结果
GBK 文件String(lb)乱码(摩攬灭湟浡?敤瑰…,即 id 两字节被当 UTF-16)
GBK 文件String(lb, EncodingANSI!)正确 id,emp_name,dept,amo
GBK 文件FromUTF8(lb)正确(BOM 无关时同样能过)
UTF-8 文件String(lb)乱码
UTF-8 文件String(lb, EncodingUTF8!)正确 "姓名","部门","金额"
UTF-8 文件FromUTF8(lb)正确,且会自动吃掉 BOM
前置:demo1.csv 为 uo_csv 产出的 UTF-8 文件;下面代码演示三种解码方式。
步骤:贴进按钮 clicked,运行后弹窗对比 String(blob) / String(blob, EncodingUTF8!) / FromUTF8(blob)。
// 示例输入:要读取的 UTF-8 CSV(由 uo_csv 产出,带 BOM)
string ls_file
ls_file = 'demo1.csv'
int li_f
blob lb_data
string ls_msg
li_f = FileOpen(ls_file, StreamMode!, Read!, LockRead!)
FileReadEx(li_f, lb_data)
FileClose(li_f)
ls_msg = 'String(blob) = ' + Left(String(lb_data), 16)
ls_msg = ls_msg + '~r~nString(blob,UTF8)= ' + Left(String(lb_data, EncodingUTF8!), 16)
ls_msg = ls_msg + '~r~nFromUTF8(blob) = ' + Left(FromUTF8(lb_data), 16)
MessageBox('三种解码方式对比', ls_msg)
规则三:读进来的东西全是字符串。 无论源里是 1234.56 还是 88,toJson 得到的都是带引号的字符串(实测 [{"column_1":"张三","column_2":"1234.56","column_3":"88","column_4":"0"}])。要做计算请自己 Dec() / Long() 转,别指望它帮你分类型。
十一、14 条实测坑清单
[*]不 Close() 就不落盘。 实测 Write 之后立刻查文件大小是 0 字节,Close() 之后才是 22 字节。异常路径里一定要把 Close() 放进 finally。
[*]同名文件再 Open 是覆盖,不是追加。 实测第二次 Open 写“第二行”后文件只剩 16 字节。要追加只能自己先读出来合并,或者写不同文件名。
[*]字段里的逗号必然导致读回错位。 这是最狠的一条:toJson 是纯按逗号切分再去掉首尾引号,它不认引号边界。实测 "含,逗号部" 读回来变成 部门=含 / 金额=逗号部,还凭空多出一列 column_4=88。→ 若 CSV 还要自己读回,写入前把逗号替换成全角逗号或分号。
[*]双引号同样不转义。 写入 含"引号" 得到 "含"引号"",标准解析器会读错;PBIDEA 自己的 toJson 反而读得对(因为它不认引号边界,歪打正着)。
[*]hasHeader 默认 true,改成 false 条数会 +1。 表头行被当数据,键名变 column_1..N。
[*]recordNumber 加的是 recordset_number,从 1 开始。 想自己编号也可以,但别忘了它多占一列会影响按索引取值。
[*]读出来全是字符串。 见规则三。
[*]decimal 精度会掉。 8800.00 写成 8800、99.90 写成 99.9;SaveAs(CSV!) 则保留。导出金额给财务时优先 SaveAs,或自己用 String(ld_amt, '0.00') 先格式化。
[*]列数参差时静默变形。 表头 3 列,某行只写 2 个值 → 缺的列直接不出现在 JSON 里;某行写 4 个 → 自动补 column_4。
[*]全空行被整行丢弃。 实测写 , , 一行后再读,条数只有 1 条(空行没了)。
[*]文件不存在时 toJson 返回 false。 有返回值可判,别省略判断。
[*]ToXLSX 本环境返回 false。 见第 9 节。
[*]Write(...) 变参只有外部函数声明支持。 PB 自定义函数写 ... 会报 C0031,但 uo_csv.Write 是 DLL 声明,可以放心用变参。
[*]对象必须 create。 uo_csv 不是 autoinstantiate,裸调直接 Null object reference。
十二、方案对比与选型
场景推荐理由
导出给外部系统 / 网页 / Mac 用户uo_csv(Open/Write/Close)UTF-8 + BOM,Excel 与在线工具都认
导出给本地财务,要求金额两位小数dw.SaveAs(CSV!, TRUE)ANSI 无 BOM、按需加引号、decimal 保精度
读一个来源不明的 CSVuo_csv.toJsonGBK 与 UTF-8(含无 BOM)实测都能正确识别中文
DataWindow 数据一键导出ds2csv / dw2csv表头自动取列名/列标题,dw2csv 还能取显示值
内存结果集(无 DataWindow)导出uo_csv + any[]不依赖 DataWindow,随算随写
要出 xlsxuo_xlsx 或 SaveAs(Excel!)uo_csv.ToXLSX 实测不可用
手工拼串(ls = ls + ',' + ...)不推荐引号/转义/编码三重地雷,出问题难查
十三、扩展点
[*]批量分片导出:Open 一次、Write 循环、Close 一次,百万行也不会把内存撑爆,比先攒字符串再 FileWrite 稳得多;
[*]与 uo_json 打通:toJson → 用 uo_json 做过滤/改名/聚合 → toCSV 回写,等于白捡一个“CSV 变换管道”;
[*]与 uo_zip 组合:导出一批 CSV 后 uo_zip.Open → Add → Close 打包,再交给 uo_ftp / uo_sftp 上传,一条流水线全在 PB 里完成;
[*]与 uo_database 组合:uo_database 查出 uo_recordset 后逐行 Write,可以完全绕开 DataWindow(注意 uo_database 占位符是 :1 / :名字,不是 ?)。
小结
[*]uo_csv 的三个动作:写文件 Open → Write → Close、读文件 toJson、回写 toCSV;
[*]写入恒为 UTF-8(Open/Write 带 BOM,toCSV 不带),读取兼容 GBK 与 UTF-8;
[*]引号由 double_quote 控制(默认加),但逗号与双引号永不转义;
[*]最需要警惕的是逗号错位坑:toJson 不认引号边界,含逗号字段读回必然串列;
[*]金额精度要求高就用 SaveAs(CSV!),ToXLSX 现阶段别用;
[*]读回来的值全是字符串,用前自己转类型。
实机验证说明:本文全部代码在 PB 12.5 + PBIDEA 最新版实机跑通——pypower pbl import 0 错误、ORCA rebuild --type full 0 错误、PBVM .pbtest 运行 PASS(20/20 硬断言)、check_pb125.py 0 错 0 提醒、check_enc.py bad=0;数据库部分在 SQL Server 2008 R2(10.50.6000.34)上完成建表/插入/取数/清理全流程。唯一未实机运行的是 dw2csv(需可视 DataWindow 控件,无界面环境无法构造),已在文中标注为源码级核对。 PB10 兼容演示包已上传,附件 csvdemo_pb10.zip(70.6 KB):
一、包里有什么
[*]csvdemo.pbl / csvdemo.pbt —— PB10 工程与库(10 个对象,PB10 全量编译 0 错误)
[*]src\ —— 全部可导入导出源码(GBK / CRLF / 无 BOM):uo_csv、uo_json、uo_map、uo_xml、uo_xml_node、uo_xml_attribute、全局函数 format、fromutf8,以及演示对象 nvo_csv_demo
[*]tests\ —— 实机断言脚本 csvdemo.pbtest(20 条硬断言)
[*]evidence\ —— PB12.5 实机运行输出与产出 CSV 的字节级十六进制对照
[*]使用说明.txt —— 环境、部署、用法、排错、实机验证情况
二、环境要求
[*]PowerBuilder 10.0(或更高版本;高版本打开后升级编译即可)
[*]PBIDEA 运行库 PbIdea.dll,需与 PB 大版本配套,放到程序目录或 PATH
[*]Windows 7 及以上
[*]数据库:本包的「写 CSV / 读 CSV」两段不依赖数据库;只有 DataStore 示例要连库
三、操作细节步骤
[*]解压到任意目录,例如 D:\pbidea_csv\
[*]用 PB10 打开 csvdemo.pbt(或把 csvdemo.pbl 加进现有工程的 liblist)
[*]把 PbIdea.dll 复制到运行目录
[*]Full Rebuild —— 预期 0 Error(websuite 自带的 C0210 警告可忽略)
[*]在窗口按钮 clicked 事件里写:
nvo_csv_demo lnv
string ls_out
lnv = create nvo_csv_demo
ls_out = lnv.of_run()
destroy lnv
MessageBox('uo_csv 演示', ls_out)
[*]运行后当前目录会生成 11 个 CSV:demo1 / demo_out / demo_html / demo_dq0 / demo_dq1 / demo_gbk / demo_u8 / demo_buf / demo_again / demo_ds / demo_native,逐个打开即可对照文中每个结论
四、SQL 说明(仅 DataStore 示例)
包内源码用的是 PB12.5 的 SNC 连接串,PB10 不支持 SNC,请改成:
SQLCA.DBMS = 'MSS Microsoft SQL Server'
SQLCA.Database = 'master'
SQLCA.ServerName = '127.0.0.1,18433'
SQLCA.LogId = 'sa'
SQLCA.LogPass = '你的口令'
SQLCA.AutoCommit = true
CONNECT USING SQLCA;
建表(脚本会自动建、自动删,无需手工准备):
create table dbo.t_csv41(
id int identity(1,1) primary key,
emp_name nvarchar(50),
dept nvarchar(50),
amount decimal(12,2)
);
insert into dbo.t_csv41(emp_name,dept,amount) values('张三','财务部',1234.56);
insert into dbo.t_csv41(emp_name,dept,amount) values('李四','技术部',8800);
insert into dbo.t_csv41(emp_name,dept,amount) values('王五','含,逗号部',99.9);
五、实机验证情况(如实标注)
[*]PB12.5 + PBIDEA 最新版:import 0 错误、ORCA rebuild --type full 0 错误、PBVM .pbtest 运行 PASS(20/20 硬断言)、check_pb125 0 错 0 提醒、check_enc bad=0
[*]数据库部分在 SQL Server 2008 R2(10.50.6000.34)上完成建表 / 插入 / 取数 / 清理全流程
[*]本 PB10 包:已用 pypower --pb-version 100 全量编译 0 错误,并已用 pbl list 校验对象齐全(10 个,防循环依赖静默丢失)
[*]未在 PB10 真机运行(本机无 PB10 运行时),PB10 下请自行跑一次确认
[*]文中 dw2csv 需可视 DataWindow 控件,无界面环境无法构造,属源码级核对、未实机运行
六、三个最容易翻车的点
[*]不调用 Close() 文件就是 0 字节,异常路径务必把 Close() 放进 finally
[*]字段里有逗号,用 toJson 读回必然串列(它不认引号边界)——要自己读回就先把逗号换成全角逗号
[*]写出来的文件是 UTF-8(Open/Write 带 BOM),读回中文要用 FromUTF8(blob) 或 String(blob, EncodingUTF8!),直接 String(blob) 会按 UTF-16LE 解释而乱码
页:
[1]