■

tailwind.config.js

export default {
  content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
  theme: { extend: {} },
  plugins: [],
}

postcss.config.js

// postcss.config.js
export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}

src/main.tsx

import React, { useEffect, useMemo, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
console.log("boot main.tsx");
// import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

// --- Types ---
export type Task = {
  id: string;
  title: string;
  estimateMin: number; // 見積り(分)
  actualMin: number; // 実績(分)
  plannedStart?: string; // 予定開始(ISO HH:MM)
  startedAt?: number; // 実際の開始(epoch ms)
  finishedAt?: number; // 実際の終了(epoch ms)
  status: "queued" | "running" | "done" | "paused";
  project?: string;
  note?: string;
  dayKey: string; // yyyy-mm-dd(日次で流す)
  interrupts: Interrupt[];
};

export type Interrupt = {
  id: string;
  taskId: string;
  startedAt: number;
  endedAt?: number;
  note?: string;
};

// --- Utils ---
const todayKey = (d = new Date()) => d.toISOString().slice(0, 10);
const pad2 = (n: number) => n.toString().padStart(2, "0");
const msToMin = (ms: number) => Math.round(ms / 60000);
const hhmm = (dateOrMs?: number | Date) => {
  if (!dateOrMs) return "--:--";
  const d = typeof dateOrMs === "number" ? new Date(dateOrMs) : dateOrMs;
  return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
};

/* 
const parseHHMM = (hhmm?: string) => {
  if (!hhmm) return undefined;
  const [h, m] = hhmm.split(":").map(Number);
  const d = new Date();
  d.setHours(h ?? 0, m ?? 0, 0, 0);
  return d.getTime();
};
*/

// --- Storage ---
const LS_KEY = "taskchute_mini_v1";

type Store = {
  tasks: Task[];
  lastOpened: string;
};

const loadStore = (): Store => {
  try {
    const raw = localStorage.getItem(LS_KEY);
    if (raw) return JSON.parse(raw);
  } catch {}
  return { tasks: [], lastOpened: todayKey() };
};

const saveStore = (store: Store) => {
  localStorage.setItem(LS_KEY, JSON.stringify(store));
};

// --- Main Component ---
export default function TaskChuteMini() {
  const [store, setStore] = useState<Store>(() => loadStore());
  const [filterProject, setFilterProject] = useState<string>("");
  const [showDone, setShowDone] = useState(false);
  const [newTask, setNewTask] = useState<Partial<Task>>({ estimateMin: 25 });
  const [now, setNow] = useState(Date.now());
  const runningTaskRef = useRef<Task | null>(null);

  useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 15_000);
    return () => clearInterval(t);
  }, []);

  useEffect(() => {
    saveStore(store);
  }, [store]);

  // 日替わりでキューを流す
  useEffect(() => {
    const k = todayKey();
    if (store.lastOpened !== k) {
      setStore((s) => ({
        lastOpened: k,
        tasks: s.tasks.map((t) =>
          t.dayKey === s.lastOpened && t.status !== "done"
            ? { ...t, dayKey: k, status: "queued", startedAt: undefined, finishedAt: undefined, actualMin: 0, interrupts: [] }
            : t
        ),
      }));
    }
  }, []);

  const dayTasks = useMemo(() => {
    const today = todayKey();
    return store.tasks.filter((t) => t.dayKey === today);
  }, [store.tasks]);

  const runningTask = useMemo(() => dayTasks.find((t) => t.status === "running"), [dayTasks]);
  useEffect(() => {
    runningTaskRef.current = runningTask ?? null;
  }, [runningTask]);

  const projects = useMemo(() => {
    const set = new Set(dayTasks.map((t) => t.project).filter(Boolean) as string[]);
    return Array.from(set);
  }, [dayTasks]);

  const filtered = useMemo(() => {
    return dayTasks.filter((t) => (filterProject ? t.project === filterProject : true)).filter((t) => (showDone ? true : t.status !== "done"));
  }, [dayTasks, filterProject, showDone]);

  const addTask = () => {
    if (!newTask.title || !newTask.estimateMin) return;
    const t: Task = {
      id: uuidv4(),
      title: newTask.title!,
      estimateMin: Math.max(1, Number(newTask.estimateMin)),
      actualMin: 0,
      plannedStart: newTask.plannedStart,
      startedAt: undefined,
      finishedAt: undefined,
      status: "queued",
      project: newTask.project || "",
      note: newTask.note || "",
      dayKey: todayKey(),
      interrupts: [],
    };
    setStore((s) => ({ ...s, tasks: [...s.tasks, t] }));
    setNewTask({ estimateMin: newTask.estimateMin });
  };

  const startTask = (id: string) => {
    setStore((s) => ({
      ...s,
      tasks: s.tasks.map((t) => {
        if (t.id === id) {
          return { ...t, status: "running", startedAt: Date.now(), finishedAt: undefined };
        } else if (t.status === "running") {
          const spent = t.startedAt ? Date.now() - t.startedAt : 0;
          return { ...t, status: "paused", actualMin: t.actualMin + msToMin(spent), startedAt: undefined };
        }
        return t;
      }),
    }));
  };

  const stopTask = (id: string) => {
    setStore((s) => ({
      ...s,
      tasks: s.tasks.map((t) => {
        if (t.id === id && t.status === "running") {
          const spent = t.startedAt ? Date.now() - t.startedAt : 0;
          return { ...t, status: "done", finishedAt: Date.now(), actualMin: t.actualMin + msToMin(spent), startedAt: undefined };
        }
        return t;
      }),
    }));
  };

  const pauseTask = (id: string) => {
    setStore((s) => ({
      ...s,
      tasks: s.tasks.map((t) => {
        if (t.id === id && t.status === "running") {
          const spent = t.startedAt ? Date.now() - t.startedAt : 0;
          return { ...t, status: "paused", actualMin: t.actualMin + msToMin(spent), startedAt: undefined };
        }
        return t;
      }),
    }));
  };

  const quickInterrupt = (note?: string) => {
    const rt = runningTaskRef.current;
    if (!rt) return;
    // pause current task and start an interrupt timer
    pauseTask(rt.id);
    const intr: Interrupt = { id: uuidv4(), taskId: rt.id, startedAt: Date.now(), note };
    setStore((s) => ({ ...s, tasks: s.tasks.map((t) => (t.id === rt.id ? { ...t, interrupts: [...t.interrupts, intr] } : t)) }));
  };

  /* 
  const endLastInterrupt = () => {
    const rt = runningTaskRef.current || dayTasks.find((t) => t.status === "paused");
    if (!rt) return;
    const last = [...rt.interrupts].reverse().find((i) => !i.endedAt);
    if (!last) return;
    setStore((s) => ({
      ...s,
      tasks: s.tasks.map((t) => {
        if (t.id !== rt.id) return t;
        return {
          ...t,
          interrupts: t.interrupts.map((i) => (i.id === last.id ? { ...i, endedAt: Date.now() } : i)),
        };
      }),
    }));
  };
*/

  const reorder = (from: number, to: number) => {
    setStore((s) => {
      const tasks = [...filtered];
      const [moved] = tasks.splice(from, 1);
      tasks.splice(to, 0, moved);
      const ids = tasks.map((t) => t.id);
      return { ...s, tasks: s.tasks.sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)) };
    });
  };

  const removeTask = (id: string) => setStore((s) => ({ ...s, tasks: s.tasks.filter((t) => t.id !== id) }));

  const stats = useMemo(() => {
    const totalEst = filtered.reduce((a, t) => a + (t.estimateMin || 0), 0);
    const totalAct = filtered.reduce((a, t) => a + (t.actualMin || 0), 0);
    const runningExtra = filtered
      .filter((t) => t.status === "running" && t.startedAt)
      .reduce((a, t) => a + msToMin(Date.now() - (t.startedAt as number)), 0);
    return { totalEst, totalAct: totalAct + runningExtra };
  }, [filtered, now]);

  const exportCSV = () => {
    const rows = [
      [
        "day",
        "project",
        "title",
        "estimate_min",
        "actual_min",
        "planned_start",
        "started_at",
        "finished_at",
        "status",
        "interrupt_count",
      ],
      ...dayTasks.map((t) => [
        t.dayKey,
        t.project || "",
        t.title.replaceAll(",", " "),
        t.estimateMin,
        t.actualMin,
        t.plannedStart || "",
        t.startedAt ? new Date(t.startedAt).toISOString() : "",
        t.finishedAt ? new Date(t.finishedAt).toISOString() : "",
        t.status,
        t.interrupts.length,
      ]),
    ]
      .map((r) => r.join(","))
      .join("\n");
    const blob = new Blob([rows], { type: "text/csv" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `taskchute_${todayKey()}.csv`;
    document.body.appendChild(a);
    a.click();
    a.remove();
  };

  return (
    <div className="min-h-screen bg-slate-50 text-slate-800 p-4 md:p-8">
      <div className="max-w-5xl mx-auto">
        <header className="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-6">
          <div>
            <h1 className="text-2xl md:text-3xl font-bold">TaskChute Mini</h1>
            <p className="text-sm text-slate-500">順番通り・見積り対実績・割込み管理(ローカル保存)</p>
          </div>
          <div className="flex flex-wrap items-center gap-2">
            <select
              className="px-3 py-2 rounded-xl border bg-white"
              value={filterProject}
              onChange={(e) => setFilterProject(e.target.value)}
            >
              <option value="">すべてのプロジェクト</option>
              {projects.map((p) => (
                <option key={p} value={p}>
                  {p}
                </option>
              ))}
            </select>
            <label className="inline-flex items-center gap-2 text-sm">
              <input type="checkbox" checked={showDone} onChange={(e) => setShowDone(e.target.checked)} />
              完了も表示
            </label>
            <button className="px-3 py-2 rounded-xl bg-slate-900 text-white hover:opacity-90" onClick={exportCSV}>
              CSV出力
            </button>
          </div>
        </header>

        {/* Add Form */}
        <div className="bg-white rounded-2xl shadow p-4 mb-6">
          <div className="grid md:grid-cols-6 gap-3">
            <input
              className="px-3 py-2 rounded-xl border col-span-2"
              placeholder="タスク名"
              value={newTask.title || ""}
              onChange={(e) => setNewTask((t) => ({ ...t, title: e.target.value }))}
            />
            <input
              type="number"
              className="px-3 py-2 rounded-xl border"
              placeholder="見積り(分)"
              value={newTask.estimateMin ?? 25}
              onChange={(e) => setNewTask((t) => ({ ...t, estimateMin: Number(e.target.value) }))}
              min={1}
            />
            <input
              className="px-3 py-2 rounded-xl border"
              placeholder="予定開始(例 09:30)"
              value={newTask.plannedStart || ""}
              onChange={(e) => setNewTask((t) => ({ ...t, plannedStart: e.target.value }))}
            />
            <input
              className="px-3 py-2 rounded-xl border"
              placeholder="プロジェクト"
              value={newTask.project || ""}
              onChange={(e) => setNewTask((t) => ({ ...t, project: e.target.value }))}
            />
            <button className="px-3 py-2 rounded-xl bg-blue-600 text-white hover:opacity-90" onClick={addTask}>
              追加
            </button>
          </div>
          <textarea
            className="mt-3 w-full px-3 py-2 rounded-xl border"
            placeholder="メモ(任意)"
            value={newTask.note || ""}
            onChange={(e) => setNewTask((t) => ({ ...t, note: e.target.value }))}
          />
        </div>

        {/* Stats */}
        <div className="grid md:grid-cols-3 gap-3 mb-4">
          <div className="bg-white rounded-2xl shadow p-4">
            <div className="text-xs text-slate-500">本日の合計見積り</div>
            <div className="text-2xl font-semibold">{stats.totalEst} 分</div>
          </div>
          <div className="bg-white rounded-2xl shadow p-4">
            <div className="text-xs text-slate-500">本日の実績(進行含む)</div>
            <div className="text-2xl font-semibold">{stats.totalAct} 分</div>
          </div>
          <div className="bg-white rounded-2xl shadow p-4">
            <div className="text-xs text-slate-500">稼働中</div>
            <div className="text-2xl font-semibold">{runningTask ? runningTask.title : "なし"}</div>
          </div>
        </div>

        {/* Task List */}
        <ul className="space-y-3">
          {filtered.map((t, idx) => (
            <li key={t.id} className="bg-white rounded-2xl shadow p-4">
              <div className="flex items-start justify-between gap-3">
                <div className="flex-1">
                  <div className="flex items-center gap-2 flex-wrap">
                    <span
                      className={`text-xs px-2 py-1 rounded-full ${
                        t.status === "done"
                          ? "bg-emerald-50 text-emerald-600"
                          : t.status === "running"
                          ? "bg-blue-50 text-blue-600"
                          : t.status === "paused"
                          ? "bg-amber-50 text-amber-700"
                          : "bg-slate-100 text-slate-600"
                      }`}
                    >
                      {t.status}
                    </span>
                    {t.project && <span className="text-xs bg-slate-100 px-2 py-1 rounded-full">{t.project}</span>}
                    <span className="font-medium text-lg">{t.title}</span>
                  </div>
                  {t.note && <div className="text-sm text-slate-500 mt-1 whitespace-pre-wrap">{t.note}</div>}
                  <div className="mt-2 grid md:grid-cols-5 gap-2 text-sm">
                    <div>
                      <div className="text-slate-500 text-xs">見積り</div>
                      <div>{t.estimateMin} 分</div>
                    </div>
                    <div>
                      <div className="text-slate-500 text-xs">実績</div>
                      <div>
                        {t.actualMin + (t.status === "running" && t.startedAt ? msToMin(Date.now() - t.startedAt) : 0)} 分
                      </div>
                    </div>
                    <div>
                      <div className="text-slate-500 text-xs">予定開始</div>
                      <div>{t.plannedStart || "-"}</div>
                    </div>
                    <div>
                      <div className="text-slate-500 text-xs">開始/終了</div>
                      <div>
                        {t.startedAt ? hhmm(t.startedAt) : "--:--"} → {t.finishedAt ? hhmm(t.finishedAt) : "--:--"}
                      </div>
                    </div>
                    <div>
                      <div className="text-slate-500 text-xs">割込み</div>
                      <div>{t.interrupts.length} 件</div>
                    </div>
                  </div>

                  {t.interrupts.length > 0 && (
                    <details className="mt-2">
                      <summary className="text-xs text-slate-500 cursor-pointer">割込みの内訳</summary>
                      <ul className="mt-1 text-sm list-disc pl-5 space-y-1">
                        {t.interrupts.map((i) => (
                          <li key={i.id}>
                            {hhmm(i.startedAt)} - {i.endedAt ? hhmm(i.endedAt) : "..."} {i.note ? `: ${i.note}` : ""}
                          </li>
                        ))}
                      </ul>
                    </details>
                  )}
                </div>
                <div className="flex flex-col items-end gap-2 w-44">
                  <div className="flex gap-2">
                    {t.status !== "running" && t.status !== "done" && (
                      <button className="px-3 py-2 rounded-xl bg-blue-600 text-white hover:opacity-90" onClick={() => startTask(t.id)}>
                        開始
                      </button>
                    )}
                    {t.status === "running" && (
                      <>
                        <button className="px-3 py-2 rounded-xl bg-amber-600 text-white hover:opacity-90" onClick={() => pauseTask(t.id)}>
                          一時停止
                        </button>
                        <button className="px-3 py-2 rounded-xl bg-emerald-600 text-white hover:opacity-90" onClick={() => stopTask(t.id)}>
                          完了
                        </button>
                      </>
                    )}
                  </div>
                  <div className="flex gap-2">
                    <button className="px-3 py-2 rounded-xl bg-slate-100" onClick={() => quickInterrupt("雑務")}>割込み</button>
                    <button className="px-3 py-2 rounded-xl bg-slate-100" onClick={() => removeTask(t.id)}>削除</button>
                  </div>
                  <div className="flex gap-1 text-xs text-slate-500">
                    <button className="px-2 py-1 rounded-lg bg-slate-100" onClick={() => idx > 0 && reorder(idx, idx - 1)}>
                      ↑
                    </button>
                    <button className="px-2 py-1 rounded-lg bg-slate-100" onClick={() => idx < filtered.length - 1 && reorder(idx, idx + 1)}>
                      ↓
                    </button>
                  </div>
                </div>
              </div>
            </li>
          ))}
        </ul>

        {/* Footer Tips */}
        <div className="mt-8 text-xs text-slate-500 space-y-1">
          <div>Tips: タスクは上から順に処理。実績は「開始」〜「一時停止/完了」で積算。割込みは走行中タスクを一時停止して記録。</div>
          <div>データはブラウザのLocalStorageに保存されます(このPCのみ)。</div>
        </div>
      </div>
    </div>
  );
}

// ここまでに TaskChuteMini が定義されています
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <TaskChuteMini />
  </React.StrictMode>
);

