0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APIを叩くツール(個人)

Last updated at Posted at 2025-05-01

全体構成

  • HTML: 画面レイアウト(ヘッダ、プルダウン、パラメータ編集部、レスポンス表示部)。
  • CSS: プルダウンの横並び、開閉可能なセクション、レスポンス表示の整形。
  • JavaScript: プルダウンの動的生成、CSV/JS読み込み、APIリクエスト送信、レスポンス処理。
  • Excel(VBA): 管理シートからプルダウン内容やパラメータをCSVまたはJSファイルに出力。
  • 運用:管理シートに環境、機能、API、パラメータを定義。VBAでデータをdata.jsまたはdata.csvに出力。HTMLがdata.jsを読み込み、画面を動的に生成。ユーザーがプルダウンで選択し、パラメータを編集後、GET/POSTでAPIを呼び出し。

VBA

Sub ExportToJs()
    Dim ws As Worksheet
    Dim filePath As String
    Dim row As Long, col As Long
    Dim paramId As Long
    Dim currentPaths As Object ' Collection
    Dim arrayIndices As Object ' Dictionary
    Dim output As String
    
    filePath = ThisWorkbook.Path & "\data.js"
    
    ' 出力バッファを構築
    output = "const data = {" & vbCrLf
    
    ' 環境
    Set ws = ThisWorkbook.Sheets("Environment")
    output = output & "  environments: [" & vbCrLf
    For row = 2 To ws.Cells(ws.Rows.Count, 1).End(xlUp).row
        output = output & "    { id: """ & ws.Cells(row, 1).Value & """, name: """ & ws.Cells(row, 2).Value & """, baseURL: """ & ws.Cells(row, 3).Value & """ }," & vbCrLf
    Next row
    output = output & "  ]," & vbCrLf
    
    ' 機能
    Set ws = ThisWorkbook.Sheets("Function")
    output = output & "  functions: [" & vbCrLf
    For row = 2 To ws.Cells(ws.Rows.Count, 1).End(xlUp).row
        output = output & "    { id: """ & ws.Cells(row, 1).Value & """, name: """ & ws.Cells(row, 2).Value & """ }," & vbCrLf
    Next row
    output = output & "  ]," & vbCrLf
    
    ' API
    output = output & "  apis: [" & vbCrLf
    For Each ws In ThisWorkbook.Sheets
        If ws.Name <> "Environment" And ws.Name <> "Function" Then
            Dim apiId As String: apiId = ws.Range("D1").Value
            Dim apiName As String: apiName = ws.Range("D2").Value
            Dim functionId As String: functionId = Left(apiId, 8) ' 例: KLLLAA00
            output = output & "    { functionId: """ & functionId & """, id: """ & apiId & """, name: """ & apiName & """ }," & vbCrLf
        End If
    Next ws
    output = output & "  ]," & vbCrLf
    
    ' パラメータ
    output = output & "  parameters: [" & vbCrLf
    For Each ws In ThisWorkbook.Sheets
        If ws.Name <> "Environment" And ws.Name <> "Function" Then
            apiId = ws.Range("D1").Value
            apiName = ws.Range("D2").Value
            functionId = Left(apiId, 8) ' 例: KLLLAA00
            
            ' パラメータ(BD列以降)
            Dim paramCount As Long: paramCount = ws.Cells(4, 56).End(xlToRight).Column - 56 + 1 ' BD=56
            For col = 56 To 55 + paramCount
                paramId = paramId + 1
                Dim paramIdStr As String: paramIdStr = Format(paramId, "000")
                Dim paramName As String: paramName = ws.Cells(4, col).Value
                If paramName = "" Then paramName = "パラメータ" & paramIdStr
                
                ' ネスト/配列構造を解析
                Dim lastRow As Long: lastRow = ws.Cells(ws.Rows.Count, 3).End(xlUp).row
                Set currentPaths = CreateObject("Scripting.Dictionary") ' 各要素はfullPathとitemType
                Set arrayIndices = CreateObject("Scripting.Dictionary") ' 配列名 -> 次のインデックス
                
                For row = 4 To lastRow
                    ' 値がある最初の列(C=3~L=12)を特定
                    Dim level As Long: level = 3
                    While level <= 12 And ws.Cells(row, level).Value = ""
                        level = level + 1
                    Wend
                    If level > 12 Then GoTo NextRow
                    
                    Dim depth As Long: depth = level - 3
                    Dim key As String: key = ws.Cells(row, level).Value
                    Dim itemType As String: itemType = ws.Cells(row, level + 23).Value ' AA列
                    
                    ' currentPathsをdepthに合わせて調整
                    While currentPaths.Count > depth
                        currentPaths.Remove currentPaths.Keys()(currentPaths.Count - 1)
                    Wend
                    
                    ' fullPathを構築
                    Dim fullPath As String
                    If currentPaths.Count = 0 Then
                        fullPath = key
                        If itemType = "配列" Then fullPath = fullPath & "[0]"
                    Else
                        Dim parent As Variant: parent = currentPaths.Item(currentPaths.Keys()(currentPaths.Count - 1))
                        Dim parentPath As String: parentPath = parent(0)
                        Dim parentType As String: parentType = parent(1)
                        
                        If parentType = "配列" Then
                            Dim arrayName As String: arrayName = Split(parentPath, "[")(0)
                            If Not arrayIndices.Exists(arrayName) Then arrayIndices(arrayName) = 0
                            Dim index As Long: index = arrayIndices(arrayName)
                            fullPath = parentPath & "[" & index & "]"
                            If itemType = "配列" Then
                                fullPath = fullPath & "." & key & "[0]"
                            Else
                                fullPath = fullPath & "." & key
                            End If
                            arrayIndices(arrayName) = index + 1
                        Else
                            fullPath = parentPath & "." & key
                            If itemType = "配列" Then fullPath = fullPath & "[0]"
                        End If
                    End If
                    
                    ' currentPathsに追加(構造マーカー)
                    If itemType = "オブジェクト" Or itemType = "配列" Then
                        currentPaths(depth) = Array(fullPath, itemType)
                    End If
                    
                    ' プロパティ行の場合、パラメータを生成
                    If itemType = "" Then
                        Dim logicalName As String: logicalName = ws.Cells(row, level + 11).Value ' N列以降
                        Dim value As String: value = ws.Cells(row, col).Value
                        If value <> "" Then
                            output = output & "    { apiId: """ & apiId & """, id: """ & paramIdStr & """, name: """ & paramName & """, logicalName: """ & logicalName & """, physicalName: """ & fullPath & """, value: """ & Replace(value, """", "\""") & """ }," & vbCrLf
                        End If
                    End If
                    
