見出し画像

TypeScriptで見出しや目次を手軽に作成!ハンディなniceTree関数の紹介

こんにちわ。nap5です。

TypeScriptで見出しや目次を手軽に作成できるハンディなniceTree関数の紹介です。

入力データの形式は以下になります。トップレベルの階層はレベルを1にし、サブアイテム管理したい場合はレベルを直前のアイテムのレベルに1加算したものにします。

  const input: Section[] = [
    { level: 1, text: 'プロローグ: 新たなる挑戦へ' },
    { level: 1, text: '1章: 打ち上げ準備 - エンジンと航行システム' },
    { level: 2, text: '1.1 サターンVロケットの威力' },
    { level: 2, text: '1.2 航行コンピュータと通信システム' },
    { level: 1, text: '2章: 宇宙への旅立ち - 月への道のり' },
    { level: 2, text: '2.1 地球から月への軌道計算' },
    { level: 2, text: '2.2 宇宙船内での生活' },
    { level: 1, text: '3章: 月面着陸 - 歴史的瞬間' },
    { level: 2, text: '3.1 ルナーモジュールの役割' },
    { level: 2, text: '3.2 月面歩行とサンプル採取' },
    { level: 3, text: '3.2.1 月の土壌と岩石の分析' },
    { level: 3, text: '3.2.2 月からの帰還準備' },
    { level: 1, text: 'エピローグ: 宇宙探査の未来へ' },
  ]


実装です。

export type Section = {
  level: number;
  text: string;
  children?: Section[];
}

/**
 * @remarks トップレベルの階層はレベルを1にし、直前のアイテムのchildrenとして追加したい場合はレベルを直前のアイテムのレベルに1加算したものにします
 * 
 * @param sections 
 * @returns 階層化されたセクションデータ
 */
export function niceTree(sections: Section[]): Section[] {
  const visited = new Set()
  const buildTree = (parentLevel: number): Section[] => {
    const result: Section[] = []
    for (const section of sections) {
      if (visited.has(section)) continue
      if (section.level <= parentLevel) break
      visited.add(section)
      result.push({ ...section, children: buildTree(section.level) })
    }
    return result
  }
  return buildTree(0)
}


使用側です。

import { test, expect, describe } from 'vitest'
import { Section, niceTree } from '.'

describe('Space Sections -> niceTree', () => {
  const input: Section[] = [
    { level: 1, text: 'プロローグ: 新たなる挑戦へ' },
    { level: 1, text: '1章: 打ち上げ準備 - エンジンと航行システム' },
    { level: 2, text: '1.1 サターンVロケットの威力' },
    { level: 2, text: '1.2 航行コンピュータと通信システム' },
    { level: 1, text: '2章: 宇宙への旅立ち - 月への道のり' },
    { level: 2, text: '2.1 地球から月への軌道計算' },
    { level: 2, text: '2.2 宇宙船内での生活' },
    { level: 1, text: '3章: 月面着陸 - 歴史的瞬間' },
    { level: 2, text: '3.1 ルナーモジュールの役割' },
    { level: 2, text: '3.2 月面歩行とサンプル採取' },
    { level: 3, text: '3.2.1 月の土壌と岩石の分析' },
    { level: 3, text: '3.2.2 月からの帰還準備' },
    { level: 1, text: 'エピローグ: 宇宙探査の未来へ' },
  ]

  test('木構造データができること', () => {
    const outputData = niceTree(input)
    expect(outputData).toStrictEqual([
      {
        level: 1,
        text: 'プロローグ: 新たなる挑戦へ',
        children: [],
      },
      {
        level: 1,
        text: '1章: 打ち上げ準備 - エンジンと航行システム',
        children: [
          {
            level: 2,
            text: '1.1 サターンVロケットの威力',
            children: [],
          },
          {
            level: 2,
            text: '1.2 航行コンピュータと通信システム',
            children: [],
          },
        ],
      },
      {
        level: 1,
        text: '2章: 宇宙への旅立ち - 月への道のり',
        children: [
          {
            level: 2,
            text: '2.1 地球から月への軌道計算',
            children: [],
          },
          {
            level: 2,
            text: '2.2 宇宙船内での生活',
            children: [],
          },
        ],
      },
      {
        level: 1,
        text: '3章: 月面着陸 - 歴史的瞬間',
        children: [
          {
            level: 2,
            text: '3.1 ルナーモジュールの役割',
            children: [],
          },
          {
            level: 2,
            text: '3.2 月面歩行とサンプル採取',
            children: [
              {
                level: 3,
                text: '3.2.1 月の土壌と岩石の分析',
                children: [],
              },
              {
                level: 3,
                text: '3.2.2 月からの帰還準備',
                children: [],
              },
            ],
          },
        ],
      },
      {
        level: 1,
        text: 'エピローグ: 宇宙探査の未来へ',
        children: [],
      },
    ])
  })
})


demo code.


簡単ですが、以上です。


この記事が気に入ったらサポートをしてみませんか?