src/index.css

@import "tailwindcss";

TaskChute相当ミニアプリ 起動までの手順まとめ

  1. Vite + React プロジェクト作成
npm create vite@latest taskchute-mini -- --template react-ts
cd taskchute-mini
  1. 依存パッケージのインストール
npm install uuid
npm install -D tailwindcss @tailwindcss/postcss postcss autoprefixer
  1. Tailwind の初期設定
npx tailwindcss init -p
  • tailwind.config.js の content を編集:
export default {
  content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
  theme: { extend: {} },
  plugins: [],
}
  • postcss.config.js を最新仕様に対応:
export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
}
  • src/index.css に以下を記述:
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. アプリコードを配置
  2. src/App.tsx に TaskChuteMini コンポーネントコードを貼り付け
  3. 先頭で React のフックをインポート:
import React, { useEffect, useMemo, useRef, useState } from "react";
  1. エントリーポイントで描画
  2. src/main.tsx を以下のように編集:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
  1. 未使用警告の処理(例:endLastInterrupt)
  2. 使う予定がなければ削除または _ プレフィックスを付与
  3. 使う場合はUIから呼び出すボタンを配置

  4. 開発サーバ起動

npm run dev

XML ソート

import xml.etree.ElementTree as ET
from xml.dom import minidom

