如何自己实现一个丝滑的流程图绘制工具(五)bpmn的xml和json互转

背景

因为服务端给的数据并不是xml,而且服务端要拿的数据是json,所以我们只能xml和json互转,来完成和服务端的对接

xml转json
import XML from './config/jsonxml.js'

 /**
     * xml转为json
     * @param {*} xml
     */
    xmlToJson(xml) {
      const xotree = new XML.ObjTree()
      const jsonData = xotree.parseXML(xml)
      return jsonData
    },

jsonxml.js

const XML = function() {}

//  constructor

XML.ObjTree = function() {
  return this
}

//  class variables

XML.ObjTree.VERSION = '0.23'

//  object prototype

XML.ObjTree.prototype.xmlDecl = '\n'
XML.ObjTree.prototype.attr_prefix = '-'

//  method: parseXML( xmlsource )

XML.ObjTree.prototype.parseXML = function(xml) {
  let root
  if (window.DOMParser) {
    var xmldom = new DOMParser()
    //      xmldom.async = false;           // DOMParser is always sync-mode
    const dom = xmldom.parseFromString(xml, 'application/xml')
    if (!dom) return
    root = dom.documentElement
  } else if (window.ActiveXObject) {
    xmldom = new ActiveXObject('Microsoft.XMLDOM')
    xmldom.async = false
    xmldom.loadXML(xml)
    root = xmldom.documentElement
  }
  if (!root) return
  return this.parseDOM(root)
}

//  method: parseHTTP( url, options, callback )

XML.ObjTree.prototype.parseHTTP = function(url, options, callback) {
  const myopt = {}
  for (const key in options) {
    myopt[key] = options[key] // copy object
  }
  if (!myopt.method) {
    if (
      typeof myopt.postBody === 'undefined' &&
      typeof myopt.postbody === 'undefined' &&
      typeof myopt.parameters === 'undefined'
    ) {
      myopt.method = 'get'
    } else {
      myopt.method = 'post'
    }
  }
  if (callback) {
    myopt.asynchronous = true // async-mode
    const __this = this
    const __func = callback
    const __save = myopt.onComplete
    myopt.onComplete = function(trans) {
      let tree
      if (trans && trans.responseXML && trans.responseXML.documentElement) {
        tree = __this.parseDOM(trans.responseXML.documentElement)
      }
      __func(tree, trans)
      if (__save) __save(trans)
    }
  } else {
    myopt.asynchronous = false // sync-mode
  }
  let trans
  if (typeof HTTP !== 'undefined' && HTTP.Request) {
    myopt.uri = url
    var req = new HTTP.Request(myopt) // JSAN
    if (req) trans = req.transport
  } else if (typeof Ajax !== 'undefined' && Ajax.Request) {
    var req = new Ajax.Request(url, myopt) // ptorotype.js
    if (req) trans = req.transport
  }
  if (callback) return trans
  if (trans && trans.responseXML && trans.responseXML.documentElement) {
    return this.parseDOM(trans.responseXML.documentElement)
  }
}

//  method: parseDOM( documentroot )

XML.ObjTree.prototype.parseDOM = function(root) {
  if (!root) return

  this.__force_array = {}
  if (this.force_array) {
    for (let i = 0; i < this.force_array.length; i++) {
      this.__force_array[this.force_array[i]] = 1
    }
  }

  let json = this.parseElement(root) // parse root node
  if (this.__force_array[root.nodeName]) {
    json = [json]
  }
  if (root.nodeType != 11) {
    // DOCUMENT_FRAGMENT_NODE
    const tmp = {}
    tmp[root.nodeName] = json // root nodeName
    json = tmp
  }
  return json
}

//  method: parseElement( element )

