文字列からテーブルを作る

配列からテーブルを作る

<!DOCTYPE html>
<meta charset="utf-8">
<title></title>

<style>
  .red { color: red;}
</style>

<body>
<table border="1" id="TB"></table>


<script>


function ary2thead (ary = [ ], thead = document.createElement ('thead')) {

  const
    textSplitStr = (
        th = '(#)?', //文字列の先頭が "#" なら th要素とするための判別
        rowSpan = '(?:r([2-9]|[1-9][0-9]+)|r[01]|r0[0-9]+)?',//rowSpan の2以上を対象として取得
        colSpan = '(?:c([2-9]|[1-9][0-9]+)|c[01]|c0[0-9]+)?',//colSpan の2以上を対象として取得
        attr = '(?:\\{(.+)?\\})?',//attribute として { ... } 括弧の内側を文字列として取得
        text = '\\s*(.*)\\s*', //前後の空白を取り除いて取得
        ctrlCom = '(?:!(?=[rc\\{])'+ rowSpan + colSpan + attr + ')?'
      )=> '^' + th + ctrlCom + text + '$',
    
    attrSplitStr = '\\s*(.+?)\\s*\\:\\s*(.*?)\\s*(?:,|;|$)',
    rows = [ ];


  for (let row of ary) {
    let tds = [];
    for (let cell of row) {
      let
        splitter = new RegExp (textSplitStr (), 'gi');
        [, th, rowSpan, colSpan, attr, textContent = ''] = splitter.exec (cell) ?? [ ],
        prop = new Map;

      if (rowSpan)
        prop.set ('rowSpan', rowSpan);
      if (colSpan)
        prop.set ('colSpan', colSpan);
      
      if (attr)
        for (let a, splitter = new RegExp (attrSplitStr, 'gi'); a = splitter.exec (attr);  )
            prop.set (a[1],a[2]);

      prop.set ('textContent', textContent);

      tds.push (Object.assign (document.createElement (th ? 'th': 'td'), Object.fromEntries (prop)));
    }
    rows.push (tds);
  }

  rows.reduce ((a, b)=>(a.insertRow ().append (...b), a), thead);
  return thead;
}


const RECS = `

#!R2C1 No	#!r2 Name	#!r1c2 INFO
#Age	#ETC

#1	akio	57	abc
#!{id:sss,className: red}2	ayako	56	def



 `;

const csv2ary = csv => csv.split (/r\n|\r|\n/g).reduce ((a,b)=>b.trim()?[...a,b]:a,[]).map (row=> row.split (/\t/g));

ary2thead (csv2ary (RECS), TB);

</script>

ショートコーディングしてみる

<!DOCTYPE html>
<meta charset="utf-8">
<title></title>

<style>
  .red { color: red;}
</style>

<body>
<table border="1" id="TB"></table>


<script>


function ary2tbody (recs = [ ], tbody = document.createElement ('tbody')) {
  function* splitter(a,b=new RegExp(attrSplitStr,'g'),c){while(c=b.exec(a))yield c.slice(1)}
  const
    addObj = (a,[b,c])=>(a[b]=c,a),
    delObj = (a,[b,c])=>((undefined===c)&&delete a[b],a),
    makeTD = (a)=>{
      let
        [, th, rowSpan, colSpan, attr, textContent] = (new RegExp (textSplitStr, 'i')).exec (a) ?? [ ],//文字列を分解
      prop = [...splitter (attr)].reduce (addObj, { rowSpan, colSpan, textContent });//要素の属性

      return Object.assign (
        document.createElement (th ? 'th': 'td'), //フラグとしての th により生成
        Object.entries (prop).reduce (delObj, prop) // prop オブジェクトから値が無いものは削除する 
      )
    },
    attrSplitStr = '\\s*(.+?)\\s*\\:\\s*(.*?)\\s*(?:,|;|$)',//文字列から属性値を切り出す "key:value, key2:value2, ..."
    textSplitStr = //#!r2c3{className:red} text
    '^' + '(#)?' + //th 文字列の先頭が "#" なら th要素とするための判別
    '(?:!(?=[rc{])'+ //制御文字は !で始まり以下の順で記述する
        '(?:r([2-9]|[1-9][0-9]+)|r[01]|r0[0-9]+)?' + //rowSpan の2以上を対象として取得
      '(?:c([2-9]|[1-9][0-9]+)|c[01]|c0[0-9]+)?' + //colSpan の2以上を対象として取得
      '(?:\\{(.+)?\\})?' + //{attr: 123, attr2: 456} の用に属性を取得する
    ')?' +
    '\\s*(.*)\\s*' + '$';//textContent 前後の空白を取り除いて取得

  recs.forEach (row=> tbody.insertRow ().append (...row.map (makeTD)));
  return tbody;
}