def sort_xml_elements(elem):
    def get_sort_key(tag):
        uri, localname = '', tag
        if '}' in tag:
            uri, localname = tag[1:].split('}')
        return (uri, localname)

    def get_element_key(element):
        sort_key = get_sort_key(element.tag)
        attr_keys = sorted((k, v) for k, v in element.attrib.items())
        return sort_key, attr_keys

    elem[:] = sorted(elem, key=get_element_key)
    for child in elem:
        sort_xml_elements(child)

def prettify_xml(elem):
    rough_string = ET.tostring(elem, 'utf-8')
    parsed = minidom.parseString(rough_string)
    pretty_string = parsed.toprettyxml(indent="    ")
    
    lines = pretty_string.splitlines()
    non_empty_lines = [line for line in lines if line.strip() != '']
    return '\n'.join(non_empty_lines)

def sort_xml_file(input_file, output_file):
    tree = ET.parse(input_file)
    root = tree.getroot()
    
    sort_xml_elements(root)
    
    with open(output_file, 'w', encoding='utf-8') as f:
        f.write(prettify_xml(root))

if __name__ == "__main__":
    input_file = 'input.xml'
    output_file = 'output.xml'
    
    sort_xml_file(input_file, output_file)

XMLベリファイ

def collect_elements(tree):
    element_dict = {}

    # 要素と属性の組み合わせを収集する関数
    def traverse(elem):
        key = (elem.tag, tuple(sorted(elem.attrib.items())))
        if key in element_dict:
            element_dict[key] += 1
        else:
            element_dict[key] = 1
        
        for child in elem:
            traverse(child)

    root = tree.getroot()
    traverse(root)

    return element_dict