NextRow:
                Next row
            Next col
        End If
    Next ws
    output = output & "  ]" & vbCrLf
    output = output & "};"
    
    ' Shift_JISでファイル出力
    With CreateObject("ADODB.Stream")
        .Type = 2 ' テキスト
        .Charset = "Shift_JIS"
        .Open
        .WriteText output
        .SaveToFile filePath, 2 ' 上書き
        .Close
    End With
    
    MsgBox "Exported to " & filePath
End Sub

HTML

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Web API Test Tool</title>
    <meta charset="Shift_JIS">
    <link rel="stylesheet" href="styles.css">
    <script src="data.js"></script>
    <script src="script.js"></script>
</head>
<body>
    <!-- ヘッダ部分 -->
    <div class="header">
        <div class="dropdown">
            <label>環境</label>
            <select id="environment" onchange="updateURL()">
                <option value="">選択してください</option>
            </select>
        </div>
        <div class="dropdown">
            <label>機能</label>
            <select id="function" onchange="updateApis()">
                <option value="">選択してください</option>
            </select>
        </div>
        <div class="dropdown">
            <label>API</label>
            <select id="api" onchange="updateParameters()">
                <option value="">選択してください</option>
            </select>
        </div>
        <div class="dropdown">
            <label>パラメータ</label>
            <select id="parameter" onchange="displayParameterDetails()">
                <option value="">選択してください</option>
            </select>
        </div>
    </div>

    <!-- リクエスト送信先URL -->
    <div class="url-header">
        <label>リクエスト送信先URL: </label>
        <input type="text" id="requestUrl" oninput="updateCustomURL()">
    </div>

    <!-- リクエストヘッダ -->
    <div class="section">
        <h2 onclick="toggleSection('headers')">リクエストヘッダ <span id="headers-toggle">[+]</span></h2>
        <div id="headers" class="collapsible">
            <textarea id="requestHeaders" placeholder='{"Content-Type": "application/json"}'></textarea>
        </div>
    </div>

    <!-- メソッド -->
    <div class="method">
        <label>メソッド: </label>
        <select id="method">
            <option value="GET">GET</option>
            <option value="POST">POST</option>
        </select>
        <button onclick="sendRequest()">送信</button>
    </div>

    <!-- パラメータ表示部 -->
    <div class="section">
        <h2 onclick="toggleSection('parameters')">パラメータ <span id="parameters-toggle">[+]</span></h2>
        <div id="parameters" class="collapsible">
            <table>
                <thead>
                    <tr>
                        <th>項目論理名</th>
                        <th>項目物理名</th>
                        <th>設定値</th>
                    </tr>
                </thead>
                <tbody id="parameterDetails"></tbody>
            </table>
        </div>
    </div>

    <!-- レスポンス表示部 -->
    <div class="section">
        <h2 onclick="toggleSection('response')">レスポンス <span id="response-toggle">[+]</span></h2>
        <div id="response" class="collapsible">
            <pre id="responseContent"></pre>
        </div>
    </div>
