diff --git a/OfficeScript/ExcelToCSV.ts b/OfficeScript/ExcelToCSV.ts new file mode 100644 index 0000000..898e96f --- /dev/null +++ b/OfficeScript/ExcelToCSV.ts @@ -0,0 +1,44 @@ +// Excel方眼紙をcsvに変換するOfficeスクリプトです。想定するExcelファイルはSample.xlsxをご確認ください。 +function main(workbook: ExcelScript.Workbook) { + // アクティブなワークシートを取得します + const sheet = workbook.getActiveWorksheet(); + + // シートの範囲を取得します + const range = sheet.getUsedRange(); + const values = range.getValues(); + + // "No"という値が初めて現れる行のインデックスを特定する + let startRowIndex = -1; + for (let i = 0; i < values.length; i++) { + if (values[i].includes("No")) { + startRowIndex = i; + break; + } + } + + if (startRowIndex === -1) { + console.log("Noが見つかりませんでした。"); + return; + } + + // CSVとして保存する文字列を作成します + let csvContent = ""; + + // "No"が出現する行から始まる行を走査し、コンマで区切られた文字列に変換します + for (let i = startRowIndex; i < values.length; i++) { + // 空白や無効なエントリをフィルタリング + const processedRow = values[i].filter(cell => cell != null && cell !== ''); + + // コンマの連続を取り除く + let row = processedRow.join(",").replace(/,{2,}/g, ','); // 連続するコンマを1つに置換 + + // 行がカンマだけでない場合のみ追加 + if (row.replace(/,/g, '').trim() !== '') { + csvContent += row + "\r\n"; // 改行コードを追加 + } + } + + // 結果をログに出力します。 + console.log(csvContent); + +} diff --git a/Python/Sample.xlsx.yaml b/Python/Sample.xlsx.yaml new file mode 100644 index 0000000..9b59749 --- /dev/null +++ b/Python/Sample.xlsx.yaml @@ -0,0 +1,69 @@ +openapi: 3.0.0 +info: + title: API Documentation + version: 1.0.0 +paths: + example_endpoint: + post: + summary: Sample + description: どう取得するか保留 + operationId: example_operationId + responses: + '200': + description: レスポンスの説明をどう取得するか保留 + content: + application/json;charset=utf-8: + schema: + type: object + properties: + result-code: + type: string + description: システム作成のコード + example: R0000000 + result-message: + type: string + description: システム生成のメッセージ + example: リクエスト完了 + user-id: + type: string + description: ユーザーID + user-info: + type: list + description: ユーザーの所属情報を格納するリスト + affiliation-code: + type: string + description: 所属を表すコード + affiliation-name: + type: string + description: 所属名 + user-name: + type: string + description: ユーザー氏名 + user-birth: + type: string + description: ユーザー生年月日 + user-phone-number: + type: string + description: ユーザー電話番号 + required: + - result-code + - result-message + - user-id + - user-info + - affiliation-code + - affiliation-name + - user-name + - user-phone-number + requestBody: + description: 未定 + required: + - user-id + content: + application/json;charset=utf-8: + schema: + type: object + properties: + user-id: + type: string + description: ユーザーID + example: XXXXX diff --git a/Python/exchange_csv_to_json.py b/Python/exchange_csv_to_json.py new file mode 100644 index 0000000..3b9a563 --- /dev/null +++ b/Python/exchange_csv_to_json.py @@ -0,0 +1,101 @@ +import csv +import json + +# 入力CSVファイルのパス +input_csv = 'input.csv' + +# OpenAPI仕様の初期テンプレート +oas_template = { + "openapi": "3.0.0", + "info": { + "title": "API Document", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "schemas": {} + } +} + +def nest_schema(hierarchy, current_level, properties): + if not hierarchy: + return properties + + next_level = hierarchy.pop(0) + if next_level not in current_level: + current_level[next_level] = { + "type": "object", + "properties": {} + } + + current_level[next_level]["properties"].update(properties) + return current_level + +# CSVファイルの読み取り +with open(input_csv, mode='r', encoding='utf-8') as file: + reader = csv.DictReader(file) + components_schemas = {} + path_specs = { + "/examplePath": { + "get": { + "summary": "Example summary", + "responses": { + "200": { + "description": "A successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + }, + "headers": {} + } + } + } + } + } + response_properties = {} + hierarchy_map = {} + + for row in reader: + hierarchy = row['階層'].split('.') + if row['項目(英名)'] == 'Content-Type': + path_specs["/examplePath"]["get"]["responses"]["200"]["headers"]["Content-Type"] = { + "schema": { + "type": "string", + "example": row['値例'] + }, + "description": row['項目説明'] + } + elif row['項目(英名)'] == 'StatusCode': + path_specs["/examplePath"]["get"]["responses"]["200"]["headers"]["StatusCode"] = { + "schema": { + "type": "integer", + "example": int(row['値例']) + }, + "description": row['項目説明'] + } + else: + nested_property = { + row['項目(英名)']: { + "type": "string" if row['データ型'] == 'string' else "integer", + "description": row['項目説明'] + } + } + response_properties = nest_schema(hierarchy, response_properties, nested_property) + + components_schemas["Response"] = { + "type": "object", + "properties": response_properties + } + +# OpenAPIテンプレートに生成されたコンポーネントとパスを追加 +oas_template["components"]["schemas"] = components_schemas +oas_template["paths"] = path_specs + +# JSONファイルに出力 +with open('openapi_spec.json', 'w', encoding='utf-8') as json_file: + json.dump(oas_template, json_file, ensure_ascii=False, indent=4) + +print("OpenAPI specification has been generated successfully!") \ No newline at end of file diff --git a/Python/exchange_excel_to_oas_yaml.py b/Python/exchange_excel_to_oas_yaml.py new file mode 100644 index 0000000..842379c --- /dev/null +++ b/Python/exchange_excel_to_oas_yaml.py @@ -0,0 +1,269 @@ +import pandas as pd +import os +import yaml + +excel_file = '/home/user/codes/japanesetraditionalexcel2openapi/Sample/Sample.xlsx' +#df_sheet_multi = pd.read_excel(excel_file, sheet_name=['入力項目定義','出力項目定義'], header=6) +read_sheet_header = 7 +df_sheet_input_fields = pd.read_excel(excel_file, sheet_name='入力項目定義', header=read_sheet_header) +df_sheet_output_fields = pd.read_excel(excel_file, sheet_name='出力項目定義', header=read_sheet_header) + +# 読み込んだシートに対して処理を行う +# Excel方眼紙であるために発生するUnnamedなヘッダーのカラムを削除し、冒頭2行に発生しがちな値がすべてNaNの行を取り除く +# そもそも項目名で取り出すしいらない処理かもしれない +## 少なくともNaNを削除する.dropna(how='all')はあったほうがスムーズに値を取り出せそう +df_sheet_input_fields = df_sheet_input_fields.loc[:, ~df_sheet_input_fields.columns.str.contains('^Unnamed')].dropna(how='all') +df_sheet_output_fields = df_sheet_output_fields.loc[:, ~df_sheet_output_fields.columns.str.contains('^Unnamed')].dropna(how='all') + +# sumary用にファイル名から単語を取得 +excel_file_name = os.path.basename(excel_file) +## 拡張子を除いたファイル名を取得 +summary_text = os.path.splitext(excel_file_name)[0] +# アンダーバー以前の部分を取得 +if '_' in summary_text: + summary_text = summary_text.split('_')[0] +else: + summary_text = summary_text + +# Excelから取得できない部分の設定 +http_request_set = "post" #API設計書から判断する必要がある +endpoint_path_set = "example_endpoint" +operation_id_set = "example_operationId" +response_code = "200"#Excelファイルから取得した値から指定するか、なければ200指定?というか記載が無いだけで400とか他も設定必要では->200決め打ちでいいっぽい +responses_schema_type = "object" + +# Excelファイル内からレスポンスヘッダのコンテンツタイプ取得 +input_fields_content_type = df_sheet_input_fields[df_sheet_input_fields['項目名(英語)'].str.contains('content-type', case=False)]['値の例'].iloc[0] +output_fields_content_type = df_sheet_output_fields[df_sheet_output_fields['項目名(英語)'].str.contains('content-type', case=False)]['値の例'].iloc[0] + +#実行結果の確認。随時削除予定 +# print(summary_text) +# print(input_fields_content_type) +# print("入力項目定義") +# print(df_sheet_input_fields) +# print("出力項目定義") +# print(df_sheet_output_fields) + +# ここから必要な項目についての話というか作りたい最終的なフォーマットの話 +""" +Excelに含まれるデータは +No,階層,項目名(日本語),項目名(英語),データ型,桁数,必須,入出力区分,種別,項目の説明,値の例 +1.GETかPOSTかの設定は変数を置いて各自で行う +2.パスの設定も各自で行う +3. Excelから取得可能な情報は使って埋める +以下は想定しているyamlファイルの完成形。プログラムで入れる部分は${}で示す。 +path: + ${endpoint_path_set} + ${http_request_set}: + tags: + - 多分ファイル名で申請アプリ_XXX_でグループ化されているならあったほうがいいもの。いったん無視でいいかも + summary:${summary_text} + description: 保留 + operationId: ${operation_id_set} + ---メソッドがpost(get以外)の場合--- + requestBody: + description:未定 + requiered: + - [必須項目がyになっている項目名を列挙] + content: + ${input_fields_content_type} + schema: + type: object + properties: + [項目名(英名)]: + type: [データ型] + description: [項目説明] + example: [値例] + --- + ---メソッドがgetでクエリパラメータやパスパラメータを持つ場合--- + parameters: + - name: "クエリパラメータ名" + in: "query" + required: true/false + schema: + type: + description: + - name: "パスパラメータ名" + .... + --- + response: + 200: + description: + 'どうやって埋めるか保留' + content: + [値例] + schema: + type: object + properties: + 英語項目名1: + type: [データ型] + description: [項目説明] + example: [値例] + ---もしネスト(次の行の階層が2以上なら)--- + properties: + 英語項目名: + type: [データ型] + description: [項目説明] + example: [値例] + + 英語項目名2: + ... + required: + 必須項目がyになっている項目名を列挙 + 必須項目がyになっている項目名を列挙 + +""" +# ここから実装 +# OASの基本構造 +oas = { + "openapi": "3.0.0", + "info": { + "title": "API Documentation", + "version": "1.0.0" + }, + "paths": { + endpoint_path_set: { + http_request_set: { + "summary": summary_text, # 例: 保留 + "description": "どう取得するか保留", + "operationId": operation_id_set, # 例: 各自設定 + "responses": { + response_code: { + "description": "レスポンスの説明をどう取得するか保留", + "content": { + output_fields_content_type: { + "schema": { + "type": responses_schema_type, + "properties": {} + } + } + } + } + } + } + } + } +} + +# POSTメソッドの場合、requestBodyを追加 +if http_request_set == "post": + oas["paths"][endpoint_path_set][http_request_set]["requestBody"] = { + "description": "未定", + "required": [], + "content": { + input_fields_content_type: { + "schema": { + "type": "object", + "properties": {} + } + } + } + } + +# ネスト用のスタック +#stack = [] + +# リクエストボディ用の更新 +for index, row in df_sheet_input_fields.iterrows(): + # ヘッダー行はスキップ + if row["種別"].lower() == "header": + continue + field_name_english = row["項目名(英語)"] + data_type = row["データ型"] + description = row["項目の説明"] + example = row["値の例"] + required = str(row["必須"]).lower() == "y" + hierarchy = row["階層"] + + + prop = { + "type": data_type, + "description": description + } + # example が NaN でない場合のみ追加 + if not pd.isna(example): + prop["example"] = example + + oas["paths"][endpoint_path_set][http_request_set]["requestBody"]["content"][input_fields_content_type]["schema"]["properties"][field_name_english] = prop + + if required: + oas["paths"][endpoint_path_set][http_request_set]["requestBody"].setdefault("required", []).append(field_name_english) + +# responsesの更新 +# ネスト用のスタック +stack = [] +current_properties = oas["paths"][endpoint_path_set][http_request_set]["responses"][response_code]["content"][output_fields_content_type]["schema"]["properties"] +# レスポンスボディ用の更新 +for index, row in df_sheet_output_fields.iterrows(): + # ヘッダー行はスキップ + if row["種別"].lower() == "header": + continue + field_name_english = row["項目名(英語)"] + data_type = row["データ型"] + description = row["項目の説明"] + example = row["値の例"] + hierarchy = int(row["階層"]) + # 空白の場合.lowerがエラーになるので文字列に変換してから判定 + required = str(row["必須"]).lower() == "y" + + prop = { + "type": data_type, + "description": description + } + + # プロパティの設定。listとなっていたらarrayに変換 + if data_type == "list": + prop = { + "type": "array", + "description": description, + "items": { + "type": "object", + "properties": {} + } + } + else: + prop = { + "type": data_type, + "description": description + } + + # example は NaN でない場合のみ追加 + if not pd.isna(example): + prop["example"] = example + + # ネストの管理: 現在の階層より高い階層の要素をスタックから取り除く + while stack and stack[-1]['hierarchy'] >= hierarchy: + stack.pop() + + # 現在の階層のプロパティに追加 + if stack: + parent_properties = stack[-1]['object']['properties'] + parent_properties[field_name_english] = prop + else: + current_properties[field_name_english] = prop + + # properties の追加は object 型要素にのみ適用 + if data_type == "object": + prop['properties'] = {} + stack.append({'hierarchy': hierarchy, 'object': prop}) + # # 現状一番うまくいっているがネスト時に不要なpropertiesが含まれてしまうやつ + # # 現在の階層のプロパティに追加 + # if stack: + # current_properties = stack[-1]['object']['properties'] + # else: + # current_properties = oas["paths"][endpoint_path_set][http_request_set]["responses"][response_code]["content"][output_fields_content_type] + # current_properties[field_name_english] = prop + # if 'properties' not in prop: + # prop['properties'] = {} + + # stack.append({'hierarchy': hierarchy, 'object': prop}) + + # 必須項目の設定 + if required: + oas["paths"][endpoint_path_set][http_request_set]["responses"]["200"].setdefault("required", []).append(field_name_english) + +output_file = f"{excel_file_name}.yaml" +with open(output_file, "w", encoding='utf8') as yaml_file: + yaml.dump(oas, yaml_file, allow_unicode=True, sort_keys=False) + +print("YAMLファイルに変換が完了しました。") \ No newline at end of file diff --git a/Sample/Sample.xlsx b/Sample/Sample.xlsx new file mode 100644 index 0000000..499ee26 Binary files /dev/null and b/Sample/Sample.xlsx differ diff --git a/Sample/Sample.xlsx.csv b/Sample/Sample.xlsx.csv new file mode 100644 index 0000000..e6806ce --- /dev/null +++ b/Sample/Sample.xlsx.csv @@ -0,0 +1,6 @@ +No,階層,項目名(日本語),項目名(英語),データ型,桁数,必須,入出力区分,種別,項目の説明,値の例 +1,1,コンテンツタイプ,content-type,string,y,入力,HEADER,レスポンスヘッダーのコンテンツタイプ,application/json;charset=utf-8 +2,1,HTTPステータスコード,http-status,number,3,y,入力,HEADER,HTTPのレスポンスコード,200 +3,1,氏名,user-name,string,,y,入力,BODY,ユーザー氏名 +4,1,生年月日,user-birth,string,,y,入力,BODY,ユーザー生年月日 +5,1,電話番号,user-phone-number,string,,y,入力,BODY,ユーザー電話番号 \ No newline at end of file