def check_duplicates(element_dict):
    # 重複している要素を検出する
    duplicates = {key: count for key, count in element_dict.items() if count > 1}
    return duplicates

def validate_xml(input_file):
    tree = ET.parse(input_file)
    element_dict = collect_elements(tree)
    duplicates = check_duplicates(element_dict)
    if duplicates:
        for key, count in duplicates.items():
            tag_name = key[0]
            attributes = ', '.join(f'{k}="{v}"' for k, v in key[1])
            print(f"Error: Duplicate elements with the same tag '{tag_name}' and attributes '{attributes}' found {count} times.")
        return False
    else:
        print("XML validation successful. No duplicates found.")
        return True

if __name__ == "__main__":
    input_file = 'output.xml'  # ソート後のXMLファイルを指定
    is_valid = validate_xml(input_file)
    if is_valid:
        print("Proceed with further processing...")
    else:
        print("Fix the XML validation errors before proceeding.")

■

コメント除去 + 関数リストアップ


const fs = require('fs');
const path = require('path');

function removeComments(code) {
  const lines = code.split(/(?<=\n)/g);
  const result = [];
  let inMultilineComment = false;
  let inLiteralDoubleQuote = false;
  let inLiteralSingleQuote = false;

  for (const line of lines) {
    let processedLine = '';
    let inComment = false;

    for (let i = 0; i < line.length; i++) {
      const char = line[i];

      if (inMultilineComment) {
        // マルチラインコメントモード
        if (char === '*' && line[i + 1] === '/') {
          // 終端検出
          inMultilineComment = false;
          i++; // /ぶんをスキップ
        } else if (char === '\n') {
          // コメント継続中は改行コードのみ行末に追加
          processedLine += char;
        }
      } else if (inComment) {
        // シングルラインコメントモード
        if (char === '\n') {
          processedLine += char;
          inComment = false;
        }
      } else if (inLiteralDoubleQuote) {
        processedLine += char;
        if (char === '"' && ((i == 0) || line[i - 1] !== '\\')) {
          inLiteralDoubleQuote = false;
        }
      } else if (inLiteralSingleQuote) {
        processedLine += char;
        if (char === "'" && ((i == 0) || line[i - 1] !== '\\')) {
          inLiteralSingleQuote = false;
        }
      } else {
        if (char === '/' && line[i + 1] === '/') {
          inComment = true;
          i++;
        } else if (char === '/' && line[i + 1] === '*') {
          inMultilineComment = true;
          i++;
        } else if (char === '"') {
          inLiteralDoubleQuote = true;
          processedLine += char;
        } else if (char === "'") {
          inLiteralSingleQuote = true;
          processedLine += char;
        } else {
          processedLine += char;
        }
      }
    }

    if (processedLine.length > 0 || inMultilineComment) {
      result.push(processedLine);
    }
  }
  return result
}

