introduce

“Convert” means to convert something that the applet does not support into something that it does. In the course of developing applets, I encountered two scenarios where I needed to “transform” :

  • htmlConverted towxml
  • svgConverted tocanvas

I’ll describe how I handled both cases in more detail below.

htmlConverted towxml

In some cases, the back-end interface of our product passes HTML strings directly to the front-end. In ReactJs, we can render HTML strings directly with dangerouslySetInnerHTML (not necessarily safe), and applet does not support HTML, so it must be processed. The main steps to solve this problem are: 1. Convert HTML to JSON (tree structure); 2. Convert JSON to WXML. After researching the problem, I found that there was an existing library, wxParse, that met the purpose of the transformation, but it seemed to me that the library did too many things, relied on too many files, and didn’t meet the need for simple processing, so I decided to write it myself.

htmlConverted tojson

On the basis of referring to html2JSON and Himalaya, I wrote a simple parsing library htmlParser. HtmlParser processes HTML strings in two steps:

lexer: Generate markup (token)

function lex(html) { let string = html let tokens = [] while (string) { if (string.indexOf("< /") === 0) { const match =  string.match(REGEXP.endTag) if (! match) continue 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 } 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 }Copy the code

parser: Spanning tree based on tags


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
}

Copy the code

Please stamp htmlParser for full code

jsonConverted towxml

After familiarizing myself with the “applet” framework, I found that I needed to use the template, fill it with JSON data, and render the corresponding WXML component based on the element type for conversion purposes. And because templates don’t have the ability to reference themselves, you have to use a clunky solution: multiple templates with the same content but different template names to resolve the nested hierarchy, which depends on the number of templates used.

< 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 Reference HTML1 Two templates are the same. < 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>Copy the code

In the above process, there are some details that need to be paid attention to, such as converting HTML entity characters, making the template image component support mode, and so on. In summary, after the above processing, the basic function of converting HTML strings to WXML components is complete.

svgConverted tocanvas

In the Web version of our product, since we need to use SVG as a DOM element in page elements and the “applet” does not have SVG component support, we also need to convert SVG strings from the back-end interface. The “applet” has no SVG component but a Canvas component, so I decided to use Canvas to simulate SVG to draw graphics and modify the graphics to meet basic needs.

The key to doing this “transformation” is also two-fold: 1. Extract elements from an SVG string; 2. Canvas simulates element functions for drawing

svgExtraction of elements

Since the SVG string is XML, htmlParser above can generate JSON from it, and the problem is solved.

canvasSimulated draw

There are many SVG elements in the Web, but fortunately we only need some basic elements: image, rect, path. Rect is not difficult to simulate with canvas, canvas is very simple to draw, the code is as follows:


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

However, during the development process, there was a difficulty: I did not know how to simulate the D property of path. The d attribute involves movement, Bezier curves, and so on. To draw with Canvas, you need to know the meaning of each attribute value of SVG D. Therefore, it is necessary to extract the attribute values of D first, and then draw them in turn according to the corresponding drawing relationship between each attribute value and canvas. When I am puzzled by this, it is good to find that there are predecessors who have done such a thing. Gist, found the analysis of D, with canvas drawing d related code, difficult problems have been solved.

/** * SVG path * < path d="c 50,0 50,100 100,100 50,0 50,-100 100,-100 100-100, "https://gist.github.com/shamansir/0ba30dc262d54d04cd7f79e03b281505 * * * the following code in the source code based on the modified * / _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) : [] }; })}Copy the code

After completing the above steps, the graph is basically drawn, but in the later stage, there is a problem with the position of SVG image. In addition to the x and Y coordinate relationships, SVG images will also be displayed in the center of the window according to the size of the window, keeping the width to height ratio based on the short side, scaling the long side, and displaying in the middle of the window. This is the part that I didn’t know, so it took me a little bit more time and effort. There are also some details that need to be taken care of, such as the need to adjust the canvas zoom to make the graphics fully visible.

conclusion

Above, is my “small program” in the development of HTML and SVG to do some “conversion” after. To sum up, string parsing is translated into a “small program” language. To extend this, if you want to support WXML strings in WXML, write a WXML template with htmlParser to “convert” WXML.

reference

  • Applets official documentation
  • wxParse
  • html2json
  • himalaya
  • SVG D attribute value parsing