</body>
</html>

CSS

styles.html

body {
    font-family: Arial, sans-serif;
    margin: 20px;
}
.header {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}
.dropdown {
    display: flex;
    align-items: center;
    gap: 5px;
}
.dropdown label {
    font-weight: bold;
}
.url-header, .method {
    margin: 10px 0;
}
.url-header input {
    width: 50%;
    padding: 5px;
}
.section h2 {
    cursor: pointer;
    background: #f0f0f0;
    padding: 10px;
    margin: 10px 0;
}
.collapsible {
    display: none;
    padding: 10px;
}
.collapsible.open {
    display: block;
}
#headers textarea {
    width: 100%;
    height: 50px; /* 狭くする */
    resize: vertical;
}
table {
    width: 100%;
    border-collapse: collapse;
}
th, td {
    border: 1px solid #ccc;
    padding: 8px;
    text-align: left;
}
th {
    background: #f0f0f0;
}
input[type="text"] {
    width: 100%;
    box-sizing: border-box;
}

JS

script.js

// カスタムURLを保持
let customURL = '';

// プルダウン初期化
window.onload = () => {
    initDropdowns();
    updateURL();
};

// プルダウン初期化
function initDropdowns() {
    const envSelect = document.getElementById('environment');
    const funcSelect = document.getElementById('function');
    
    // 環境(最初の項目をデフォルト選択)
    data.environments.forEach((env, index) => {
        const option = document.createElement('option');
        option.value = env.id;
        option.textContent = env.name;
        envSelect.appendChild(option);
        if (index === 0) {
            envSelect.value = env.id; // デフォルト選択
        }
    });

    // 機能
    data.functions.forEach(func => {
        const option = document.createElement('option');
        option.value = func.id;
        option.textContent = `${func.id}_${func.name}`;
        funcSelect.appendChild(option);
    });

    updateApis(); // APIプルダウンを初期化
}

// APIプルダウン更新
function updateApis() {
    const funcSelect = document.getElementById('function');
    const apiSelect = document.getElementById('api');
    apiSelect.innerHTML = '<option value="">選択してください</option>';

    const funcId = funcSelect.value;
    if (funcId) {
        data.apis
            .filter(api => api.functionId === funcId)
            .forEach(api => {
                const option = document.createElement('option');
                option.value = api.id;
                option.textContent = `${api.id}_${api.name}`;
                apiSelect.appendChild(option);
            });
    }
    updateParameters();
    updateURL();
}

// パラメータプルダウン更新
function updateParameters() {
    const apiSelect = document.getElementById('api');
    const paramSelect = document.getElementById('parameter');
    paramSelect.innerHTML = '<option value="">選択してください</option>';

    const apiId = apiSelect.value;
    if (apiId) {
        const paramGroups = {};
        data.parameters
            .filter(param => param.apiId === apiId)
            .forEach(param => {
                if (!paramGroups[param.id]) {
                    paramGroups[param.id] = param.name;
                }
            });

        Object.entries(paramGroups).forEach(([id, name]) => {
            const option = document.createElement('option');
            option.value = id;
            option.textContent = `${id}_${name}`;
            paramSelect.appendChild(option);
        });
    }
    displayParameterDetails();
}

// パラメータ詳細表示
function displayParameterDetails() {
    const apiSelect = document.getElementById('api');
    const paramSelect = document.getElementById('parameter');
    const paramDetails = document.getElementById('parameterDetails');
    paramDetails.innerHTML = '';

    const apiId = apiSelect.value;
    const paramId = paramSelect.value;
    if (apiId && paramId) {
        data.parameters
            .filter(param => param.apiId === apiId && param.id === paramId)
            .forEach(param => {
                if (param.value !== "") {
                    const row = document.createElement('tr');
                    row.innerHTML = `
                        <td>${param.logicalName}</td>
                        <td>${param.physicalName}</td>
                        <td><input type="text" value="${param.value}" onchange="updateParameterValue('${param.physicalName}', this.value)"></td>
                    `;
                    paramDetails.appendChild(row);
                }
            });
    }
}