function extractFunctions(lines) {
  //  const lines = code.split(/\n/g);
  const result = [];
  let currentFunctionName = '';
  let startLine = -1;
  let endLine = -1;
  let inString = false;
  let stringStartChar = '';
  let depth = 0;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];

    // 関数定義かどうかを判定(リテラル内は無視)
    const regex = /function\s+(\w+)\s*\(/;
    const match = line.match(regex);
    if (match && depth === 0 && !inString) {
      currentFunctionName = match[1];
      startLine = i + 1;
      depth = 1;
    }

    // 関数{}の開始と終了判定
    for (let j = 0; j < line.length; j++) {
      const char = line[j];
      if ((char === '"' || char === '`') && !inString) {
        inString = true;
        stringStartChar = char;
      } else if (char === stringStartChar && inString) {
        inString = false;
      } else if (!inString) {
        if (char === '{') {
          depth++;
        } else if (char === '}') {
          depth--;
          if (depth === 1) {
            endLine = i + 1;
            result.push(`${currentFunctionName},${startLine},${endLine}`);
            currentFunctionName = '';
            depth = 0;
          }
        }
      }
    }
  }

  return result.join('\n');
}

function main() {
  // 処理対象のファイルパスを指定
  const filePath = path.join(__dirname, 'example.js');

  // ファイルを読み込む
  const code = fs.readFileSync(filePath, 'utf8');
  const code_linux = code.replace(/\r\n/g, '\n');
  // コメントを削除
  const lines_without_comments = removeComments(code_linux);
  // 結果を出力
  const functions = extractFunctions(lines_without_comments);
  console.log(functions);
}

