treeToFilepath.mjs

import get from 'lodash-es/get.js'
import map from 'lodash-es/map.js'
import join from 'lodash-es/join.js'
import size from 'lodash-es/size.js'
import drop from 'lodash-es/drop.js'
import isestr from './isestr.mjs'
import isearr from './isearr.mjs'
import treeObj from './treeObj.mjs'


/**
 * 轉換為樹狀物件成檔案路徑陣列
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/treeToFilepath.test.mjs Github}
 * @memberOf wsemi
 * @param {Array} treeItems 輸入樹狀物件陣列,每個須提供pathInfors、_type鍵值,其中pathInfors為各所屬(含自己)階層節點資訊,_type需為'folder'或'file',詳見filepathToTree轉出treeItems
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {String} [delimiter='/'] 輸入路徑分隔字串,預設'/'
 * @param {String} [bindRoot='root'] 輸入臨時封裝用根目錄字串,詳見filepathToTree內設定,預設'root'
 * @returns {Array} 回傳檔案路徑陣列,每個項目為物件並提供path與type兩鍵值,其中type為'folder'或'file'
 * @example
 *
 * let fps1 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/bbb/z1.txt' }]
 * let r1 = filepathToTree(fps1)
 * console.log('r1.treeItems', r1.treeItems)
 * // => r1.treeItems [
 * //   {
 * //     ns: 1,
 * //     ts: [ 'root' ],
 * //     pathInfors: [ [Object] ],
 * //     _type: 'folder',
 * //     type: 'array',
 * //     numOfChilren: -1,
 * //     id: 'root',
 * //     parentId: '',
 * //     text: 'root',
 * //     children: [ [Object] ],
 * //     data: null
 * //   }
 * // ]
 *
 * let rfps1 = treeToFilepath(r1.treeItems, {})
 * console.log('rfps1', rfps1)
 * // => rfps1 [
 * //   { type: 'folder', path: '/aaa' },
 * //   { type: 'folder', path: '/aaa/bbb' },
 * //   { type: 'file', path: '/aaa/bbb/z1.txt' }
 * // ]
 *
 * let fps2 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa1.txt' }, { 'type': 'file', 'path': '/aaa2.txt' }, { 'type': 'folder', 'path': '/aaa/aaabbb' }, { 'type': 'file', 'path': '/aaa/aaabbb.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/aaabbbccc.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcd/abcde.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdef1.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdef2.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdef3 aaa bbb ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg01.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg02.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg03.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg04.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg05.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg06.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg07.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg08.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg09.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg10.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg11.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg12.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg13.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg14.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg15.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg16.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg17.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg18.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg19.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcde/abcdefg20.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcd1.txt' }, { 'type': 'file', 'path': '/aaa/aaabbb/abc/abcd2.txt' }, { 'type': 'folder', 'path': '/bbb' }, { 'type': 'file', 'path': '/ccc/cccddd/cccdddeee.txt' }, { 'type': 'folder', 'path': '/eee' }, { 'type': 'folder', 'path': '/eee/eeefff1' }, { 'type': 'folder', 'path': '/eee/eeefff2' }, { 'type': 'folder', 'path': '/ggg/' }, { 'type': 'folder', 'path': 'c:\\\\hhh' }, { 'type': 'folder', 'path': '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff' }, { 'type': 'file', 'path': '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt' }, { 'type': 'file', 'path': '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt' }, { 'type': 'folder', 'path': 'd:\\\\中文路徑1' }, { 'type': 'folder', 'path': '/中文路徑2' }, { 'type': 'file', 'path': '/中文路徑2/aaa/aaabbb/abc/測試.txt' }]
 * let r2 = filepathToTree(fps2, { delimiter: '>' })
 * console.log('r2.treeItems', r2.treeItems)
 * // => r2.treeItems [
 * //   {
 * //     ns: 1,
 * //     ts: [ 'root' ],
 * //     pathInfors: [ [Object] ],
 * //     _type: 'folder',
 * //     type: 'array',
 * //     numOfChilren: -1,
 * //     id: 'root',
 * //     parentId: '',
 * //     text: 'root',
 * //     children: [
 * //       [Object], [Object],
 * //       [Object], [Object],
 * //       [Object], [Object],
 * //       [Object], [Object],
 * //       [Object], [Object],
 * //       [Object]
 * //     ],
 * //     data: null
 * //   }
 * // ]
 *
 * let rfps2 = treeToFilepath(r2.treeItems, {})
 * console.log('rfps2', rfps2)
 * // rfps2 [
 * //   { type: 'folder', path: '/aaa' },
 * //   { type: 'folder', path: '/aaa/aaabbb' },
 * //   { type: 'folder', path: '/aaa/aaabbb/abc' },
 * //   { type: 'folder', path: '/aaa/aaabbb/abc/abcd' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcd/abcde.txt' },
 * //   { type: 'folder', path: '/aaa/aaabbb/abc/abcde' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdef1.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdef2.txt' },
 * //   {
 * //     type: 'file',
 * //     path: '/aaa/aaabbb/abc/abcde/abcdef3 aaa bbb ccc dddddd eeeeeee fffffffffff ggggggggggggg.txt'
 * //   },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg01.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg02.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg03.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg04.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg05.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg06.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg07.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg08.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg09.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg10.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg11.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg12.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg13.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg14.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg15.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg16.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg17.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg18.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg19.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcde/abcdefg20.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcd1.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/abc/abcd2.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb/aaabbbccc.txt' },
 * //   { type: 'file', path: '/aaa/aaabbb.txt' },
 * //   { type: 'folder', path: '/aaaa' },
 * //   {
 * //     type: 'folder',
 * //     path: '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff'
 * //   },
 * //   {
 * //     type: 'folder',
 * //     path: '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg'
 * //   },
 * //   {
 * //     type: 'file',
 * //     path: '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/ggg/hhh.txt'
 * //   },
 * //   {
 * //     type: 'file',
 * //     path: '/aaaa/bbbbbb cccccccccccc ddd dd ddd ddd ddd eeeeeeeeeeee ffff/gfedcba.txt'
 * //   },
 * //   { type: 'folder', path: '/bbb' },
 * //   { type: 'folder', path: '/c:' },
 * //   { type: 'folder', path: '/c:/hhh' },
 * //   { type: 'folder', path: '/ccc' },
 * //   { type: 'folder', path: '/ccc/cccddd' },
 * //   { type: 'file', path: '/ccc/cccddd/cccdddeee.txt' },
 * //   { type: 'folder', path: '/d:' },
 * //   { type: 'folder', path: '/d:/中文路徑1' },
 * //   { type: 'folder', path: '/eee' },
 * //   { type: 'folder', path: '/eee/eeefff1' },
 * //   { type: 'folder', path: '/eee/eeefff2' },
 * //   { type: 'folder', path: '/ggg' },
 * //   { type: 'folder', path: '/中文路徑2' },
 * //   { type: 'folder', path: '/中文路徑2/aaa' },
 * //   { type: 'folder', path: '/中文路徑2/aaa/aaabbb' },
 * //   { type: 'folder', path: '/中文路徑2/aaa/aaabbb/abc' },
 * //   { type: 'file', path: '/中文路徑2/aaa/aaabbb/abc/測試.txt' },
 * //   { type: 'file', path: '/aaa1.txt' },
 * //   { type: 'file', path: '/aaa2.txt' }
 * // ]
 *
 * let fps3 = [{ 'type': 'folder', 'path': '/aaa1' }, { 'type': 'folder', 'path': '/aaa2' }, { 'type': 'folder', 'path': '/aaa10' }, { 'type': 'file', 'path': '/aaa/bbb/z1.txt' }, { 'type': 'file', 'path': '/aaa/bbb/z2.txt' }, { 'type': 'file', 'path': '/aaa/bbb/z10.txt' }]
 * let r3 = filepathToTree(fps3)
 * console.log('r3.treeItems', r3.treeItems)
 * // => r3.treeItems [
 * //   {
 * //     ns: 1,
 * //     ts: [ 'root' ],
 * //     pathInfors: [ [Object] ],
 * //     _type: 'folder',
 * //     type: 'array',
 * //     numOfChilren: -1,
 * //     id: 'root',
 * //     parentId: '',
 * //     text: 'root',
 * //     children: [ [Object], [Object], [Object], [Object] ],
 * //     data: null
 * //   }
 * // ]
 *
 * let rfps3 = treeToFilepath(r3.treeItems, {})
 * console.log('rfps3', rfps3)
 * // => rfps3 [
 * //   { type: 'folder', path: '/aaa' },
 * //   { type: 'folder', path: '/aaa/bbb' },
 * //   { type: 'file', path: '/aaa/bbb/z1.txt' },
 * //   { type: 'file', path: '/aaa/bbb/z10.txt' },
 * //   { type: 'file', path: '/aaa/bbb/z2.txt' },
 * //   { type: 'folder', path: '/aaa1' },
 * //   { type: 'folder', path: '/aaa10' },
 * //   { type: 'folder', path: '/aaa2' }
 * // ]
 *
 * let fps4 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/bbb/z1.txt' }]
 * let r4 = filepathToTree(fps4)
 * console.log('r4.treeItems', r4.treeItems)
 * // => r1.treeItems [
 * //   {
 * //     ns: 1,
 * //     ts: [ 'root' ],
 * //     pathInfors: [ [Object] ],
 * //     _type: 'folder',
 * //     type: 'array',
 * //     numOfChilren: -1,
 * //     id: 'root',
 * //     parentId: '',
 * //     text: 'root',
 * //     children: [ [Object] ],
 * //     data: null
 * //   }
 * // ]
 *
 * let rfps4 = treeToFilepath(r4.treeItems, { delimiter: '>' })
 * console.log('rfps4', rfps4)
 * // => rfps4 [
 * //   { type: 'folder', path: '>aaa' },
 * //   { type: 'folder', path: '>aaa>bbb' },
 * //   { type: 'file', path: '>aaa>bbb>z1.txt' }
 * // ]
 *
 * let fps5 = [{ 'type': 'folder', 'path': '/aaa' }, { 'type': 'file', 'path': '/aaa/bbb/z1.txt' }]
 * let r5 = filepathToTree(fps5, { bindRoot: 'ROOT' })
 * console.log('r5.treeItems', r5.treeItems)
 * // => r5.treeItems [
 * //   {
 * //     ns: 1,
 * //     ts: [ 'ROOT' ],
 * //     pathInfors: [ [Object] ],
 * //     _type: 'folder',
 * //     type: 'array',
 * //     numOfChilren: -1,
 * //     id: 'ROOT',
 * //     parentId: '',
 * //     text: 'ROOT',
 * //     children: [ [Object] ],
 * //     data: null
 * //   }
 * // ]
 *
 * let rfps5 = treeToFilepath(r5.treeItems, { bindRoot: 'ROOT' })
 * console.log('rfps5', rfps5)
 * // => rfps5 [
 * //   { type: 'folder', path: '/aaa' },
 * //   { type: 'folder', path: '/aaa/bbb' },
 * //   { type: 'file', path: '/aaa/bbb/z1.txt' }
 * // ]
 *
 */
