#include #define FIO_STR_NAME render_result #include "fio-stl.h" #include "isomorfeus_preact_ext.h" static ID id_ancestors; 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_freeze; 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_include; static ID id_is_a; static ID id_is_renderable; static ID id_key; static ID id_keyq; static ID id_keys; static ID id_match; static ID id_merge; static ID id_new; 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_start_withq; static ID id_state; static ID id_style; static ID id_style_obj_to_css; 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_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_aria; 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; static VALUE cProc; static VALUE cRegexp; extern VALUE cVNode; static VALUE rXlink; static VALUE rUnsafe; static VALUE rVoid; static VALUE rXlink2; int set_default_prop_value(VALUE prop, VALUE value, VALUE props) { if ((rb_funcall(value, id_keyq, 1, sym_default) == Qtrue) && (rb_funcall(props, id_keyq, 1, prop) == Qfalse)) { rb_hash_aset(props, props, rb_hash_lookup(value, sym_default)); } return 0; } 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_funcall(rXlink, id_match, 1, 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 SYM2ID(rb_to_symbol(name_s)); } } return name_id; } VALUE normalize_prop_value(ID name_id, VALUE name_s, VALUE value, VALUE self) { if (name_id == id_style && value != Qnil && TYPE(value) == T_HASH) { return rb_funcall(self, id_style_obj_to_css, 1, value); } // name[0] === 'a' && name[1] === 'r' if (value == Qtrue && rb_funcall(name_s, id_start_withq, 1, s_aria) == Qtrue) { // 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 && rb_funcall(name_s, id_start_withq, 1, s_aria) == Qtrue) { // 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; } VALUE get_context(VALUE node_type, VALUE context) { VALUE ctx_type = rb_funcall(node_type, id_context_type, 0); VALUE provider = Qnil; VALUE cctx = context; if (ctx_type != Qnil) { provider = rb_hash_lookup(context, rb_funcall(ctx_type, id_context_id, 0)); if (provider != Qnil) { cctx = rb_hash_lookup(rb_funcall(provider, id_props, 0), sym_value); } else { cctx = rb_funcall(ctx_type, id_value, 0); } } return cctx; } VALUE render_class_component(VALUE vnode, VALUE context) { VNode *v = (VNode *)DATA_PTR(vnode); VALUE node_type = v->type; VALUE props = v->props; VALUE state = rb_hash_new(); // 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_funcall(props, id_freeze, 0); // instantiate component VALUE c = rb_funcall(node_type, id_new, 2, props, cctx); v->component = c; // set state, props, context rb_ivar_set(c, iv_props, props); if (rb_funcall(c, id_state, 0) == Qnil) { rb_ivar_set(c, iv_state, state); } if (rb_ivar_get(c, iv_next_state) == Qnil) { rb_ivar_set(c, iv_next_state, rb_funcall(c, id_state, 0)); } 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_funcall(c, id_state, 0); 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_funcall(rb_funcall(c, id_state, 0), id_freeze, 0); // 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, render_result_s *rres) { VNode *v; VALUE res; VALUE node_type, props; int i, len; if (vnode == Qnil || vnode == Qfalse || vnode == Qtrue || ((TYPE(vnode) == T_STRING) && RSTRING_LEN(vnode) == 0)) { return; } switch (TYPE(vnode)) { case T_STRING: res = rb_funcall(self, id_encode_entities, 1, vnode); render_result_write(rres, RSTRING_PTR(res), RSTRING_LEN(res)); return; case T_FIXNUM: case T_BIGNUM: res = rb_obj_as_string(vnode); render_result_write(rres, RSTRING_PTR(res), RSTRING_LEN(res)); 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; } v = (VNode *)DATA_PTR(vnode); node_type = v->type; props = v->props; if (TYPE(node_type) != T_STRING) { // components and Fragments VALUE rendered; VALUE ancestors = rb_funcall(node_type, id_ancestors, 0); rb_funcall(ancestors, id_include, 1, cFragment); if (RARRAY_PTR(ancestors)[0] == cFragment) { rendered = rb_hash_lookup(props, sym_children); } else { rendered = render_class_component(vnode, context); 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; } props = v->props; // render JSX to HTML VALUE node_type_s = rb_obj_as_string(node_type); render_result_write(rres, "<", 1); render_result_write(rres, RSTRING_PTR(node_type_s), RSTRING_LEN(node_type_s)); VALUE children = Qnil; VALUE html = Qnil; if (props != Qnil) { 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]; if (TYPE(name) == T_STRING) { name = rb_to_symbol(name); } ID name_id = SYM2ID(name); VALUE value = rb_hash_lookup(props, 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 name_s = rb_id2str(name_id); if (rb_funcall(rUnsafe, id_match, 1, name_s) == Qtrue) { continue; } name_id = normalize_prop_name(name_id, is_svg_mode); 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_s), str_textarea) == 0) { // children = value; } else if (value != Qnil && value != Qfalse && rb_funcall(value, id_is_a, 1, cProc) != 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; render_result_write(rres, " ", 1); render_result_write(rres, RSTRING_PTR(name_s), RSTRING_LEN(name_s)); continue; } if (name_id == id_value) { if (strcmp(RSTRING_PTR(node_type_s), str_select) == 0) { select_value = value; continue; } else if ( // If we're looking at an