なんとなく https://github.com/hitame01/test/archive/refs/tags/v0.1.0.zip https://github.com/hitame01/test/archive/refs/tags/v0.2.0.zip https://github.com/hitame01/test/archive/refs/tags/v0.3.0.zip
■
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相当ミニアプリ 起動までの手順まとめ
- Vite + React プロジェクト作成
npm create vite@latest taskchute-mini -- --template react-ts cd taskchute-mini
- 依存パッケージのインストール
npm install uuid npm install -D tailwindcss @tailwindcss/postcss postcss autoprefixer
- 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;
- アプリコードを配置
src/App.tsxに TaskChuteMini コンポーネントコードを貼り付け- 先頭で React のフックをインポート:
import React, { useEffect, useMemo, useRef, useState } from "react";
- エントリーポイントで描画
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>
);
- 未使用警告の処理(例:endLastInterrupt)
- 使う予定がなければ削除または
_プレフィックスを付与 使う場合はUIから呼び出すボタンを配置
開発サーバ起動
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