officegen-2
Version:
Office Open XML Generator using Node.js streams. Supporting Microsoft Office 2007 and later Word (docx), PowerPoint (pptx,ppsx) and Excel (xlsx). This module is for all frameworks and environments. No need for any commandline tool - this module is doing everything inside it.
477 lines (421 loc) • 17.8 kB
text/coffeescript
###
MS Excel 2007 Creater v0.0.1
Author : chuanyi.zheng@gmail.com
History: 2012/11/07 first created
###
fs = require 'fs'
path = require 'path'
exec = require 'child_process'
xml = require 'xmlbuilder'
existsSync = fs.existsSync || path.existsSync
tool =
i2a : (i) ->
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.charAt(i-1)
copy : (origin, target) ->
if existsSync(origin)
fs.mkdirSync(target, 755) if not existsSync(target)
files = fs.readdirSync(origin)
if files
for f in files
oCur = origin + '/' + f
tCur = target + '/' + f
s = fs.statSync(oCur)
if s.isFile()
fs.writeFileSync(tCur,fs.readFileSync(oCur,''),'')
else
if s.isDirectory()
tool.copy oCur, tCur
opt =
tmpl_path : __dirname
class ContentTypes
constructor: ()->
toxml:()->
types = xml.create('Types',{version:'1.0',encoding:'UTF-8',standalone:true})
types.att('xmlns','http://schemas.openxmlformats.org/package/2006/content-types')
types.ele('Override',{PartName:'/xl/theme/theme1.xml',ContentType:'application/vnd.openxmlformats-officedocument.theme+xml'})
types.ele('Override',{PartName:'/xl/styles.xml',ContentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'})
types.ele('Default',{Extension:'rels',ContentType:'application/vnd.openxmlformats-package.relationships+xml'})
types.ele('Default',{Extension:'xml',ContentType:'application/xml'})
types.ele('Override',{PartName:'/xl/workbook.xml',ContentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'})
types.ele('Override',{PartName:'/docProps/app.xml',ContentType:'application/vnd.openxmlformats-officedocument.extended-properties+xml'})
for i in [1...sheets.length]
types.ele('Override',{PartName:'/xl/worksheets/sheet'+i+'.xml',ContentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'})
types.ele('Override',{PartName:'/xl/sharedStrings.xml',ContentType:'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'})
types.ele('Override',{PartName:'/docProps/core.xml',ContentType:'application/vnd.openxmlformats-package.core-properties+xml'})
return types.end()
class DocPropsApp
constructor: ()->
toxml: ()->
props = xml.create('Properties',{version:'1.0',encoding:'UTF-8',standalone:true})
props.att('xmlns','http://schemas.openxmlformats.org/officeDocument/2006/extended-properties')
props.att('xmlns:vt','http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes')
props.ele('Application','Microsoft Excel')
props.ele('DocSecurity','0')
props.ele('ScaleCrop','false')
tmp = props.ele('HeadingPairs').ele('vt:vector',{size:2,baseType:'variant'})
tmp.ele('vt:variant').ele('vt:lpstr','工作表')
tmp.ele('vt:variant').ele('vt:i4',''+.sheets.length)
tmp = props.ele('TitlesOfParts').ele('vt:vector',{size:.sheets.length,baseType:'lpstr'})
for i in [1...sheets.length]
tmp.ele('vt:lpstr',.sheets[i-1].name)
props.ele('Company')
props.ele('LinksUpToDate','false')
props.ele('SharedDoc','false')
props.ele('HyperlinksChanged','false')
props.ele('AppVersion','12.0000')
return props.end()
class XlWorkbook
constructor: ()->
toxml: ()->
wb = xml.create('workbook',{version:'1.0',encoding:'UTF-8',standalone:true})
wb.att('xmlns','http://schemas.openxmlformats.org/spreadsheetml/2006/main')
wb.att('xmlns:r','http://schemas.openxmlformats.org/officeDocument/2006/relationships')
wb.ele('fileVersion ',{appName:'xl',lastEdited:'4',lowestEdited:'4',rupBuild:'4505'})
wb.ele('workbookPr',{filterPrivacy:'1',defaultThemeVersion:'124226'})
wb.ele('bookViews').ele('workbookView ',{xWindow:'0',yWindow:'90',windowWidth:'19200',windowHeight:'11640'})
tmp = wb.ele('sheets')
for i in [1...sheets.length]
tmp.ele('sheet',{name:.sheets[i-1].name,sheetId:''+i,'r:id':'rId'+i})
wb.ele('calcPr',{calcId:'124519'})
return wb.end()
class XlRels
constructor: ()->
toxml: ()->
rs = xml.create('Relationships',{version:'1.0',encoding:'UTF-8',standalone:true})
rs.att('xmlns','http://schemas.openxmlformats.org/package/2006/relationships')
for i in [1...sheets.length]
rs.ele('Relationship',{Id:'rId'+i,Type:'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',Target:'worksheets/sheet'+i+'.xml'})
rs.ele('Relationship',{Id:'rId'+(.sheets.length+1),Type:'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',Target:'theme/theme1.xml'})
rs.ele('Relationship',{Id:'rId'+(.sheets.length+2),Type:'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',Target:'styles.xml'})
rs.ele('Relationship',{Id:'rId'+(.sheets.length+3),Type:'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',Target:'sharedStrings.xml'})
return rs.end()
class SharedStrings
constructor: ()->
= {}
= []
str2id: (s)->
id = [s]
if id
return id
else
.push s
[s] = .length
return .length
toxml: ()->
sst = xml.create('sst',{version:'1.0',encoding:'UTF-8',standalone:true})
sst.att('xmlns','http://schemas.openxmlformats.org/spreadsheetml/2006/main')
sst.att('count',''+.length)
sst.att('uniqueCount',''+.length)
for i in [0....length]
si = sst.ele('si')
si.ele('t',[i])
si.ele('phoneticPr',{fontId:1,type:'noConversion'})
return sst.end()
class Sheet
constructor: (, , , ) ->
= {}
for i in [1..]
[i] = {}
for j in [1..]
[i][j] = {v:0, dataType: 'string'}
= []
= []
= {}
= {}
set: (col, row, value) ->
if (typeof value) is "string"
#console.log "Found string " + value
[row][col].v = .ss.str2id(''+value) if value? and value isnt ''
#console.log "id=" + [row][col].v
[row][col].dataType = 'string'
else
[row][col].v = value
[row][col].dataType = 'number'
merge: (from_cell, to_cell) ->
.push({from:from_cell, to:to_cell})
width: (col, wd) ->
.push {c:col,cw:wd}
height: (row, ht) ->
[row] = ht
font: (col, row, font_s)->
['font_'+col+'_'+row] = .st.font2id(font_s)
fill: (col, row, fill_s)->
['fill_'+col+'_'+row] = .st.fill2id(fill_s)
border: (col, row, bder_s)->
['bder_'+col+'_'+row] = .st.bder2id(bder_s)
align: (col, row, align_s)->
['algn_'+col+'_'+row] = align_s
valign: (col, row, valign_s)->
['valgn_'+col+'_'+row] = valign_s
rotate: (col, row, textRotation)->
['rotate_'+col+'_'+row] = textRotation
wrap: (col, row, wrap_s)->
['wrap_'+col+'_'+row] = wrap_s
style_id: (col, row) ->
inx = '_'+col+'_'+row
style = {
font_id:['font'+inx],
fill_id:['fill'+inx],
bder_id:['bder'+inx],
align:['algn'+inx],
valign:['valgn'+inx],
rotate:['rotate'+inx],
wrap:['wrap'+inx]
}
id = .st.style2id(style)
return id
toxml: () ->
ws = xml.create('worksheet',
{version:'1.0',encoding:'UTF-8',standalone:true})
ws.att('xmlns','http://schemas.openxmlformats.org/spreadsheetml/2006/main')
ws.att('xmlns:r',
'http://schemas.openxmlformats.org/officeDocument/2006/relationships')
ws.ele('dimension',{ref:'A1'})
ws.ele('sheetViews').ele('sheetView',{workbookViewId:'0'})
ws.ele('sheetFormatPr',{defaultRowHeight:'13.5'})
if .length > 0
cols = ws.ele('cols')
for cw in
cols.ele('col',{min:''+cw.c,max:''+cw.c,width:cw.cw,customWidth:'1'})
sd = ws.ele('sheetData')
for i in [1..]
r = sd.ele('row',{r:''+i,spans:'1:'+})
ht = [i]
if ht
r.att('ht',ht)
r.att('customHeight','1')
for j in [1..]
ix = [i][j]
sid =
if (ix.v isnt 0) or (sid isnt 1)
c = r.ele('c',{r:''+tool.i2a(j)+i})
c.att('s',''+(sid-1)) if sid isnt 1
if ix.v isnt 0
# comment out for test generating number
#console.log [i][j]
if ix.dataType is "string"
c.att('t','s')
c.ele('v', '' + (ix.v-1))
else if ix.dataType is "number"
c.ele('v', '' + ix.v)
else
c.ele('v', '' + ix.v)
if .length > 0
mc = ws.ele('mergeCells',{count:.length})
for m in
mc.ele('mergeCell',
{ref:(''+tool.i2a(m.from.col)+m.from.row+':'+tool.i2a(m.to.col)+m.to.row)})
ws.ele('phoneticPr',{fontId:'1',type:'noConversion'})
ws.ele('pageMargins',{left:'0.7',right:'0.7',top:'0.75',bottom:'0.75',header:'0.3',footer:'0.3'})
ws.ele('pageSetup',{paperSize:'9',orientation:'portrait',horizontalDpi:'200',verticalDpi:'200'})
return ws.end()
class Style
constructor: ()->
= {}
= [] # font style
= [] # fill style
= [] # border style
= [] # cell style<ref-font,ref-fill,ref-border,align>
with_default:()->
=
=
=
= '-'
= '-'
= '-'
= '-'
=
font2id: (font)->
font or= {}
font.bold or= '-'
font.iter or= '-'
font.sz or= '11'
font.color or= '-'
font.name or= 'Arial'
font.scheme or='minor'
font.family or= '2'
k = 'font_'+font.bold+font.iter+font.sz+font.color+font.name+font.scheme+font.family
id = [k]
if id
return id
else
.push font
[k] = .length
return .length
fill2id: (fill)->
fill or= {}
fill.type or= 'none'
fill.bgColor or= '-'
fill.fgColor or= '-'
k = 'fill_' + fill.type + fill.bgColor + fill.fgColor
id = [k]
if id
return id
else
.push fill
[k] = .length
return .length
bder2id: (bder)->
bder or= {}
bder.left or= '-'
bder.right or= '-'
bder.top or= '-'
bder.bottom or= '-'
k = 'bder_'+bder.left+'_'+bder.right+'_'+bder.top+'_'+bder.bottom
id = [k]
if id
return id
else
.push bder
[k] = .length
return .length
style2id:(style)->
style.align or=
style.valign or=
style.rotate or=
style.wrap or=
style.font_id or=
style.fill_id or=
style.bder_id or=
k = 's_' + style.font_id + '_' + style.fill_id + '_' + style.bder_id + '_' + style.align + '_' + style.valign + '_' + style.wrap + '_' + style.rotate
id = [k]
if id
return id
else
.push style
[k] = .length
return .length
toxml: ()->
ss = xml.create('styleSheet',{version:'1.0',encoding:'UTF-8',standalone:true})
ss.att('xmlns','http://schemas.openxmlformats.org/spreadsheetml/2006/main')
fonts = ss.ele('fonts',{count:.length})
for o in
e = fonts.ele('font')
e.ele('b') if o.bold isnt '-'
e.ele('i') if o.iter isnt '-'
e.ele('sz',{val:o.sz})
e.ele('color',{theme:o.color}) if o.color isnt '-'
e.ele('name',{val:o.name})
e.ele('family',{val:o.family})
e.ele('charset',{val:'134'})
e.ele('scheme',{val:'minor'}) if o.scheme isnt '-'
fills = ss.ele('fills',{count:.length})
for o in
e = fills.ele('fill')
es = e.ele('patternFill',{patternType:o.type})
es.ele('fgColor',{theme:'8',tint:'0.79998168889431442'}) if o.fgColor isnt '-'
es.ele('bgColor',{indexed:o.bgColor}) if o.bgColor isnt '-'
bders = ss.ele('borders',{count:.length})
for o in
e = bders.ele('border')
if o.left isnt '-' then e.ele('left',{style:o.left}).ele('color',{auto:'1'}) else e.ele('left')
if o.right isnt '-' then e.ele('right',{style:o.right}).ele('color',{auto:'1'}) else e.ele('right')
if o.top isnt '-' then e.ele('top',{style:o.top}).ele('color',{auto:'1'}) else e.ele('top')
if o.bottom isnt '-' then e.ele('bottom',{style:o.bottom}).ele('color',{auto:'1'}) else e.ele('bottom')
e.ele('diagonal')
ss.ele('cellStyleXfs',{count:'1'}).ele('xf',{numFmtId:'0',fontId:'0',fillId:'0',borderId:'0'}).ele('alignment',{vertical:'center'})
cs = ss.ele('cellXfs',{count:.length})
for o in
e = cs.ele('xf',{numFmtId:'0',fontId:(o.font_id-1),fillId:(o.fill_id-1),borderId:(o.bder_id-1),xfId:'0'})
e.att('applyFont','1') if o.font_id isnt 1
e.att('applyFill','1') if o.fill_id isnt 1
e.att('applyBorder','1') if o.bder_id isnt 1
if o.align isnt '-' or o.valign isnt '-' or o.wrap isnt '-'
e.att('applyAlignment','1')
ea = e.ele('alignment',{textRotation:(if o.rotate is '-' then '0' else o.rotate),horizontal:(if o.align is '-' then 'left' else o.align), vertical:(if o.valign is '-' then 'top' else o.valign)})
ea.att('wrapText','1') if o.wrap isnt '-'
ss.ele('cellStyles',{count:'1'}).ele('cellStyle',{name:'常规',xfId:'0',builtinId:'0'})
ss.ele('dxfs',{count:'0'})
ss.ele('tableStyles',{count:'0',defaultTableStyle:'TableStyleMedium9',defaultPivotStyle:'PivotStyleLight16'})
return ss.end()
class Workbook
constructor: (, ) ->
= ''+parseInt(Math.random()*9999999)
# create temp folder & copy template data
target = path.join(path.resolve(),)
fs.rmdirSync(target) if existsSync(target)
tool.copy (opt.tmpl_path + '/tmpl'),target
# init
= []
= new SharedStrings
= new ContentTypes(@)
= new DocPropsApp(@)
= new XlWorkbook(@)
= new XlRels(@)
= new Style(@)
createSheet: (name, cols, rows) ->
sheet = new Sheet(@,name,cols,rows)
.push sheet
return sheet
save: (cb) =>
target = path.join(path.resolve(),)
# 1 - build [Content_Types].xml
if not fs.existsSync(target)
fs.mkdirSync target, (e) ->
return
if !e or (e and e.code is 'EEXIST')
console.log path + 'created'
else
console.log(e)
fs.writeFileSync(target+'\\[Content_Types].xml',.toxml(),'utf8')
# 2 - build docProps/app.xml
if not fs.existsSync(path.join(target,'docProps'))
fs.mkdirSync path.join(target,'docProps'), (e) ->
return
if !e or (e and e.code is 'EEXIST')
console.log path + 'created'
else
console.log(e)
fs.writeFileSync(target+'\\docProps\\app.xml',.toxml(),'utf8')
# 3 - build xl/workbook.xml
if not fs.existsSync(path.join(target,'xl'))
fs.mkdirSync path.join(target,'xl'), (e) ->
return
if !e or (e and e.code is 'EEXIST')
console.log path + 'created'
else
console.log(e)
fs.writeFileSync(target+'\\xl\\workbook.xml',.toxml(),'utf8')
# 4 - build xl/sharedStrings.xml
fs.writeFileSync(target+'\\xl\\sharedStrings.xml',.toxml(),'utf8')
# 5 - build xl/_rels/workbook.xml.rels
fs.mkdirSync path.join(target,path.join('xl','_rels')), (e) ->
return
if !e or (e and e.code is 'EEXIST')
console.log path + 'created'
else
console.log(e)
fs.writeFileSync(target+'\\xl\\_rels\\workbook.xml.rels',.toxml(),'utf8')
# 6 - build xl/worksheets/sheet(1-N).xml
fs.mkdirSync path.join(target,path.join('xl', 'worksheets')), (e) ->
return
if !e or (e and e.code is 'EEXIST')
console.log path + 'created'
else
console.log(e)
for i in [0....length]
fs.writeFileSync(target+'\\xl\\worksheets\\sheet'+(i+1)+'.xml',[i].toxml(),'utf8')
# 7 - build xl/styles.xml
fs.writeFileSync(target+'\\xl\\styles.xml',.toxml(),'utf8')
# 8 - compress temp folder to target file
args = ' a -tzip "' + path.join(path.resolve(),) + '" "*"'
opts = {cwd:target}
exec.exec '"'+opt.tmpl_path+'\\tool\\7za.exe"' + args, opts, (err,stdout,stderr)->
# 9 - delete temp folder
exec.exec 'rmdir "' + target + '" /q /s',()->
console.log(err)
cb err
cancel: () ->
#target = path.join(path.resolve(),)
# delete temp folder
fs.rmdirSync target
module.exports =
createWorkbook: (fpath, fname)->
return new Workbook(fpath, fname)