attstr.mjs

// import trim from 'lodash-es/trim.js'
import uniq from 'lodash-es/uniq.js'
import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import each from 'lodash-es/each.js'
import filter from 'lodash-es/filter.js'
import join from 'lodash-es/join.js'
import split from 'lodash-es/split.js'
import drop from 'lodash-es/drop.js'
// import sep from './sep.mjs'
import isbol from './isbol.mjs'
import isarr from './isarr.mjs'
import isearr from './isearr.mjs'
import isstr from './isstr.mjs'
import isestr from './isestr.mjs'
import arrHas from './arrHas.mjs'
import haskey from './haskey.mjs'


/**
 * 屬性字串處理
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/attstr.test.mjs Github}
 * @memberOf wsemi
 * @returns {Object} 回傳attstr物件,提供parse、join、remove、add共4種處理函數
 * @example
 *
 * let c
 * let c1
 * let c2
 * let r
 *
 * let at = attstr({ uniqItems: true })
 *
 * //parse
 * console.log('parse')
 *
 * c = 'abc123'
 * r = at.parse(c)
 * console.log(r)
 * // => [ 'abc123' ]
 *
 * c = 'abc123;abc123'
 * r = at.parse(c)
 * console.log(r)
 * // => [ 'abc123' ]
 *
 * c = 'abc123;def456'
 * r = at.parse(c)
 * console.log(r)
 * // => [ 'abc123', 'def456' ]
 *
 * c = 'abc@123'
 * r = at.parse(c)
 * console.log(r)
 * // => [ { item: 'abc@123', table: 'abc', id: '123' } ]
 *
 * c = 'abc@123;abc@123'
 * r = at.parse(c)
 * console.log(r)
 * // => [ { item: 'abc@123', table: 'abc', id: '123' } ]
 *
 * c = 'abc@123;def@456'
 * r = at.parse(c)
 * console.log(r)
 * // => [
 * //   { item: 'abc@123', table: 'abc', id: '123' },
 * //   { item: 'def@456', table: 'def', id: '456' }
 * // ]
 *
 * c = ''
 * r = at.parse(c)
 * console.log(r)
 * // => []
 *
 * //join
 * console.log('join')
 *
 * c = ['abc123']
 * r = at.join(c)
 * console.log(r)
 * // => 'abc123'
 *
 * c = ['abc123', 'def456']
 * r = at.join(c)
 * console.log(r)
 * // => 'abc123;def456'
 *
 * c = ['abc@123']
 * r = at.join(c)
 * console.log(r)
 * // => 'abc@123'
 *
 * c = ['abc@123', 'def@456']
 * r = at.join(c)
 * console.log(r)
 * // => 'abc@123;def@456'
 *
 * c = [{ table: 'abc', id: '123' }, { table: 'def', id: '456' }]
 * r = at.join(c)
 * console.log(r)
 * // => 'abc@123;def@456'
 *
 * c = []
 * r = at.join(c)
 * console.log(r)
 * // => ''
 *
 * //add
 * console.log('add')
 *
 * c1 = 'abc123'
 * c2 = 'def456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123;def456
 *
 * c1 = 'abc123'
 * c2 = 'def456;ghi789'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123;def456;ghi789
 *
 * c1 = 'abc123'
 * c2 = 'abc123'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123
 *
 * c1 = 'abc123'
 * c2 = 'abc123;def456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123;def456
 *
 * c1 = 'abc123;ghi789'
 * c2 = 'abc123;def456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123;ghi789;def456
 *
 * c1 = ''
 * c2 = 'abc123'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123
 *
 * c1 = ''
 * c2 = 'abc123;def456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc123;def456
 *
 * c1 = 'abc@123'
 * c2 = 'def@456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc@123;def@456
 *
 * c1 = 'abc@123'
 * c2 = 'def@456;ghi@789'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc@123;def@456;ghi@789
 *
 * c1 = 'abc@123;ghi@789'
 * c2 = 'abc@123;def@456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc@123;ghi@789;def@456
 *
 * c1 = ''
 * c2 = 'abc@123'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc@123
 *
 * c1 = ''
 * c2 = 'abc@123;def@456'
 * r = at.add(c1, c2)
 * console.log(r)
 * // => abc@123;def@456
 *
 * //remove
 * console.log('remove')
 *
 * c1 = 'abc123'
 * c2 = 'abc123'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = 'abc123;def456'
 * c2 = 'abc123'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => def456
 *
 * c1 = 'abc123'
 * c2 = 'def456'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc123
 *
 * c1 = 'abc123'
 * c2 = 'ghi789;jkl012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc123
 *
 * c1 = 'abc123'
 * c2 = 'abc123;jkl012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = 'abc123;def456'
 * c2 = 'ghi789;jkl012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc123;def456
 *
 * c1 = 'abc123;def456'
 * c2 = 'def456;jkl012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc123
 *
 * c1 = ''
 * c2 = 'ghi789'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = ''
 * c2 = 'ghi789;jkl012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = 'abc@123'
 * c2 = 'abc@123'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = 'abc@123;def@456'
 * c2 = 'abc@123'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => def@456
 *
 * c1 = 'abc@123'
 * c2 = 'def@456'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc@123
 *
 * c1 = 'abc@123'
 * c2 = 'ghi@789;jkl@012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc@123
 *
 * c1 = 'abc@123'
 * c2 = 'abc@123;jkl@012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = 'abc@123;def@456'
 * c2 = 'ghi@789;jkl@012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc@123;def@456
 *
 * c1 = 'abc@123;def@456'
 * c2 = 'def@456;jkl@012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => abc@123
 *
 * c1 = ''
 * c2 = 'ghi@789'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * c1 = ''
 * c2 = 'ghi@789;jkl@012'
 * r = at.remove(c1, c2)
 * console.log(r)
 * // => ''
 *
 * let at2 = attstr({ uniqItems: true, dlmItem: ',', dlmSep: '|' })
 *
 * c = 'x1|abc@123|def@456,x2|ghi@789'
 * r = at2.parse(c)
 * console.log(r)
 * // => [
 * //   { item: 'x1|abc@123|def@456', table: 'x1', id: ['abc@123','def@456'] },
 * //   { item: 'x2|ghi@789', table: 'x2', id: 'ghi@789' }
 * // ]
 *
 * let at3 = attstr({ uniqItems: true, dlmItem: ',', dlmSep: '|', keyTable: 'name', keyId: 'emails' })
 *
 * c = 'x1|abc@123|def@456,x2|ghi@789'
 * r = at3.parse(c)
 * console.log(r)
 * // => [
 * //   { item: 'x1|abc@123|def@456', name: 'x1', emails: ['abc@123','def@456'] },
 * //   { item: 'x2|ghi@789', name: 'x2', emails: 'ghi@789' }
 * // ]
 *
 * let at4 = attstr({ uniqItems: false })
 *
 * c = ['abc@123', 'abc@123', 'def@456']
 * r = at4.join(c)
 * console.log(r)
 * // => 'abc@123;abc@123;def@456'
 *
 * c = [{ table: 'abc', id: '123' }, { table: 'def', id: '456' }, { table: 'def', id: '456' }]
 * r = at4.join(c)
 * console.log(r)
 * // => 'abc@123;def@456;def@456'
 *
 *
 * c1 = 'abc123;ghi789'
 * c2 = 'abc123;def456'
 * r = at4.add(c1, c2)
 * console.log(r)
 * // => abc123;ghi789;abc123;def456
 *
 * c1 = 'abc@123;ghi@789'
 * c2 = 'abc@123;def@456'
 * r = at4.add(c1, c2)
 * console.log(r)
 * // => abc@123;ghi@789;abc@123;def@456
 *
 */