main();

■

コメント除去

function removeComments(code) {
  const lines = code.split(/(?<=:)/g);
  const result = [];
  let inMultilineComment = false;
  let inLiteralDoubleQuote = false;
  let inLiteralSingleQuote = false;

  for (const line of lines) {
    let processedLine = '';
    let inComment = false;

    for (let i = 0; i < line.length; i++) {
      const char = line[i];

      if (inMultilineComment) {
        // マルチラインコメントモード
        if (char === '*' && line[i + 1] === '/') {
          // 終端検出
          inMultilineComment = false;
          i++; // /ぶんをスキップ
        } else if (char === '\n') {
          // コメント継続中は改行コードのみ行末に追加
          processedLine += char;
        }
      } else if (inComment) {
        // シングルラインコメントモード
        if (char === '\n') {
          processedLine += char;
          inComment = false;
        }
      } else if (inLiteralDoubleQuote) {
        processedLine += char;
        if (char === '"' && ((i == 0) || line[i - 1] !== '\\')) {
          inLiteralDoubleQuote = false;
        }
      } else if (inLiteralSingleQuote) {
        processedLine += char;
        if (char === "'" && ((i == 0) || line[i - 1] !== '\\')) {
          inLiteralSingleQuote = false;
        }
      } else {
        if (char === '/' && line[i + 1] === '/') {
          inComment = true;
          i++;
        } else if (char === '/' && line[i + 1] === '*') {
          inMultilineComment = true;
          i++;
        } else if (char === '"') {
          inLiteralDoubleQuote = true;
          processedLine += char;
        } else if (char === "'") {
          inLiteralSingleQuote = true;
          processedLine += char;
        } else {
          processedLine += char;
        }
      }
    }

    if (processedLine.length > 0 || inMultilineComment) {
      result.push(processedLine);
    }
  }

  return result.join('\n');
}

const fs = require('fs');
const path = require('path');

function main() {
  // 処理対象のファイルパスを指定
  const filePath = path.join(__dirname, 'example.js');

  // ファイルを読み込む
  const code = fs.readFileSync(filePath, 'utf8');
  const code_linux = code.replace(/\r\n/g, '\n');
  //console.log(code_linux);

  // コメントを削除
  const codeWithoutComments = removeComments(code_linux);

  // 結果を出力
  console.log(codeWithoutComments);
}

main();

■

import re

def split_data(input_str):
    pattern = r"(\d+?)\s*:(.*?)[,\s]"
    results = []
    for match in re.finditer(pattern, input_str):
        num = match.group(1)
        data = match.group(2)
        results.append((num, data))
    return results

# 使用例
input_str = "0:aa,bb,dd,dd,1:ee,ff,gg,2:hh...., 3 :ii,jj,kk"
output = split_data(input_str)
for num, data in output:
    print(f"Number: {num}, Data: {data}")

文字列分割

python学習


import lxml.etree as etree
# 追加・更新関数

class PathManipulator:
    @staticmethod
    def custom_split(xpath, sep='/'):
        result = []
        in_quotes = False
        start = 0

        for i, char in enumerate(xpath):
            if char in ('"', "'"):
                in_quotes = not in_quotes
            elif char in sep and not in_quotes:
                result.append(xpath[start:i])
                start = i + 1

        result.append(xpath[start:])
        return [x for x in result if x]  # 空文字列を除外する

    @staticmethod
    def strncmp(s1, s2, n):
        len1 = len(s1)
        len2 = len(s2)
        min_len = min(len1, len2, n)

        for i in range(min_len):
            if s1[i] != s2[i]:
                return ord(s1[i]) - ord(s2[i])

        if min_len < n:
            return len1 - len2
        else:
            return 0

    @staticmethod
    def split_except_literal(input_string, separator):
        result = []
        temp = ""
        skip_count = 0
        separetor_len = len(separator)
        literal_started = False
        for i, char in enumerate(input_string):
            separator_candidate = PathManipulator.extract_string(
                input_string, i, separetor_len)
            if skip_count > 0:
                skip_count -= 1
                continue
            if char == "'":
                literal_started = not literal_started
                temp += char
            elif PathManipulator.strncmp(separator_candidate, separator, separetor_len) == 0 and not literal_started:
                skip_count = separetor_len - 1
                result.append(temp)
                temp = ""
            else:
                temp += char
        if temp:
            result.append(temp)
        return result

    @staticmethod
    def extract_string(string, start_index, length):
        return string[start_index:start_index + length]


# 追加・更新関数

