In that component "output-console.component.html (1-89)


@if (dataEntries() && dataEntries().length > 0) {
  <div class="d-flex justify-content-between mb-2">
    <button
      (click)="showRawData.set(!showRawData())"
      mat-flat-button
      color="primary"
    >
      @if (showRawData()) {
        Show Beautified Data
      } @else {
        Show Raw Data
      }
    </button>

    <button (click)="onCopyToClipboard()" mat-flat-button color="primary">
      Copy to Clipboard
    </button>
  </div>

  <div class="border bg-body-secondary console">
    <samp>
      @switch (detectedLanguage()) {
        @case ("json") {
          <div class="d-flex flex-column gap-4">
            @for (data of parsedJson(); track data) {
              @if (showRawData()) {
                <pre><code>{{ data }}</code></pre>
              } @else {
                <ngx-json-viewer class="bg-white" [json]="data" />
              }
            }
          </div>
        }
        @case ("xml") {
          @for (data of dataEntries(); track data) {
            @if (showRawData()) {
              <pre><code>{{ data }}</code></pre>
            } @else {
              <pre class="xml-pre">
                <code [highlight]="data | prettyXml" language="xml"></code>
              </pre>
            }
          }
        }
        @case ("csv") {
          @if (showRawData()) {
            <pre><code>{{ dataEntries().join('\n') }}</code></pre>
          } @else {
            <table class="table table-striped">
              <thead>
                <tr>
                  @for (header of getCsvHeaders(parsedCsv()[0]); track header) {
                    <th>
                      {{ header }}
                    </th>
                  }
                </tr>
              </thead>
              <tbody>
                @for (row of parsedCsv(); track row) {
                  <tr>
                    @for (
                      header of getCsvHeaders(parsedCsv()[0]);
                      track header
                    ) {
                      <td>
                        @if (isObject(row[header])) {
                          <div [innerHTML]="getNestedTable(row[header])"></div>
                        } @else {
                          {{ row[header] }}
                        }
                      </td>
                    }
                  </tr>
                }
              </tbody>
            </table>
          }
        }
        @default {
          @for (data of dataEntries(); track data) {
            <pre><code>{{ data }}</code></pre>
          }
        }
      }
    </samp>
  </div>
}


output-console.component.ts (1-185)


import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
  input,
  signal,
} from '@angular/core';
import { Highlight } from 'ngx-highlightjs';
import { HighlightLineNumbers } from 'ngx-highlightjs/line-numbers';
import { PrettyJsonPipe } from '../shared/pipes/pretty-json.pipe';
import { PrettyXmlPipe } from '../shared/pipes/pretty-xml.pipe';
import { NgxJsonViewerModule } from 'ngx-json-viewer';
import Papa from 'papaparse';
import { MatButtonModule } from '@angular/material/button';
import { Clipboard } from '@angular/cdk/clipboard';

@Component({
  selector: 'app-output-console',
  standalone: true,
  imports: [
    CommonModule,
    Highlight,
    PrettyJsonPipe,
    PrettyXmlPipe,
    NgxJsonViewerModule,
    MatButtonModule,
    HighlightLineNumbers,
  ],
  templateUrl: './output-console.component.html',
  styleUrls: ['./output-console.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OutputConsoleComponent {
  private readonly clipboard = inject(Clipboard);

  public consoleData = input.required<string[]>();

  public showRawData = signal<boolean>(false);

  public dataEntries = computed(() => {
    let entries: string[] = [];
    for (let dataString of this.consoleData()) {
      entries.push(
        ...dataString
          .split('\n')
          .map((line) => line.trim())
          .filter((line) => line.length > 0),
      );
    }
    return entries;
  });

  public detectedLanguage = computed(() => {
    return this.detectLanguage(this.dataEntries()[0]);
  });

  public parsedJson = computed(() => {
    if (this.detectedLanguage() !== 'json') {
      return [];
    }

    return this.dataEntries().map((dataString) => {
      try {
        return JSON.parse(dataString);
      } catch (e) {
        console.error('JSON Parsing Error:', e);
        return null;
      }
    });
  });

  public parsedCsv = computed(() => {
    if (this.detectedLanguage() !== 'csv') {
      return [];
    }

    const processedDataEntries = this.dataEntries().map((line) => {
      const index = line.indexOf(',');
      if (index >= 0) {
        const id = line.substring(0, index);
        const data = line.substring(index + 1);

        const quotedData = '"' + data.replace(/"/g, '""') + '"';
        return `${id},${quotedData}`;
      } else {
        return line;
      }
    });

    const allData = processedDataEntries.join('\n');

    const result = Papa.parse<{ [key: string]: any }>(allData, {
      header: true,
      skipEmptyLines: true,
    });

    return result.data.map((row) => {
      Object.keys(row).forEach((key) => {
        const value = row[key];
        if (typeof value === 'string' && value.trim().startsWith('{')) {
          try {
            row[key] = JSON.parse(value);
          } catch (e) {
            console.warn(`Could not parse JSON in CSV field ${key}:`, e);
          }
        }
      });
      return row;
    });
  });

  public isObject(value: any): boolean {
    return value && typeof value === 'object' && !Array.isArray(value);
  }

  public getCsvHeaders(row: any): string[] {
    return row ? Object.keys(row) : [];
  }

  public detectLanguage(data: string): 'json' | 'xml' | 'csv' | 'plaintext' {
    data = data.trim();
    try {
      JSON.parse(data);
      return 'json';
    } catch {
      // Not JSON
    }

    if (data.startsWith('<')) {
      return 'xml';
    }

    if (data.includes(',') || data.includes('\n')) {
      return 'csv';
    }

    return 'plaintext';
  }

  public getNestedTable(obj: any): string {
    let table = '<table class="table table-bordered nested-table">';
    table += '<thead><tr>';

    for (const key of Object.keys(obj)) {
      table += `<th>${key}</th>`;
    }

    table += '</tr></thead><tbody><tr>';

    for (const key of Object.keys(obj)) {
      const value = obj[key];
      if (this.isObject(value)) {
        table += `<td>${this.getNestedTable(value)}</td>`;
      } else {
        table += `<td>${value}</td>`;
      }
    }

    table += '</tr></tbody></table>';

    return table;
  }

  onCopyToClipboard() {
    let dataToCopy = '';
    switch (this.detectedLanguage()) {
      case 'json':
        dataToCopy = JSON.stringify(this.parsedJson(), null, 2);
        break;
      case 'csv':
        dataToCopy = Papa.unparse(this.parsedCsv());
        break;
      case 'xml':
      case 'plaintext':
      default:
        dataToCopy = this.dataEntries().join('\n');
        break;
    }

    const pending = this.clipboard.copy(dataToCopy);
  }
}" if the data looks like that "id,name
67,Lilly Brown
41,Elijah Williams
16,Sophia Martinez
95,Jacob Johnson
100,Avery Taylor
65,Mia Anderson
44,Oliver Hernandez
49,Isabella Davis
12,Lucas Wilson
36,Evelyn Rodriguez" the table it's generated perfectly how i want, however if the data contains only one column like that "id
2
51
8
53
36
28
45
6
99
29
" tha table looks basic? like it goes on the default case i think?