const RECS = `

#!R2C1 No  #!r2 Name  #!r1c2 INFO
#Age  #ETC

#1  akio  57  abc
#!{id:sss,className: red}2  ayako  56  def



 `;

const csv2ary = csv => csv.split (/r\n|\r|\n/g).reduce ((a,b)=>b.trim()?[...a,b]:a,[]).map (row=> row.split (/\t/g));

ary2thead (csv2ary (RECS), TB);

</script>

さらにショート!

<!DOCTYPE html>
<meta charset="utf-8">
<title></title>

<style>
  .red { color: red;}
</style>

<body>
<table border="1" id="TB"></table>


<script>
const ary2tbody=(a=[],b=document.createElement('tbody'))=>{
function*c(a,b=new RegExp('\\s*(.+?)\\s*\\:\\s*(.*?)\\s*(?:,|;|$)','g'),c){while(c=b.exec(a))yield c.slice(1)}
const d=(a,[b,c])=>(a[b]=c,a),e=(a,[b,c])=>((undefined===c)&&delete a[b],a),f=g=>{let[,h,i,j,k,l]=(new RegExp('^(#)?(?:!(?=[rc{])(?:r([2-9]|[1-9][0-9]+)|r[01]|r0[0-9]+)?(?:c([2-9]|[1-9][0-9]+)|c[01]|c0[0-9]+)?(?:\\{(.+)?\\})?)?\\s*(.*)\\s*$','i')).exec(g)??[],m=[...c(k)].reduce(d,{rowSpan:i,colSpan:j,textContent:l});return Object.assign(document.createElement(h?'th':'td'),Object.entries(m).reduce(e,m))};
a.forEach(c=>b.insertRow().append(...c.map(f)));return b}


const RECS = `

#!R2C1 No	#!r2 Name	#!r1c2 INFO
#Age	#ETC

#1	akio	57	abc
#!{id:sss,className: red}2	ayako	56	def



 `;

 const csv2ary = csv => csv.split (/r\n|\r|\n/g).reduce ((a,b)=>b.trim()?[...a,b]:a,[]).map (row=> row.split (/\t/g));

ary2tbody (csv2ary (RECS), TB);


</script>

再考して更に短く

const
  ary2tbody=(a=[],b=document.createElement('tbody'))=>{
  let A='^(#)?(?:!(?=[rc\\{])(?:r([2-9]|[1-9]\\d+)|r[01]|r0\\d+)?(?:c([2-9]|[1-9]\\d+)|c[01]|c0\\d+)?(?:\\{(.+)?\\})?)?\\s*(.*)\\s*$',B=RegExp,C=Object;  
  a.map(c=>{b.insertRow().append(...c.map(d=>{let[,z,y,x,w,v]=(new B(A,'i')).exec(d)??[],u={rowSpan:y,colSpan:x,textContent:v};
  C.entries(u).map(a=>a[1]===undefined&&delete u[a[0]]);for(let a,b=new B('\\s*(.+?)\\s*\\:\\s*(.*?)\\s*(,|;|$)','g');a=b.exec(w);u[a[1]]=a[2]);
  return C.assign(document.createElement(z?'th':'td'),u)}))});return b};