class XmlManipulator:
    def __init__(self, filepath):
        self.filepath = filepath
        self. parser = etree.XMLParser(remove_blank_text=True)
        self.tree = etree.parse(self.filepath, self.parser)
        self.root = self.tree.getroot()

    def upsert(self, xpath):
        parts = PathManipulator.custom_split(xpath)
        current_element = self.root
        query = "."

        for i, part in enumerate(parts):
            if (part == "."):
                continue

            # タグ名と属性([]をひとかたまりの文字列として扱う)取得する
            tag_name, *attributes_string = PathManipulator.custom_split(part, '[]')
            print(attributes_string)
            print(part + " -> " + tag_name + " -> " + str(attributes_string))
            tag_name = tag_name.strip()
            attributes_string_internal = " ".join(attributes_string).strip()
            attributes_string = ""
            if len(attributes_string_internal) is not 0:
                attributes_string = '[' + attributes_string_internal + ']'

            query = f"./{tag_name}{attributes_string}"
            elements = current_element.xpath(query)

            if not elements:
                # 要素が存在しない場合は新規作成する
                new_element = etree.SubElement(current_element, tag_name)

                # 属性を設定する
                for attribute in PathManipulator.split_except_literal(attributes_string_internal, 'and'):
                    attribute = attribute.strip()
                    key, value = attribute.split('=')
                    key = key.strip("@")
                    new_element.set(key.strip(), value.strip("]['\""))

                current_element = new_element
            else:
                # 要素が存在する場合はその要素を選択する
                print("Found: " + str(len(elements)) +
                      " elements for the given XPath = " + query)
                current_element = elements[0]

        return current_element

    # 削除関数

    def delete(self, xpath):
        elements = self.root.xpath(xpath)
        if len(elements) > 1:
            print("Warning: Multiple elements found for the given XPath = " + xpath)
        elif len(elements) == 1:
            parent = elements[0].getparent()
            parent.remove(elements[0])
        else:
            print("Warning: No elements found for the given XPath = " + xpath)

    def save(self,  filepath):
        # 名前空間を削除して保存する
        for elem in self.root.iter():
            elem.tag = etree.QName(elem).localname
        # インデントを行う
        tree = self.root.getroottree()
        tree.write(filepath, pretty_print=True,
                   encoding='utf-8', xml_declaration=True)


# テスト用のコード
if __name__ == "__main__":
    xml_mod =XmlManipulator('input.xml')
    # 追加・更新のテスト
    # ない場合は新規作成
    xml_mod.upsert(
        "./base[@class='hogeC' and @distName='/aaa/bbb-99/fff-2/ddd-1/']")
    # ない場合は新規作成
    xml_mod.upsert(
        "./base[@class='hogeC' and @distName='/aaa/bbb-99/fff-2/ddd-4/']")
    # ない場合は新規作成
    xml_mod.upsert(
        "./base[@class='hogeC' and @distName='/aaa/eee-99/fff-2/ddd-4/']")
    element = xml_mod.upsert(
        "./base[@class='hogeA' and @distName='/aaa/bbb-99/ccc-5/ddd-1/']/list[@name='hogeList']/p")
    element.text = "hoge1"
    element = xml_mod.upsert(
        "./base[@class='hogeA' and @distName='/aaa/bbb-99/ccc-5/ddd-1/']/list[@name='hogeList']/p")
    element.text = "hoge2"
    element = xml_mod.upsert(
        "./base[@class='hogeA' and @distName='/aaa/bbb-99/ccc-5/ddd-1/']/list[@name='hogeList']/p")
    element.text = "hoge3"
    element = xml_mod.upsert(
        "./base[@class='hogeA' and @distName='/aaa/bbb-99/ccc-5/ddd-1/']/list[@name='hogeList']/p")
    element.text = "hoge4"
    element = xml_mod.upsert(
        "./base[@class='hogeA' and @distName='/aaa/bbb-99/ccc-5/ddd-1/']/list[@name='hogeList']/p")
    element.text = "hoge5"
    xml_mod.upsert("./base[@class='hogeA']")  # ある場合は取得
    # 削除のテスト
    xml_mod.delete("./base[@class='hogeC']")  # 削除する場合
    xml_mod.delete("./base[@class='hogeD']")  # 存在しない場合

    # 保存のテスト
    xml_mod.save( "output.xml")

import csv
import os