XML.ObjTree.prototype.parseElement = function(elem) {
  //  COMMENT_NODE
  if (elem.nodeType == 7) {
    return
  }

  //  TEXT_NODE CDATA_SECTION_NODE
  if (elem.nodeType == 3 || elem.nodeType == 4) {
    const bool = elem.nodeValue.match(/[^\x00-\x20]/)
    if (bool == null) return // ignore white spaces
    return elem.nodeValue
  }

  let retval
  const cnt = {}

  //  parse attributes
  if (elem.attributes && elem.attributes.length) {
    retval = {}
    for (var i = 0; i < elem.attributes.length; i++) {
      var key = elem.attributes[i].nodeName
      if (typeof key !== 'string') continue
      var val = elem.attributes[i].nodeValue
      if (!val) continue
      key = this.attr_prefix + key
      if (typeof cnt[key] === 'undefined') cnt[key] = 0
      cnt[key]++
      this.addNode(retval, key, cnt[key], val)
    }
  }

  //  parse child nodes (recursive)
  if (elem.childNodes && elem.childNodes.length) {
    let textonly = true
    if (retval) textonly = false // some attributes exists
    for (var i = 0; i < elem.childNodes.length && textonly; i++) {
      const ntype = elem.childNodes[i].nodeType
      if (ntype == 3 || ntype == 4) continue
      textonly = false
    }
    if (textonly) {
      if (!retval) retval = ''
      for (var i = 0; i < elem.childNodes.length; i++) {
        retval += elem.childNodes[i].nodeValue
      }
    } else {
      if (!retval) retval = {}
      for (var i = 0; i < elem.childNodes.length; i++) {
        var key = elem.childNodes[i].nodeName
        if (typeof key !== 'string') continue
        var val = this.parseElement(elem.childNodes[i])
        if (!val) continue
        if (typeof cnt[key] === 'undefined') cnt[key] = 0
        cnt[key]++
        this.addNode(retval, key, cnt[key], val)
      }
    }
  }
  return retval
}

//  method: addNode( hash, key, count, value )

XML.ObjTree.prototype.addNode = function(hash, key, cnts, val) {
  if (this.__force_array[key]) {
    if (cnts == 1) hash[key] = []
    hash[key][hash[key].length] = val // push
  } else if (cnts == 1) {
    // 1st sibling
    hash[key] = val
  } else if (cnts == 2) {
    // 2nd sibling
    hash[key] = [hash[key], val]
  } else {
    // 3rd sibling and more
    hash[key][hash[key].length] = val
  }
}

//  method: writeXML( tree )

XML.ObjTree.prototype.writeXML = function(tree) {
  const xml = this.hash_to_xml(null, tree)
  return this.xmlDecl + xml
}

//  method: hash_to_xml( tagName, tree )

XML.ObjTree.prototype.hash_to_xml = function(name, tree) {
  const elem = []
  const attr = []
  for (const key in tree) {
    if (!tree.hasOwnProperty(key)) continue
    const val = tree[key]
    if (key.charAt(0) != this.attr_prefix) {
      if (typeof val === 'undefined' || val == null) {
        elem[elem.length] = `<${key} />`
      } else if (typeof val === 'object' && val.constructor == Array) {
        elem[elem.length] = this.array_to_xml(key, val)
      } else if (typeof val === 'object') {
        elem[elem.length] = this.hash_to_xml(key, val)
      } else {
        elem[elem.length] = this.scalar_to_xml(key, val)
      }
    } else {
      attr[attr.length] = ` ${key.substring(1)}="${this.xml_escape(val)}"`
    }
  }
  const jattr = attr.join('')
  let jelem = elem.join('')
  if (typeof name === 'undefined' || name == null) {
    // no tag
  } else if (elem.length > 0) {
    if (jelem.match(/\n/)) {
      jelem = `<${name}${jattr}>\n${jelem}${name}>\n`
    } else {
      jelem = `<${name}${jattr}>${jelem}${name}>\n`
    }
  } else {
    jelem = `<${name}${jattr} />\n`
  }
  return jelem
}

//  method: array_to_xml( tagName, array )

XML.ObjTree.prototype.array_to_xml = function(name, array) {
  const out = []
  for (let i = 0; i < array.length; i++) {
    const val = array[i]
    if (typeof val === 'undefined' || val == null) {
      out[out.length] = `<${name} />`
    } else if (typeof val === 'object' && val.constructor == Array) {
      out[out.length] = this.array_to_xml(name, val)
    } else if (typeof val === 'object') {
      out[out.length] = this.hash_to_xml(name, val)
    } else {
      out[out.length] = this.scalar_to_xml(name, val)
    }
  }
  return out.join('')
}

//  method: scalar_to_xml( tagName, text )

XML.ObjTree.prototype.scalar_to_xml = function(name, text) {
  if (name == '#text') {
    return this.xml_escape(text)
  }
  return `<${name}>${this.xml_escape(text)}${name}>\n`
}

//  method: xml_escape( text )