function attstr(opt = {}) {
    let dlmItem = ';'
    let dlmSep = '@'
    let keyTable = 'table'
    let keyId = 'id'


    //uniqItems
    let uniqItems = get(opt, 'uniqItems')
    if (!isbol(uniqItems)) {
        uniqItems = false
    }

    //dlmItem
    let _dlmItem = get(opt, 'dlmItem', '')
    if (isestr(_dlmItem)) {
        dlmItem = _dlmItem
    }


    //dlmSep
    let _dlmSep = get(opt, 'dlmSep', '')
    if (isestr(_dlmSep)) {
        dlmSep = _dlmSep
    }


    //keyTable
    let _keyTable = get(opt, 'keyTable', '')
    if (isestr(_keyTable)) {
        keyTable = _keyTable
    }


    //keyId
    let _keyId = get(opt, 'keyId', '')
    if (isestr(_keyId)) {
        keyId = _keyId
    }


    function sepItems(composItems) {

        //check
        if (!isestr(composItems)) {
            return []
        }

        //arrItems
        let arrItems = split(composItems, dlmItem) //須用split否則用sep會無法處理空資料問題

        //uniq
        if (uniqItems) {
            arrItems = uniq(arrItems)
        }

        return arrItems
    }


    function joinItems(arrItems) {

        //check
        if (!isearr(arrItems)) {
            return ''
        }

        //uniq
        if (uniqItems) {
            arrItems = uniq(arrItems)
        }

        //str
        let str = join(arrItems, dlmItem)

        return str
    }


    function atItemsMergeS1(its) {

        //filter
        its = filter(its, isestr)

        //uniq
        if (uniqItems) {
            its = uniq(its)
        }

        return joinItems(its)
    }


    function atItemsMergeS2(its) {

        //itsTemp
        let itsTemp = []
        each(its, (v) => {
            let table = get(v, keyTable, '')
            // console.log('table', table)
            let id = get(v, keyId, '')
            // console.log('id', id)
            if (!isestr(table)) {
                // console.log(`atItemsMergeS2: invalid keyTable[${keyTable}] in its`, v, its)
            }
            if (isstr(id)) {
                if (id === '') {
                    // console.log(`atItemsMergeS2: invalid keyId[${keyId}] in its`, v, its)
                }
            }
            else if (isarr(id)) {
                if (size(id) === 0) {
                    // console.log(`atItemsMergeS2: invalid keyId[${keyId}] in its`, v, its)
                }
            }
            else {
                // console.log(`atItemsMergeS2: keyId[${keyId}] is not a string or an array in its`, v, its)
                id = ''
            }
            if (isstr(id)) {
                itsTemp.push(`${table}${dlmSep}${id}`)
            }
            else if (isarr(id)) {
                let ids = join(id, dlmSep)
                itsTemp.push(`${table}${dlmSep}${ids}`)
            }
        })

        //itsTemp
        its = joinItems(itsTemp)

        return its
    }


    function atJoin(its, opt = {}) {

        //check
        if (!isearr(its)) {
            return ''
        }

        //mode
        let mode = get(opt, 'mode')
        if (mode !== '1p' && mode !== '2p') {
            mode = 'auto'
        }

        if (mode === '1p') {
            return atItemsMergeS1(its)
        }
        else if (mode === '2p') {
            return atItemsMergeS2(its)
        }

        //it0
        let it0 = get(its, 0)

        //bti
        let bti = haskey(it0, keyTable) && haskey(it0, keyId)
        // console.log('bti', bti)

        //mode='2p'
        if (bti) {
            return atItemsMergeS2(its)
        }

        //default
        return atItemsMergeS1(its)
    }


    function atParseS1(composItems) {

        //check
        if (!isestr(composItems)) {
            return []
        }

        //arrItems
        let arrItems = sepItems(composItems)

        //ids
        let ids = filter(arrItems, isestr)

        return ids
    }


    function atParseS2(composItems) {

        //check
        if (!isestr(composItems)) {
            return []
        }

        //arrItems
        let arrItems = sepItems(composItems)

        //ids
        let ids = []
        each(arrItems, (v) => {
            let s = split(v, dlmSep) //須用split否則用sep會無法處理空資料問題
            // console.log(`size(s)`, size(s))
            let table = get(s, 0, '')
            // table = trim(table) //不使用trim避免空白被刪除
            let id = get(s, 1, '')
            // id = trim(id) //不使用trim避免空白被刪除
            if (table === '') {
                // console.log(`atParseS2: invalid keyTable[${keyTable}] in composItems`, v, composItems)
            }
            else if (id === '') {
                // console.log(`atParseS2: invalid keyId[${keyId}] in composItems`, v, composItems)
            }
            if (size(s) >= 3) {
                //有2個以上id
                s = drop(s)
                id = s //改儲存為陣列
            }
            ids.push({
                item: v,
                [keyTable]: table,
                [keyId]: id,
            })
        })

        return ids
    }


    function atParse(composItems, opt = {}) {

        //check
        if (!isestr(composItems)) {
            return []
        }

        //mode
        let mode = get(opt, 'mode')
        if (mode !== '1p' && mode !== '2p') {
            mode = 'auto'
        }

        if (mode === '1p') {
            return atParseS1(composItems)
        }
        else if (mode === '2p') {
            return atParseS2(composItems)
        }

        //bc
        let bc = composItems.indexOf(dlmSep) >= 0

        //mode='2p'
        if (bc) {
            return atParseS2(composItems)
        }

        //default
        return atParseS1(composItems)
    }


    function atAddS1(composItems1, composItems2) {

        //atParseS1
        let itp1 = atParseS1(composItems1)
        let itp2 = atParseS1(composItems2)

        //its
        let its = [...itp1, ...itp2]

        //check
        if (size(its) === 0) {
            return ''
        }

        return atItemsMergeS1(its)
    }


    function atAddS2(composItems1, composItems2) {

        //atParseS2
        let itp1 = atParseS2(composItems1)
        let itp2 = atParseS2(composItems2)

        //its
        let its = [...itp1, ...itp2]

        //check
        if (size(its) === 0) {
            return ''
        }

        return atItemsMergeS2(its)
    }


    function atAdd(composItems1, composItems2, opt = {}) {

        //mode
        let mode = get(opt, 'mode')
        if (mode !== '1p' && mode !== '2p') {
            mode = 'auto'
        }

        if (mode === '1p') {
            return atAddS1(composItems1, composItems2)
        }
        else if (mode === '2p') {
            return atAddS2(composItems1, composItems2)
        }

        //bc1, bc2
        let bc1 = composItems1.indexOf(dlmSep) >= 0
        let bc2 = composItems2.indexOf(dlmSep) >= 0

        //mode='2p'
        if (bc1 || bc2) {
            return atAddS2(composItems1, composItems2)
        }

        //default
        return atAddS1(composItems1, composItems2)
    }


    function atRemoveS1(composItems1, composItems2) {

        //atParseS1
        let itp1 = atParseS1(composItems1)
        let itp2 = atParseS1(composItems2)

        //its
        let its = []
        each(itp1, (v) => {
            if (!arrHas(v, itp2)) {
                its.push(v)
            }
        })

        //check
        if (size(its) === 0) {
            return ''
        }

        return atItemsMergeS1(its)
    }


    function atRemoveS2(composItems1, composItems2) {

        //atParseS2
        let itp1 = atParseS2(composItems1)
        let itp2 = atParseS2(composItems2)

        //its
        let its = []
        each(itp1, (v) => {
            if (!arrHas(v, itp2)) {
                its.push(v)
            }
        })

        //check
        if (size(its) === 0) {
            return ''
        }

        return atItemsMergeS2(its)
    }


    function atRemove(composItems, removeItemOrId, opt = {}) {

        //check composItems
        if (!isestr(composItems)) {
            return ''
        }

        //check removeItemOrId
        if (!isestr(removeItemOrId)) {
            return composItems
        }

        //mode
        let mode = get(opt, 'mode')
        if (mode !== '1p' && mode !== '2p') {
            mode = 'auto'
        }

        if (mode === '1p') {
            return atRemoveS1(composItems, removeItemOrId)
        }
        else if (mode === '2p') {
            return atRemoveS2(composItems, removeItemOrId)
        }

        //bt
        // let bc = composItems.indexOf(dlmSep) >= 0 //bc可為空字串故不一定有dlmSep故不納入檢測
        let bt = removeItemOrId.indexOf(dlmSep) >= 0

        //mode='2p'
        if (bt) {
            return atRemoveS2(composItems, removeItemOrId)
        }

        //default
        return atRemoveS1(composItems, removeItemOrId)
    }

    //at
    let at = {
        parse: atParse,
        join: atJoin,
        remove: atRemove,
        add: atAdd,
    }

    return at
}


export default attstr