class CSVFileManager:
    def __init__(self, file_path, title_row_index=0, primary_keys=None):
        self.file_path = file_path
        self.title_row_index = title_row_index
        self.primary_keys = primary_keys or []
        self.data = self._read_csv()

    def _read_csv(self):
        with open(self.file_path, 'r', newline='') as file:
            reader = csv.reader(file)
            data = list(reader)
        return data

    def save(self, file_path=None):
        file_path = file_path or self.file_path
        with open(file_path, 'w', newline='') as file:
            writer = csv.writer(file)
            writer.writerows(self.data)

    def upsert(self, new_row):
        title_row = self.data[self.title_row_index]
        
        # Check if primary keys are provided
        if not self.primary_keys:
            raise ValueError("Primary keys not specified.")
        
        # Find index of primary key columns
        key_indices = [title_row.index(key) for key in self.primary_keys]
        
        # Check for duplicate primary keys
        for row in self.data[self.title_row_index + 1:]:
            if all(row[idx] == new_row[title_row.index(title)] for idx, title in zip(key_indices, self.primary_keys)):
                raise ValueError("Duplicate primary keys found.")
        
        # Upsert or append the new row
        for idx, row in enumerate(self.data[self.title_row_index + 1:], start=self.title_row_index + 1):
            if all(row[idx] == new_row[title_row.index(title)] for idx, title in zip(key_indices, self.primary_keys)):
                self.data[idx] = new_row
                break
        else:
            self.data.append(new_row)

    def delete(self, key_values):
        title_row = self.data[self.title_row_index]
        
        # Check if primary keys are provided
        if not self.primary_keys:
            raise ValueError("Primary keys not specified.")
        
        # Find index of primary key columns
        key_indices = [title_row.index(key) for key in self.primary_keys]
        
        # Find rows with matching primary keys
        matching_rows = []
        for idx, row in enumerate(self.data[self.title_row_index + 1:], start=self.title_row_index + 1):
            if all(row[idx] == value for idx, value in zip(key_indices, key_values)):
                matching_rows.append(idx)
        
        # Error if no matching rows found
        if not matching_rows:
            print("Warning: No rows found with specified primary keys.")
            return
        
        # Error if multiple matching rows found
        if len(matching_rows) > 1:
            raise ValueError("Multiple rows found with specified primary keys.")
        
        # Delete matching row
        del self.data[matching_rows[0]]


# Example usage:
file_path = "example.csv"
primary_keys = ["ID"]

# Initialize CSVFileManager instance
manager = CSVFileManager(file_path, title_row_index=3, primary_keys=primary_keys)

# Upsert a new row
new_row = ["123", "John Doe", "30"]
manager.upsert(new_row)

# Delete a row
manager.delete(["123"])

# Save changes to file
manager.save()
import pandas as pd
import csv


def custom_split(csv_line, sep=',', strip=True):
    result = []
    in_quotes = False
    start = 0

    for i, char in enumerate(csv_line):
        if char in ('"', "'"):
            in_quotes = not in_quotes
        elif char in sep and not in_quotes:
            csv_value = csv_line[start:i]
            if strip:
                csv_value = csv_value.strip()
            csv_value = remove_outer_quotes(csv_value)
            result.append(csv_value)
            start = i + 1

    csv_value = csv_line[start:]
    if strip:
        csv_value = csv_value.strip()
    result.append(csv_value)
    return [x for x in result if x]  # 空文字列を除外する


def remove_outer_quotes(s):
    if len(s) < 2 or (s[0] != s[-1]) or (s[0] not in ['"', "'"]):
        return s
    elif s.count(s[0]) >= 3:
        return s
    else:
        return s[1:-1]


class CustomCSVParser:
    def __init__(self, file_path, skiprows=3):
        self.data = []
        with open(file_path, 'r') as f:
            lines = f.readlines()
            self.titles = custom_split(lines[skiprows].strip())
            data_lines = lines[skiprows + 1:]
            for line in data_lines:
                line = line.strip()
                if line:
                    row = custom_split(line)
                    self.data.append(row)

    def convertToCsv(self, dn, list_val, param_name, value):
        try:
            dn_index = self.titles.index('DN')
            list_index = self.titles.index('list')
            param_index = self.titles.index('param abbreviated name')
            value_index = self.titles.index('value')

            for row in self.data:
                row_list = row[list_index]
                print(len(row_list), len(list_val), len("''"))
                if row_list == list_val:
                    print("same!")
                if row[dn_index] == dn and row_list == list_val and row[param_index] == param_name:
                    values = row[value_index].split(',')
                    for val_pair in values:
                        key, val = val_pair.split(':')
                        if key == value:
                            return val
                    raise ValueError("Specified value not found in CSV.")
            raise ValueError("Specified combination not found in CSV.")
        except Exception as e:
            return str(e)

    def convertToCom(self, dn, list_val, param_name, value):
        try:
            row = self.data[(self.data['DN'] == dn) & (self.data['list'] == list_val) & (
                self.data['param abbreviated name'] == param_name)]
            if not row.empty:
                if value in row.values[0][4:]:
                    idx = row.values[0][4:].tolist().index(value) + 1
                    return row.columns[idx]
                else:
                    raise ValueError("Specified value not found in CSV.")
            else:
                raise ValueError("Specified combination not found in CSV.")
        except Exception as e:
            return str(e)


# Example usage:
converter = CustomCSVParser("stepdata.csv")
print(converter.convertToCsv("/aaa/bbb-/ccc-/ddd-",
      '', "data_name_1", "111"))  # Output: h2
print(converter.convertToCom("/aaa/bbb-/ccc-/ddd-",
      '', "data_name_1", "h2"))  # Output: 112