function treeToFilepath(data, opt = {}) {

    //check
    if (!isearr(data)) {
        return []
    }

    //delimiter
    let delimiter = get(opt, 'delimiter')
    if (!isestr(delimiter)) {
        delimiter = '/'
    }

    //bindRoot
    let bindRoot = get(opt, 'bindRoot')
    if (!isestr(bindRoot)) {
        bindRoot = 'root'
    }

    //nodes
    let nodes = []
    treeObj(data, (value, key, nk) => {
        // console.log('nk:', nk, 'key:', key, 'value:', value)

        //type
        let type = get(value, '_type', '')
        if (type !== 'folder' && type !== 'file') {
            return value
        }

        //pathInfors
        let pathInfors = get(value, 'pathInfors', [])

        //texts
        let texts = map(pathInfors, 'name')

        //text0
        let text0 = get(texts, 0, '')

        //bText0EqRoot
        let bText0EqRoot = text0 === bindRoot

        //check, 只有一層時text0為bindRoot, 則不儲存
        if (size(texts) === 1 && bText0EqRoot) {
            return value
        }

        //剔除最上層bindRoot
        if (bText0EqRoot) {
            texts = drop(texts)
        }

        //path
        let path = delimiter + join(texts, delimiter)

        //node
        let node = {
            type,
            path,
        }

        //push
        nodes.push(node)

        return value
    }, { force: true })
    // console.log('nodes', nodes)

    return nodes
}


export default treeToFilepath