#include "vnode.h" #include #include #include rb_encoding *utf8_encoding; static ID id_checked; static ID id_children; static ID id_class; static ID id_class_name; static ID id_class_name2; static ID id_context_id; static ID id_context_type; static ID id_dangerously_set_inner_html; static ID id_default_checked; static ID id_default_selected; static ID id_default_value; static ID id_development; static ID id_downcase; static ID id_encode_entities; static ID id_eqeq; static ID id_for; static ID id_get_child_context; static ID id_get_derived_state_from_props; static ID id_gsub; static ID id_html_for; static ID id_key; static ID id_keys; static ID id_merge; static ID id_props; static ID id_ref; static ID id_render; static ID id_render_buffer; static ID id_selected; static ID id_self; static ID id_source; static ID id_style; static ID id_style_obj_to_css; static ID id_to_s; static ID id_validate_props; static ID id_value; static ID iv_context; static ID iv_declared_props; static ID iv_next_state; static ID iv_props; static ID iv_state; static char *str_aria = "aria-"; static char *str_data = "data-"; static char *str_foreign_object = "foreignObject"; static char *str_option = "option"; static char *str_select = "select"; static char *str_selected = "selected"; static char *str_svg = "svg"; static char *str_textarea = "textarea"; static char *str_rve = "^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$"; static char *str_run = "[\\s\\n\\\\\\/='\"\\0<>]"; static char *str_rxl = "^xlink:?."; static char *str_rx2 = "^xlink:?"; // static char *str_ree = "[\"&<]"; static VALUE s_false; static VALUE s_true; static VALUE s_xlink; static VALUE sym_children; static VALUE sym_class; static VALUE sym_default; static VALUE sym_for; static VALUE sym_html; static VALUE sym_key; static VALUE sym_ref; static VALUE sym_selected; static VALUE sym_value; static VALUE mIsomorfeus; static VALUE mPreact; static VALUE cFragment; extern VALUE cVNode; static VALUE rXlink; static VALUE rUnsafe; static VALUE rVoid; static VALUE rXlink2; static int set_default_prop_value(VALUE prop, VALUE value, VALUE props) { VALUE def_val = rb_hash_lookup(value, sym_default); if ((def_val != Qnil) && (rb_hash_lookup(props, prop) == Qnil)) rb_hash_aset(props, prop, def_val); return 0; } static ID normalize_prop_name(ID name_id, VALUE is_svg_mode) { if (name_id == id_class_name || name_id == id_class_name2) return id_class; if (name_id == id_html_for) return id_for; if (name_id == id_default_value) return id_value; if (name_id == id_default_checked) return id_checked; if (name_id == id_default_selected) return id_selected; if (is_svg_mode == Qtrue) { VALUE name_s = rb_id2str(name_id); if (rb_reg_match(rXlink, name_s) == Qtrue) { name_s = rb_funcall(name_s, id_downcase, 0); name_s = rb_funcall(name_s, id_gsub, 2, rXlink2, s_xlink); return rb_intern_str(name_s); } } return name_id; } static VALUE normalize_prop_value(ID name_id, VALUE name_s, VALUE value, VALUE self) { if (name_id == id_style && TYPE(value) == T_HASH) return rb_funcall(self, id_style_obj_to_css, 1, value); if (value == Qtrue && RSTRING_LEN(name_s) > 4) { if ((0 == strncmp(RSTRING_PTR(name_s), str_aria, 5)) || (0 == strncmp(RSTRING_PTR(name_s), str_data, 5))) { // always use string values instead of booleans for aria attributes // also see https://github.com/preactjs/preact/pull/2347/files return s_true; } } if (value == Qfalse && RSTRING_LEN(name_s) > 4) { if ((0 == strncmp(RSTRING_PTR(name_s), str_aria, 5)) || (0 == strncmp(RSTRING_PTR(name_s), str_data, 5))) { // always use string values instead of booleans for aria attributes // also see https://github.com/preactjs/preact/pull/2347/files return s_false; } } return value; } static VALUE get_context(VALUE node_type, VALUE context) { VALUE ctx_type = rb_funcall(node_type, id_context_type, 0); if (ctx_type != Qnil) { VALUE provider = rb_hash_lookup(context, rb_funcall(ctx_type, id_context_id, 0)); if (provider != Qnil) return rb_hash_lookup(rb_funcall(provider, id_props, 0), sym_value); else return rb_funcall(ctx_type, id_value, 0); } return context; } static VALUE render_class_component(VALUE vnode, VALUE context, VNode *v) { VALUE node_type = v->type; VALUE props = v->props; // get context VALUE cctx = get_context(node_type, context); // validate props VALUE declared_props = rb_ivar_get(node_type, iv_declared_props); if (declared_props != Qnil) { rb_hash_foreach(declared_props, set_default_prop_value, props); if (rb_funcall(mIsomorfeus, id_development, 0) == Qtrue) rb_funcall(node_type, id_validate_props, 1, props); } // freeze props rb_obj_freeze(props); // instantiate component const VALUE cargs[2] = {props, cctx}; VALUE c = rb_class_new_instance(2, (const VALUE *)&cargs, node_type); v->component = c; // set state, props, context rb_ivar_set(c, iv_props, props); if (rb_ivar_get(c, iv_state) == Qnil) rb_ivar_set(c, iv_state, rb_hash_new()); if (rb_ivar_get(c, iv_next_state) == Qnil) rb_ivar_set(c, iv_next_state, rb_ivar_get(c, iv_state)); rb_ivar_set(c, iv_context, cctx); // get_derived_state_from_props if (rb_respond_to(c, id_get_derived_state_from_props)) { VALUE state = rb_ivar_get(c, iv_state); VALUE res = rb_funcall(c, id_get_derived_state_from_props, 2, rb_funcall(c, id_props, 0), state); rb_ivar_set(c, iv_state, rb_funcall(state, id_merge, res)); } // freeze state rb_obj_freeze(rb_ivar_get(c, iv_state)); // render return rb_funcall(c, id_render, 0); } void internal_render_to_string(VALUE vnode, VALUE context, VALUE is_svg_mode, VALUE select_value, VALUE self, VALUE rres) { size_t i, len; if (vnode == Qnil || vnode == Qfalse || vnode == Qtrue) return; switch (TYPE(vnode)) { case T_STRING: if (RSTRING_LEN(vnode) == 0) return; rb_str_buf_append(rres, rb_funcall(self, id_encode_entities, 1, vnode)); return; case T_FIXNUM: case T_BIGNUM: rb_str_buf_append(rres, rb_funcall(vnode, id_to_s, 0)); return; case T_ARRAY: len = RARRAY_LEN(vnode); for (i = 0; i < len; i++) { internal_render_to_string(RARRAY_PTR(vnode)[i], context, is_svg_mode, select_value, self, rres); } return; } VNode *v = (VNode *)DATA_PTR(vnode); VALUE node_type = v->type; VALUE props = v->props; if (TYPE(node_type) != T_STRING) { // Components and Fragments VALUE rendered = Qnil; if (node_type == cFragment && RHASH_SIZE(props) > 0) { rendered = rb_hash_lookup(props, sym_children); } else { rendered = render_class_component(vnode, context, v); VALUE component = v->component; if (rb_respond_to(component, id_get_child_context)) { context = rb_funcall(context, id_merge, 1, rb_funcall(component, id_get_child_context, 0)); } } internal_render_to_string(rendered, context, is_svg_mode, select_value, self, rres); return; } // render JSX to HTML rb_str_cat(rres, "<", 1); rb_str_buf_append(rres, node_type); VALUE children = Qnil; VALUE html = Qnil; if (props != Qnil && RHASH_SIZE(props) > 0) { children = rb_hash_lookup(props, sym_children); VALUE attrs = rb_funcall(props, id_keys, 0); len = RARRAY_LEN(attrs); for (i = 0; i < len; i++) { VALUE name = RARRAY_PTR(attrs)[i]; ID name_id = SYM2ID(name); VALUE value = rb_hash_lookup(props, rb_to_symbol(name)); if (name_id == id_key || name_id == id_ref || name_id == id_self || name_id == id_source || name_id == id_children || ((name_id == id_class_name || name_id == id_class_name2) && (rb_hash_lookup(props, sym_class) != Qnil)) || ((name_id == id_html_for) && (rb_hash_lookup(props, sym_for) != Qnil))) continue; VALUE orig_name_id = name_id; VALUE name_s = rb_id2str(name_id); if (rb_reg_match(rUnsafe, name_s) == Qtrue) continue; name_id = normalize_prop_name(name_id, is_svg_mode); if (name_id != orig_name_id) name_s = rb_id2str(name_id); value = normalize_prop_value(name_id, name_s, value, self); if (name_id == id_dangerously_set_inner_html && value != Qnil) { html = rb_hash_lookup(value, sym_html); } else if (name_id == id_value && strcmp(RSTRING_PTR(node_type), str_textarea) == 0) { // children = value; } else if (value != Qnil && value != Qfalse && rb_obj_is_proc(value) != Qtrue) { if (value == Qtrue || ((TYPE(value) == T_STRING) && (RSTRING_PTR(value)[0] == 0))) { // if (name_id == id_class || name_id == id_style) { continue; } value = name_s; rb_str_cat(rres, " ", 1); rb_str_buf_append(rres, name_s); continue; } if (name_id == id_value) { if (strcmp(RSTRING_PTR(node_type), str_select) == 0) { select_value = value; continue; } else if ( // If we're looking at an