github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/jsx/jsx.go (about)

     1  /*
     2  
     3  Package jsx allows you to render blocks of HTML as myitcv.io/react elements.
     4  It is a temporary runtime solution for what will become a compile-time
     5  transpilation, much like JSX's relationship with Javascript.
     6  
     7  For more information see https://github.com/myitcv/react/wiki
     8  
     9  */
    10  package jsx
    11  
    12  import (
    13  	"fmt"
    14  	"strings"
    15  
    16  	"myitcv.io/react"
    17  
    18  	"github.com/russross/blackfriday"
    19  
    20  	"golang.org/x/net/html"
    21  	"golang.org/x/net/html/atom"
    22  )
    23  
    24  var htmlCache = make(map[string][]react.Element)
    25  
    26  // HTML is a runtime JSX-like parsereact. It parses the supplied HTML string into
    27  // myitcv.io/react element values. It exists as a stop-gap runtime solution to
    28  // full JSX-like support within the GopherJS compilereact. It should only be used
    29  // where the argument is a compile-time constant string (TODO enforce this
    30  // within reactVet). HTML will panic in case s cannot be parsed as a valid HTML
    31  // fragment
    32  //
    33  func HTML(s string) []react.Element {
    34  	s = strings.TrimSpace(s)
    35  
    36  	if v, ok := htmlCache[s]; ok {
    37  		return v
    38  	}
    39  
    40  	// a dummy div for parsing the fragment
    41  	div := &html.Node{
    42  		Type:     html.ElementNode,
    43  		Data:     "div",
    44  		DataAtom: atom.Div,
    45  	}
    46  
    47  	elems, err := html.ParseFragment(strings.NewReader(s), div)
    48  	if err != nil {
    49  		panic(fmt.Errorf("failed to parse HTML %q: %v", s, err))
    50  	}
    51  
    52  	var toParse []*html.Node
    53  	var toWalk []*html.Node
    54  
    55  	for _, v := range elems {
    56  		if v.Type == html.TextNode && strings.TrimSpace(v.Data) == "" {
    57  			continue
    58  		}
    59  		toParse = append(toParse, v)
    60  		toWalk = append(toWalk, v)
    61  	}
    62  
    63  	var v *html.Node
    64  
    65  	for len(toWalk) > 0 {
    66  		v, toWalk = toWalk[0], toWalk[1:]
    67  
    68  		c := v.FirstChild
    69  
    70  		for c != nil {
    71  			if c.Type == html.TextNode && strings.TrimSpace(c.Data) == "" {
    72  				v.RemoveChild(c)
    73  			}
    74  
    75  			toWalk = append(toWalk, c)
    76  			c = c.NextSibling
    77  		}
    78  	}
    79  
    80  	var res []react.Element
    81  
    82  	for _, v := range toParse {
    83  		res = append(res, parse(v))
    84  	}
    85  
    86  	htmlCache[s] = res
    87  
    88  	return res
    89  }
    90  
    91  // HTMLElem is a convenience wrapper around HTML where only a single root
    92  // element is expected. HTMLElem will panic if more than one HTML element
    93  // results
    94  //
    95  func HTMLElem(s string) react.Element {
    96  	res := HTML(s)
    97  
    98  	if v := len(res); v != 1 {
    99  		panic(fmt.Errorf("expected single element result from %q; got %v", s, v))
   100  	}
   101  
   102  	return res[0]
   103  }
   104  
   105  // Markdown is a runtime JSX-like parser for markdown. It parses the supplied
   106  // markdown string into an HTML string and then hands off to the HTML function.
   107  // Like the HTML function, it exists as a stop-gap runtime solution to full
   108  // JSX-like support within the GopherJS compilereact. It should only be used where
   109  // the argument is a compile-time constant string (TODO enforce this within
   110  // reactVet). Markdown will panic in case the markdown string s results in an
   111  // invalid HTML string
   112  //
   113  func Markdown(s string) []react.Element {
   114  
   115  	h := blackfriday.MarkdownCommon([]byte(s))
   116  
   117  	return HTML(string(h))
   118  }