// パラメータ値更新
let editedParameters = {};
function updateParameterValue(physicalName, value) {
    editedParameters[physicalName] = value;
}

// カスタムURL更新
function updateCustomURL() {
    customURL = document.getElementById('requestUrl').value;
}

// リクエストURL更新
function updateURL() {
    const envSelect = document.getElementById('environment');
    const apiSelect = document.getElementById('api');
    const requestUrl = document.getElementById('requestUrl');
    
    const envId = envSelect.value;
    const apiId = apiSelect.value;
    if (envId && apiId) {
        const env = data.environments.find(e => e.id === envId);
        const defaultUrl = `${env.baseURL}${apiId}`;
        requestUrl.value = customURL || defaultUrl; // カスタムURLがなければデフォルト
        customURL = ''; // リセット
    } else {
        requestUrl.value = '';
    }
}

// セクションの開閉
function toggleSection(sectionId) {
    const section = document.getElementById(sectionId);
    const toggle = document.getElementById(`${sectionId}-toggle`);
    section.classList.toggle('open');
    toggle.textContent = section.classList.contains('open') ? '[-]' : '[+]';
}

// リクエスト送信
function sendRequest() {
    const envSelect = document.getElementById('environment');
    const apiSelect = document.getElementById('api');
    const methodSelect = document.getElementById('method');
    const headersInput = document.getElementById('requestHeaders');
    const requestUrl = document.getElementById('requestUrl');
    const responseContent = document.getElementById('responseContent');

    const envId = envSelect.value;
    const apiId = apiSelect.value;
    const method = methodSelect.value;

    if (!envId || !apiId) {
        responseContent.textContent = '環境とAPIを選択してください';
        return;
    }

    const url = requestUrl.value; // 編集されたURLを使用
    let headers = {};
    try {
        headers = JSON.parse(headersInput.value || '{}');
    } catch (e) {
        responseContent.textContent = '無効なヘッダ形式';
        return;
    }

    // JSONボディ生成
    const paramSelect = document.getElementById('parameter');
    const paramId = paramSelect.value;
    const body = {};
    if (paramId) {
        data.parameters
            .filter(param => param.apiId === apiId && param.id === paramId)
            .forEach(param => {
                const value = editedParameters[param.physicalName] || param.value;
                if (value !== "") {
                    let parsedValue = value;
                    if (value.startsWith('[') && value.endsWith(']')) {
                        try {
                            parsedValue = JSON.parse(value);
                        } catch (e) {
                            console.warn(`Invalid JSON array: ${value}`);
                        }
                    }
                    setNestedValue(body, param.physicalName, parsedValue);
                }
            });
    }

    // リクエスト送信
    fetch(url, {
        method: method,
        headers: headers,
        body: method === 'POST' ? JSON.stringify({ [apiId]: body }) : undefined
    })
    .then(response => {
        responseContent.textContent = `Status: ${response.status}\n`;
        return response.text();
    })
    .then(text => {
        try {
            const json = JSON.parse(text);
            responseContent.textContent += JSON.stringify(json, null, 2);
        } catch {
            responseContent.textContent += text;
        }
        toggleSection('response');
    })
    .catch(error => {
        responseContent.textContent = `エラー: ${error.message}`;
        toggleSection('response');
    });
}

// ネストされた値の設定
function setNestedValue(obj, key, value) {
    const parts = key.split('.');
    let current = obj;

    for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        const arrayMatch = part.match(/(\w+)\[(\d+)\]/);

        if (arrayMatch) {
            const arrayName = arrayMatch[1];
            const index = parseInt(arrayMatch[2]);

            current[arrayName] = current[arrayName] || [];
            while (current[arrayName].length <= index) {
                current[arrayName].push(null);
            }

            if (i === parts.length - 1) {
                current[arrayName][index] = value;
            } else {
                current[arrayName][index] = current[arrayName][index] || {};
                current = current[arrayName][index];
            }
        } else {
            if (i === parts.length - 1) {
                current[part] = value;
            } else {
                current[part] = current[part] || {};
                current = current[part];
            }
        }
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?