XML.ObjTree.prototype.xml_escape = function(text) {
  return `${text}`
    .replace(/&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
}

export default XML

json 转为xml

const getIncoming = (id, data) => {
  return data.filter(item => item.targetRef === id).map(items => items.id)
}
const getOutGoing = (id, data) => {
  return data.filter(item => item.sourceRef === id).map(items => items.id)
}
const getLabel = (data, labelStyle) => {
  const keyWord = ['isBold', 'isItalic', 'isStrikeThrough', 'isUnderline', 'fontFamily', 'size']
  const arr = data.filter(item => {
    return keyWord.find(key => {
      return key in labelStyle
    })
  })
  return arr.map(item => {
    const obj = {}
    keyWord.forEach(key => {
      if (labelStyle[key]) {
        obj[key === 'fontFamily' ? 'name' : key] = labelStyle[key] || ''
      }
    })
    return {
      '-id': item.id,
      'omgdc:Font': obj
    }
  })
}
export function convertJsonToBpmn(jsonData) {
  if (!jsonData || !Object.keys(jsonData).length) return {}
  const result = {
    definitions: {
      '-xmlns': 'http://www.omg.org/spec/BPMN/20100524/MODEL',
      '-xmlns:bpmndi': 'http://www.omg.org/spec/BPMN/20100524/DI',
      '-xmlns:omgdi': 'http://www.omg.org/spec/DD/20100524/DI',
      '-xmlns:omgdc': 'http://www.omg.org/spec/DD/20100524/DC',
      '-xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
      '-xmlns:bioc': 'http://bpmn.io/schema/bpmn/biocolor/1.0',
      '-xmlns:color': 'http://www.omg.org/spec/BPMN/non-normative/color/1.0',
      '-id': 'sid-38422fae-e03e-43a3-bef4-bd33b32041b2',
      '-targetNamespace': 'http://bpmn.io/bpmn',
      '-exporter': 'bpmn-js (https://demo.bpmn.io)',
      '-exporterVersion': '5.1.2',
      process: {
        '-id': 'Process_1',
        '-isExecutable': 'true',
        task: [],
        sequenceFlow: []
      },
      'bpmndi:BPMNDiagram': {
        '-id': 'BpmnDiagram_1',
        'bpmndi:BPMNPlane': {
          '-id': 'BpmnPlane_1',
          '-bpmnElement': 'Process_1',
          'bpmndi:BPMNShape': [],
          'bpmndi:BPMNEdge': []
        }
      },
      'bpmndi:BPMNLabelStyle': {}
    }
  }

  // Convert tasks
  jsonData.nodeLists.forEach(task => {
    const taskId = task.config.id
    const incoming = getIncoming(taskId, jsonData.lines)
    const outGoing = getOutGoing(taskId, jsonData.lines)
    const obj = {
      '-id': taskId
    }
    if (incoming.length > 1) {
      obj.incoming = incoming
    } else if (incoming.length === 1) {
      obj.incoming = incoming[0]
    }
    if (outGoing.length > 1) {
      obj.outgoing = outGoing
    } else if (outGoing.length === 1) {
      obj.outgoing = outGoing[0]
    }

    result.definitions.process.task.push(obj)
    const { x, y, width, height, labelStyle } = task.config

    const element = {
      '-id': `${taskId}_di`,
      '-bpmnElement': taskId,
      'omgdc:Bounds': {
        '-x': x,
        '-y': y,
        '-width': width,
        '-height': height
      },
      'bpmndi:BPMNLabel': {}
    }
    if (labelStyle && Object.keys(labelStyle).length) {
      const { x, y, width, height, id } = labelStyle
      element['bpmndi:BPMNLabel']['-labelStyle'] = id
      element['bpmndi:BPMNLabel']['omgdc:Bounds'] = {
        '-x': x,
        '-y': y,
        '-width': width,
        '-height': height
      }
      result.definitions['bpmndi:BPMNLabelStyle'] = getLabel(jsonData.nodeLists, labelStyle)
    }
    // Convert BPMN shapes
    result.definitions['bpmndi:BPMNDiagram']['bpmndi:BPMNPlane']['bpmndi:BPMNShape'].push(element)
  })

  // Convert sequence flows
  jsonData.lines.forEach(line => {
    const sequenceFlowId = line.id
    const sourceRef = line.sourceRef
    const targetRef = line.targetRef

    result.definitions.process.sequenceFlow.push({
      '-id': `${sequenceFlowId}`,
      '-name': line.name,
      '-sourceRef': sourceRef,
      '-targetRef': targetRef
    })

    // Convert BPMN edges
    result.definitions['bpmndi:BPMNDiagram']['bpmndi:BPMNPlane']['bpmndi:BPMNEdge'].push({
      '-id': `${sequenceFlowId}_di`,
      '-bpmnElement': sequenceFlowId,
      'omgdi:waypoint': line.point.map(p => {
        return { '-x': p.x, '-y': p.y }
      }),
      'bpmndi:BPMNLabel': {
        'omgdc:Bounds': {
          '-x': line.x,
          '-y': line.y,
          '-width': line.width,
          '-height': line.height
        }
      }
    })
  })

  return result
}

你可能感兴趣的:(流程图,xml,json)