WDocxSplit.mjs

import path from 'path'
import process from 'process'
import get from 'lodash-es/get.js'
import isestr from 'wsemi/src/isestr.mjs'
import isp0int from 'wsemi/src/isp0int.mjs'
import str2b64 from 'wsemi/src/str2b64.mjs'
import execProcess from 'wsemi/src/execProcess.mjs'
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
import ensureBins from './ensureBins.mjs'
import judgeSplitOutput from './judgeSplitOutput.mjs'


//fnExe, 切分器名稱(bin/manifest.json之name)
let fnExe = 'splitDocx.exe'


function isWindows() {
    return process.platform === 'win32'
}


/**
 * 依照指定字串分割Docx檔
 *
 * 分割由splitDocx.exe調用本機Microsoft Word(win32com)完成,故須於Windows且已安裝Word之機器執行。splitDocx.exe不含於npm包,首次分割時經ensureBins自GitHub(釘選提交)或環境變數W_BIN_MIRROR之鏡像取得,以bin/manifest.json之SHA-256與大小校驗後存於快取(預設為套件根之bin-cache/,可由W_BIN_CACHE改位置),之後自快取取得(亦校驗);環境變數W_BIN_OFFLINE=1時不連網。取得失敗則reject字串,內含錯誤代碼(如E_OFFLINE_MISSING、E_HASH_MISMATCH)與原因,詳見README〈外部執行檔〉
 *
 * @param {String} fpIn 輸入來源Html檔位置字串
 * @param {String} strSep 輸入內文會提供之分割字串
 * @param {String} fdOut 輸入轉出Docx檔位置字串
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Integer} [opt.padZero=0] 輸入是否補0位數整數,例如padZero=3時檔名會變成001.docx,預設0
 * @returns {Promise} 回傳Promise,resolve回傳成功訊息'ok',reject回傳錯誤訊息字串。reject之字串有下列型態:參數檢測失敗為說明字串;取不到執行檔為`[錯誤代碼] 原因`(如`[E_OFFLINE_MISSING] ...`);splitDocx.exe無法執行或離開碼非0為execProcess之訊息(如`code[N]:\n...`);splitDocx.exe執行後自報失敗(如輸入非docx、docx損毀、Word無法開啟)為`[E_SPLIT_FAILED] 原因`,原因為exe輸出之診斷(已依系統字碼頁解碼,不回顯輸入路徑,但Word之診斷可能自行含檔名),此時不會產生輸出檔(分割中途失敗時,失敗前已寫出之檔不會被清除)。splitDocx.exe不論成敗離開碼皆為0,故以其輸出判定成敗:須有`success`行且無`error:`行才resolve,空輸出或未知輸出亦視為失敗
 * @example
 *
 * import w from 'wsemi'
 * import WDocxSplit from './src/WDocxSplit.mjs'
 * //import WDocxSplit from 'w-docx-split/src/WDocxSplit.mjs'
 * //import WDocxSplit from 'w-docx-split'
 *
 * async function test() {
 *
 *     let fpIn = `./test/docin.docx`
 *     let strSep = '[systag:sepline]'
 *     let fdOut = `./test/out`
 *     let opt = {
 *         padZero: 3,
 *     }
 *
 *     let r = await WDocxSplit(fpIn, strSep, fdOut, opt)
 *     console.log(r)
 *     // => ok
 *
 *     w.fsDeleteFolder(fdOut)
 *
 * }
 * test()
 *     .catch((err) => {
 *         console.log('catch', err)
 *     })
 *
 */
async function WDocxSplit(fpIn, strSep, fdOut, opt = {}) {
    let errTemp = null

    //isWindows
    if (!isWindows()) {
        return Promise.reject('operating system is not windows')
    }

    //check
    if (!fsIsFile(fpIn)) {
        return Promise.reject(`fpIn[${fpIn}] does not exist`)
    }

    //check
    if (!isestr(strSep)) {
        return Promise.reject(`strSep[${strSep}] is not an effective string`)
    }

    //check
    if (!fsIsFolder(fdOut)) {
        fsCreateFolder(fdOut)
    }

    //padZero
    let padZero = get(opt, 'padZero')
    if (!isp0int(padZero)) {
        padZero = 0
    }

    //轉絕對路徑
    fpIn = path.resolve(fpIn)
    fdOut = path.resolve(fdOut)

    //prog, 經ensureBins取得splitDocx.exe(快取命中亦校驗, 未命中則自鏡像或GitHub取得並校驗), 本函數以字串reject, 故將帶code之Error轉為字串
    let prog = ''
    let errBin = null
    await ensureBins({ names: fnExe })
        .then((r) => {
            prog = r[fnExe]
        })
        .catch((err) => {
            errBin = `${isestr(get(err, 'code')) ? `[${err.code}] ` : ''}${isestr(get(err, 'message')) ? err.message : String(err)}`
        })
    if (errBin) {
        return Promise.reject(errBin)
    }
    // console.log('prog', prog)

    //inp
    let inp = {
        fpIn,
        strSep,
        fdOut,
        padZero,
    }
    // console.log('inp', inp)

    //input to b64
    let cInput = JSON.stringify(inp)
    let b64Input = str2b64(cInput)
    // console.log('b64Input', b64Input)

    //execProcess, splitDocx.exe以ANSI字碼頁輸出(中文系統為big5), 故codeCmd給auto使失敗訊息不亂碼
    let output = ''
    await execProcess(prog, b64Input, { codeCmd: 'auto' })
        .then((r) => {
            output = r
        })
        .catch((err) => {
            console.log('execProcess catch', err)
            errTemp = err.toString()
        })

    //check
    if (errTemp) {
        return Promise.reject(errTemp)
    }

    //check, splitDocx.exe內部失敗時只於stdout印出`error: ...`且離開碼為0, execProcess不會reject, 故須依輸出判定
    let j = judgeSplitOutput(output)
    if (!j.ok) {
        return Promise.reject(j.msg)
    }

    return 'ok'
}


//ensureBins, 以靜態屬性提供(不另加具名匯出, 使UMD打包後default匯出形態不變): 供部署前預先取得splitDocx.exe, 見README〈外部執行檔〉
WDocxSplit.ensureBins = ensureBins


export default WDocxSplit