我在开辟"小顺序"中做的一些"转换"的事情

引见

“转换” 意义是将”小顺序”不支撑的东西转换成它支撑的东西。我在开辟的小顺序的历程当中遇到了两种须要做“转换”的场景:

  • html 转换成 wxml

  • svg 转换成 canvas

我将在下文细致引见我是怎样处置惩罚这两种状况的。

html 转换成 wxml

我们的产物在某些场景下,后端接口会直接传 html 字符串给前端。在 ReactJs 中,我们能够用 dangerouslySetInnerHTML 直接衬着 html 字符串(不肯定平安),而 ”小顺序“不支撑 html ,因而必需对 html 举行处置惩罚。处理这个题目标步骤主如果:1. 将 html 转换成 json ( 树构造) ;2. 将 json 转换成 wxml 。我在对题目做了调研后发明,现有一个库 wxParse 满足该转换的目标,然则在我看来,这个库做的事变太多,须要依靠文件过量,不满足只须要简朴处置惩罚的须要,所以我决议本身写。

html 转换成 json

在参考了 html2jsonhimalaya 两个库的处置惩罚思绪的基础上,我写了一个简朴的剖析库 htmlParser htmlParser 处置惩罚 html字符串分两步:

lexer: 天生标记(token

function lex(html) {
  let string = html
  let tokens = []

  while (string) {
    // 先处置惩罚以 "</" 最先的完毕标签
    if (string.indexOf("</") === 0) {
      const match = string.match(REGEXP.endTag)
      if (!match) continue
      // 经由历程 substring 截断这个标签的字符串长度
      string = string.substring(match[0].length)
      tokens.push({
        tag: match[1],
        type: 'tag-end',
      })
      continue
    }
    // 处置惩罚以 "<" 最先的标签
    if (string.indexOf("<") === 0) {
      const match = string.match(REGEXP.startTag)
      if (!match) continue
      string = string.substring(match[0].length)
      const tag = match[1]
      const isEmpty = !!MAKER.empty[tag]
      const type = isEmpty ? 'tag-empty' : 'tag-start'
      const attributes = getAttributes(match[2])

      tokens.push({
        tag,
        type,
        attributes
      })
      continue
    }
    // 每一个处置惩罚历程的其他部份字符串被当作 "text" 文本处置惩罚(暂时不处置惩罚其他状况)    
    const index = string.indexOf('<')
    const text = index < 0 ? string : string.substring(0, index)

    string = index < 0 ? "" : string.substring(index)
    tokens.push({
      type: "text",
      text
    })
  }
  return tokens
}

parser: 根据标记天生树
上面的 lexerhtml 字符串分开成了一个一个 token,然后,我们经由历程遍历一切的标识来构建立

function parse(tokens) {
  let root = {
    tag: "root",
    children: []
  }
  let tagArray = [root]
  tagArray.last = () => tagArray[tagArray.length - 1]

  for (var i = 0; i < tokens.length; i++) {
    const token = tokens[i]
    if (token.type === 'tag-start') {
      // 构建节点
      const node = {
        type: "Element",
        tagName: token.tag,
        attributes: Object.assign({}, {
          class: token.tag
        }, token.attributes),
        children: []
      }
      tagArray.push(node)
      continue
    }
    if (token.type === 'tag-end') {
      let parent = tagArray[tagArray.length - 2]
      let node = tagArray.pop()
      // 将该节点到场父节点中
      parent.children.push(node) 
      continue
    }
    if (token.type === 'text') {
      // 往该节点中到场子元素
      tagArray.last().children.push({
        type: 'text',
        content: replaceMark(token.text)
      })
      continue
    }
    if (token.type === 'tag-empty') { 
     // 往该节点中到场子元素
      tagArray.last().children.push({
        type: "Element",
        tagName: token.tag,
        attributes: Object.assign({}, {
          class: token.tag
        }, token.attributes),
      })
      continue
    }
  }
  return root
}

全部顺序的运转效果举例:

var html = '<div style='height:10rpx;width: 20rpx;'><img src="http://xxx.jpg class="image"/></div>'
htmlParser(html)
# 转换效果
{
  "tag": "root",
  "children": [{
    "type": "Element",
    "tagName": "div",
    "attributes": {
      "style": "height:10rpx;width: 20rpx;"
    },
    "children": [ {
      "type": "Element",
      "tagName": "img",
      "attributes": {
          src: "http://xxx.jpg",
          class: "image"
      }
    }]  
  }]
}

以上,我们完成了 html字符串的转换,完整代码请戳 htmlParser

json 转换成 wxml

在熟习了“小顺序”框架的基础上,发明须要借助模板 template ,将 json 数据添补进 template,并根据元素范例衬着响应的 wxml 组件以抵达转换目标。比方:

# 定义一个称号为 html-image 的模板
<template name="html-image">
  <image
    mode="widthFix"
    class="{{attributes.class}}"
    src="{{attributes.src}}"></image>
</template>
/* 运用模板
   个中 json 的构造为: {
      "type": "Element",
      "tagName": "img",
      "attributes": {
          src: "http://xxx.jpg",
          class: "image"
      }
    }
 */
<template is="html-image" data={{json}}></template>

如许,我们就能够转化胜利了。

而因为模板没有援用本身的才能,只能运用笨办法运用多个一样内容,然则模板称号不一样的模板来处理嵌套的层级关联,而嵌套的层级取决于运用的模板个数

<template name="html-image">
  <image
    mode="widthFix"
    class="{{attributes.class}}"
    src="{{attributes.src}}"></image>
</template>

<template name="html-video">
  <video
    class="{{attributes.class}}"
    src="{{attributes.src}}"></video>
</template>

<template name="html-text" wx:if="{{content}}">
  <text>{{content}}</text>
</template>

<template name="html-br">
  <text>\n</text>
</template>

<template name="html-item">
  <block wx:if="{{item.type === 'text'}}">
    <template is="html-text" data="{{...item}}" />
  </block>
  <block wx:elif="{{item.tagName === 'img'}}">
    <template is="html-image" data="{{...item}}" />
  </block>
  <block wx:elif="{{item.tagName === 'video'}}">
    <template is="html-video" data="{{...item}}" />
  </block>
  <block wx:elif="{{item.tagName === 'br'}}">
    <template is="html-br"></template>
  </block>
  <block wx:else></block>
</template>

// html 援用 html1 两个模板一样
<template name="html">
  <block wx:if="{{tag}}">
    <block wx:for="{{children}}" wx:key="{{index}}">
      <block wx:if="{{item.children.length}}">
      <template is="html1" data="{{...item}}"/>
      </block>
      <block wx:else>
        <template is="html-item" data="{{item}}"/>
      </block>
    </block>
  </block>
</template>

<template name="html1">
  <view class="{{attributes.class}}" style="{{attributes.style}}">
    <block wx:for="{{children}}" wx:key="{{index}}">
      <block wx:if="{{item.children.length}}">
        <template is="html2" data="{{...item}}"/>
      </block>
      <block wx:else>
        <template is="html-item" data="{{item}}"/>
      </block>
    </block>
  </view>
</template>

如上处置惩罚历程当中,有些须要注重的细节,比方:要对 html 实体字符转换,让模板的 image 组件支撑 mode 等等。总之,经由如上的处置惩罚,html 字符串对 wxml 组件的转换基础功用完成。

svg 转换成 canvas

在我们的产物 web 版本中,因为须要在页面元素中运用 svg 作为 dom 元素,而“小顺序” 没有 svg 组件的支撑,如此一来,我们也须要对后端接口传来的 svg 字符串做转换。“小顺序”没有svg 组件然则有 canvas 组件,因而我决议运用 canvas 来模仿 svg 绘制图形,并将图形做肯定的修正以满足基础需求。

做这个“转换”的症结也有两点:1. 提取 svg 字符串中的元素;2.canvas 模仿元素功用举行绘制

svg 元素的提取

因为 svg 字符串是一个 xml, 用上面的 htmlParser 能够将其天生 json ,题目处理。

canvas 模仿绘制

websvg 的元素有许多,幸亏我们须要的只要一些基础的元素:image, rect, pathrectcanvas 模仿不算难事,canvas 绘制起来很简朴,代码以下:

// draw rect
 ctx.save()
 ctx.setFillStyle(attr.fill)
 ctx.fillRect(attr.x, attr.y, attr.width, attr.height)
 ctx.restore()

但是,在开辟历程当中,遇到了一个难点:不知道对 pathd 属性怎样举行模仿。d 属性触及挪动、贝塞尔曲线等等。比方:<path d="M250 150 L150 350 L350 350 Z" /> 这个例子定义了一条途径,它最先于位置 250 150,抵达位置 150 350,然后从那边最先到 350 350,末了在 250 150 封闭途径(M = movetoL = linetoZ = closepath )。用 canvas 举行绘制,须要先提取 d 各属性值,再根据各属性值与 canvas 的对应绘制关联顺次举行绘制。在我为此犯难的时刻,幸亏发明有前人有做了如许事变。在 gist 上,发明了对 d 的剖析,与 canvas 绘制 d 的相干代码,难题题目也得以处理。

 /**
  * svg path
  * <path d="M250 150 L150 350 L350 350 Z" />
  * d 属性值 "M250 150 L150 350 L350 350 Z"
  * 我们提取属性的的构造为: [
  *  { marker: 'M', values: [250, 150]}
  * ]
  * https://gist.github.com/shamansir/0ba30dc262d54d04cd7f79e03b281505
  * 以下代码为 d 属性的提取部份,已在源代码基础上修正,
  */
  _pathDtoCommands(str) {
      let results = [],
        match;
      while ((match = markerRegEx.exec(str)) !== null) {
        results.push(match)
      }
      return results
        .map((match) => {
          return {
            marker: str[match.index],
            index: match.index
          }
        })
        .reduceRight((all, cur) => {
          let chunk = str.substring(cur.index, all.length ? all[all.length - 1].index : str.length);
          return all.concat([{
            marker: cur.marker,
            index: cur.index,
            chunk: (chunk.length > 0) ? chunk.substr(1, chunk.length - 1) : chunk
          }])
        }, [])
        .reverse()
        .map((command) => {
          let values = command.chunk.match(digitRegEx);
          return {
            marker: command.marker,
            values: values ? values.map(parseFloat) : []
          };
        })
    }

完成了如上的步骤后,图形基础绘制出来了,然则在后期,涌现了 svg image 位置的题目。svg 中的图片除了会有 x, y 坐标关联,还会根据视窗大小,以短边为准,坚持宽高比,长边做缩放,视窗中居中显现。这是我之前不清楚的部份,为此多花了点时候和精神。另外,还有些细节须要注重,比方须要调解 canvas缩放比例,以让图形完整显现。

总结

以上,就是我在开辟“小顺序”中对 htmlsvg 做的一些“转换”的阅历。总结起来就是,对字符串剖析,转换成“小顺序”言语。在此延长一下,如需在 wxml 中支撑 wxml 字符串,借助 htmlParser 做剖析,再写一个 wxml 模板,我们也就能够“转换” wxml

参考

    原文作者:nicksite
    原文地址: https://segmentfault.com/a/1190000008965265
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