Commit 41b668f8 authored by 段廷瑞's avatar 段廷瑞

feat: 文本格式化功能

parent 2705cb1f
...@@ -62,3 +62,4 @@ bin ...@@ -62,3 +62,4 @@ bin
/.claude/settings.local.json /.claude/settings.local.json
/AppHome/ClaudeChat /AppHome/ClaudeChat
/CLAUDE.md /CLAUDE.md
*.zip
...@@ -365,6 +365,12 @@ ...@@ -365,6 +365,12 @@
<Compile Include="UI\wTaskBoard.Designer.cs"> <Compile Include="UI\wTaskBoard.Designer.cs">
<DependentUpon>wTaskBoard.cs</DependentUpon> <DependentUpon>wTaskBoard.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\wTextFormatter.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\wTextFormatter.Designer.cs">
<DependentUpon>wTextFormatter.cs</DependentUpon>
</Compile>
<Compile Include="UI\wUser.cs"> <Compile Include="UI\wUser.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
...@@ -524,6 +530,9 @@ ...@@ -524,6 +530,9 @@
<EmbeddedResource Include="UI\wTaskBoard.resx"> <EmbeddedResource Include="UI\wTaskBoard.resx">
<DependentUpon>wTaskBoard.cs</DependentUpon> <DependentUpon>wTaskBoard.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="UI\wTextFormatter.resx">
<DependentUpon>wTextFormatter.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\wUser.resx"> <EmbeddedResource Include="UI\wUser.resx">
<DependentUpon>wUser.cs</DependentUpon> <DependentUpon>wUser.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
......
namespace Tools.UI
{
partial class wTextFormatter
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tabControl = new System.Windows.Forms.TabControl();
this.tabSql = new System.Windows.Forms.TabPage();
this.tlpSql = new System.Windows.Forms.TableLayoutPanel();
this.panelSqlInputHeader = new System.Windows.Forms.Panel();
this.lblSqlInput = new System.Windows.Forms.Label();
this.lblDialect = new System.Windows.Forms.Label();
this.cbSqlDialect = new System.Windows.Forms.ComboBox();
this.btnFormatSql = new System.Windows.Forms.Button();
this.tbSqlInput = new System.Windows.Forms.TextBox();
this.panelSqlResult = new System.Windows.Forms.Panel();
this.lblSqlOutput = new System.Windows.Forms.Label();
this.btnCopySql = new System.Windows.Forms.Button();
this.tbSqlOutput = new System.Windows.Forms.TextBox();
this.tabJson = new System.Windows.Forms.TabPage();
this.tlpJson = new System.Windows.Forms.TableLayoutPanel();
this.panelJsonInputHeader = new System.Windows.Forms.Panel();
this.lblJsonInput = new System.Windows.Forms.Label();
this.btnUnescape = new System.Windows.Forms.Button();
this.btnEscape = new System.Windows.Forms.Button();
this.btnFormatJson = new System.Windows.Forms.Button();
this.tbJsonInput = new System.Windows.Forms.TextBox();
this.panelJsonResult = new System.Windows.Forms.Panel();
this.lblJsonOutput = new System.Windows.Forms.Label();
this.btnExpand = new System.Windows.Forms.Button();
this.btnCompress = new System.Windows.Forms.Button();
this.btnCopyJson = new System.Windows.Forms.Button();
this.tbJsonOutput = new System.Windows.Forms.TextBox();
this.tabControl.SuspendLayout();
this.tabSql.SuspendLayout();
this.tlpSql.SuspendLayout();
this.panelSqlInputHeader.SuspendLayout();
this.panelSqlResult.SuspendLayout();
this.tabJson.SuspendLayout();
this.tlpJson.SuspendLayout();
this.panelJsonInputHeader.SuspendLayout();
this.panelJsonResult.SuspendLayout();
this.SuspendLayout();
//
// tabControl
//
this.tabControl.Controls.Add(this.tabSql);
this.tabControl.Controls.Add(this.tabJson);
this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl.Location = new System.Drawing.Point(0, 0);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(800, 580);
this.tabControl.TabIndex = 0;
//
// tabSql
//
this.tabSql.Controls.Add(this.tlpSql);
this.tabSql.Location = new System.Drawing.Point(4, 26);
this.tabSql.Name = "tabSql";
this.tabSql.Padding = new System.Windows.Forms.Padding(3);
this.tabSql.Size = new System.Drawing.Size(792, 550);
this.tabSql.TabIndex = 0;
this.tabSql.Text = "SQL格式化";
this.tabSql.UseVisualStyleBackColor = true;
//
// tlpSql
//
this.tlpSql.ColumnCount = 3;
this.tlpSql.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpSql.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpSql.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpSql.Controls.Add(this.panelSqlInputHeader, 1, 1);
this.tlpSql.Controls.Add(this.tbSqlInput, 1, 2);
this.tlpSql.Controls.Add(this.panelSqlResult, 1, 3);
this.tlpSql.Controls.Add(this.tbSqlOutput, 1, 4);
this.tlpSql.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpSql.Location = new System.Drawing.Point(3, 3);
this.tlpSql.Name = "tlpSql";
this.tlpSql.RowCount = 6;
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 45F));
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 55F));
this.tlpSql.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpSql.Size = new System.Drawing.Size(786, 544);
this.tlpSql.TabIndex = 0;
//
// panelSqlInputHeader
//
this.panelSqlInputHeader.Controls.Add(this.lblSqlInput);
this.panelSqlInputHeader.Controls.Add(this.lblDialect);
this.panelSqlInputHeader.Controls.Add(this.cbSqlDialect);
this.panelSqlInputHeader.Controls.Add(this.btnFormatSql);
this.panelSqlInputHeader.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelSqlInputHeader.Location = new System.Drawing.Point(9, 9);
this.panelSqlInputHeader.Name = "panelSqlInputHeader";
this.panelSqlInputHeader.Size = new System.Drawing.Size(768, 24);
this.panelSqlInputHeader.TabIndex = 0;
//
// lblSqlInput
//
this.lblSqlInput.AutoSize = true;
this.lblSqlInput.Location = new System.Drawing.Point(0, 4);
this.lblSqlInput.Name = "lblSqlInput";
this.lblSqlInput.Size = new System.Drawing.Size(82, 17);
this.lblSqlInput.TabIndex = 0;
this.lblSqlInput.Text = "输入SQL文本:";
this.lblSqlInput.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lblDialect
//
this.lblDialect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.lblDialect.AutoSize = true;
this.lblDialect.Location = new System.Drawing.Point(498, 4);
this.lblDialect.Name = "lblDialect";
this.lblDialect.Size = new System.Drawing.Size(58, 17);
this.lblDialect.TabIndex = 1;
this.lblDialect.Text = "SQL方言:";
this.lblDialect.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// cbSqlDialect
//
this.cbSqlDialect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cbSqlDialect.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cbSqlDialect.FormattingEnabled = true;
this.cbSqlDialect.Location = new System.Drawing.Point(557, 0);
this.cbSqlDialect.Name = "cbSqlDialect";
this.cbSqlDialect.Size = new System.Drawing.Size(110, 25);
this.cbSqlDialect.TabIndex = 2;
//
// btnFormatSql
//
this.btnFormatSql.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnFormatSql.Location = new System.Drawing.Point(675, 0);
this.btnFormatSql.Name = "btnFormatSql";
this.btnFormatSql.Size = new System.Drawing.Size(90, 25);
this.btnFormatSql.TabIndex = 3;
this.btnFormatSql.Text = "SQL格式化";
this.btnFormatSql.UseVisualStyleBackColor = true;
this.btnFormatSql.Click += new System.EventHandler(this.btnFormatSql_Click);
//
// tbSqlInput
//
this.tbSqlInput.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbSqlInput.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.tbSqlInput.Location = new System.Drawing.Point(9, 39);
this.tbSqlInput.Multiline = true;
this.tbSqlInput.Name = "tbSqlInput";
this.tbSqlInput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.tbSqlInput.Size = new System.Drawing.Size(768, 206);
this.tbSqlInput.TabIndex = 2;
this.tbSqlInput.WordWrap = false;
//
// panelSqlResult
//
this.panelSqlResult.Controls.Add(this.lblSqlOutput);
this.panelSqlResult.Controls.Add(this.btnCopySql);
this.panelSqlResult.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelSqlResult.Location = new System.Drawing.Point(9, 251);
this.panelSqlResult.Name = "panelSqlResult";
this.panelSqlResult.Size = new System.Drawing.Size(768, 24);
this.panelSqlResult.TabIndex = 3;
//
// lblSqlOutput
//
this.lblSqlOutput.AutoSize = true;
this.lblSqlOutput.Location = new System.Drawing.Point(0, 4);
this.lblSqlOutput.Name = "lblSqlOutput";
this.lblSqlOutput.Size = new System.Drawing.Size(71, 17);
this.lblSqlOutput.TabIndex = 0;
this.lblSqlOutput.Text = "格式化结果:";
this.lblSqlOutput.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnCopySql
//
this.btnCopySql.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCopySql.Location = new System.Drawing.Point(675, 0);
this.btnCopySql.Name = "btnCopySql";
this.btnCopySql.Size = new System.Drawing.Size(90, 25);
this.btnCopySql.TabIndex = 0;
this.btnCopySql.Text = "复制结果";
this.btnCopySql.UseVisualStyleBackColor = true;
this.btnCopySql.Click += new System.EventHandler(this.btnCopySql_Click);
//
// tbSqlOutput
//
this.tbSqlOutput.BackColor = System.Drawing.SystemColors.ControlLightLight;
this.tbSqlOutput.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbSqlOutput.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.tbSqlOutput.Location = new System.Drawing.Point(9, 281);
this.tbSqlOutput.Multiline = true;
this.tbSqlOutput.Name = "tbSqlOutput";
this.tbSqlOutput.ReadOnly = true;
this.tbSqlOutput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.tbSqlOutput.Size = new System.Drawing.Size(768, 253);
this.tbSqlOutput.TabIndex = 4;
this.tbSqlOutput.WordWrap = false;
//
// tabJson
//
this.tabJson.Controls.Add(this.tlpJson);
this.tabJson.Location = new System.Drawing.Point(4, 26);
this.tabJson.Name = "tabJson";
this.tabJson.Padding = new System.Windows.Forms.Padding(3);
this.tabJson.Size = new System.Drawing.Size(792, 550);
this.tabJson.TabIndex = 1;
this.tabJson.Text = "JSON格式化";
this.tabJson.UseVisualStyleBackColor = true;
//
// tlpJson
//
this.tlpJson.ColumnCount = 3;
this.tlpJson.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpJson.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpJson.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpJson.Controls.Add(this.panelJsonInputHeader, 1, 1);
this.tlpJson.Controls.Add(this.tbJsonInput, 1, 2);
this.tlpJson.Controls.Add(this.panelJsonResult, 1, 3);
this.tlpJson.Controls.Add(this.tbJsonOutput, 1, 4);
this.tlpJson.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpJson.Location = new System.Drawing.Point(3, 3);
this.tlpJson.Name = "tlpJson";
this.tlpJson.RowCount = 6;
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 45F));
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 55F));
this.tlpJson.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));
this.tlpJson.Size = new System.Drawing.Size(786, 544);
this.tlpJson.TabIndex = 0;
//
// panelJsonInputHeader
//
this.panelJsonInputHeader.Controls.Add(this.lblJsonInput);
this.panelJsonInputHeader.Controls.Add(this.btnUnescape);
this.panelJsonInputHeader.Controls.Add(this.btnEscape);
this.panelJsonInputHeader.Controls.Add(this.btnFormatJson);
this.panelJsonInputHeader.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelJsonInputHeader.Location = new System.Drawing.Point(9, 9);
this.panelJsonInputHeader.Name = "panelJsonInputHeader";
this.panelJsonInputHeader.Size = new System.Drawing.Size(768, 24);
this.panelJsonInputHeader.TabIndex = 0;
//
// lblJsonInput
//
this.lblJsonInput.AutoSize = true;
this.lblJsonInput.Location = new System.Drawing.Point(0, 4);
this.lblJsonInput.Name = "lblJsonInput";
this.lblJsonInput.Size = new System.Drawing.Size(91, 17);
this.lblJsonInput.TabIndex = 0;
this.lblJsonInput.Text = "输入JSON文本:";
this.lblJsonInput.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnUnescape
//
this.btnUnescape.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnUnescape.Location = new System.Drawing.Point(487, 0);
this.btnUnescape.Name = "btnUnescape";
this.btnUnescape.Size = new System.Drawing.Size(90, 25);
this.btnUnescape.TabIndex = 1;
this.btnUnescape.Text = "去除转义";
this.btnUnescape.UseVisualStyleBackColor = true;
this.btnUnescape.Click += new System.EventHandler(this.btnUnescape_Click);
//
// btnEscape
//
this.btnEscape.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnEscape.Location = new System.Drawing.Point(582, 0);
this.btnEscape.Name = "btnEscape";
this.btnEscape.Size = new System.Drawing.Size(90, 25);
this.btnEscape.TabIndex = 2;
this.btnEscape.Text = "添加转义";
this.btnEscape.UseVisualStyleBackColor = true;
this.btnEscape.Click += new System.EventHandler(this.btnEscape_Click);
//
// btnFormatJson
//
this.btnFormatJson.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnFormatJson.Location = new System.Drawing.Point(677, 0);
this.btnFormatJson.Name = "btnFormatJson";
this.btnFormatJson.Size = new System.Drawing.Size(90, 25);
this.btnFormatJson.TabIndex = 3;
this.btnFormatJson.Text = "JSON格式化";
this.btnFormatJson.UseVisualStyleBackColor = true;
this.btnFormatJson.Click += new System.EventHandler(this.btnFormatJson_Click);
//
// tbJsonInput
//
this.tbJsonInput.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbJsonInput.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.tbJsonInput.Location = new System.Drawing.Point(9, 39);
this.tbJsonInput.Multiline = true;
this.tbJsonInput.Name = "tbJsonInput";
this.tbJsonInput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.tbJsonInput.Size = new System.Drawing.Size(768, 206);
this.tbJsonInput.TabIndex = 1;
this.tbJsonInput.WordWrap = false;
//
// panelJsonResult
//
this.panelJsonResult.Controls.Add(this.lblJsonOutput);
this.panelJsonResult.Controls.Add(this.btnExpand);
this.panelJsonResult.Controls.Add(this.btnCompress);
this.panelJsonResult.Controls.Add(this.btnCopyJson);
this.panelJsonResult.Dock = System.Windows.Forms.DockStyle.Fill;
this.panelJsonResult.Location = new System.Drawing.Point(9, 251);
this.panelJsonResult.Name = "panelJsonResult";
this.panelJsonResult.Size = new System.Drawing.Size(768, 24);
this.panelJsonResult.TabIndex = 2;
//
// lblJsonOutput
//
this.lblJsonOutput.AutoSize = true;
this.lblJsonOutput.Location = new System.Drawing.Point(0, 4);
this.lblJsonOutput.Name = "lblJsonOutput";
this.lblJsonOutput.Size = new System.Drawing.Size(71, 17);
this.lblJsonOutput.TabIndex = 0;
this.lblJsonOutput.Text = "格式化结果:";
this.lblJsonOutput.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// btnExpand
//
this.btnExpand.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnExpand.Location = new System.Drawing.Point(516, 0);
this.btnExpand.Name = "btnExpand";
this.btnExpand.Size = new System.Drawing.Size(75, 25);
this.btnExpand.TabIndex = 1;
this.btnExpand.Text = "展开";
this.btnExpand.UseVisualStyleBackColor = true;
this.btnExpand.Click += new System.EventHandler(this.btnExpand_Click);
//
// btnCompress
//
this.btnCompress.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCompress.Location = new System.Drawing.Point(597, 0);
this.btnCompress.Name = "btnCompress";
this.btnCompress.Size = new System.Drawing.Size(75, 25);
this.btnCompress.TabIndex = 2;
this.btnCompress.Text = "压缩";
this.btnCompress.UseVisualStyleBackColor = true;
this.btnCompress.Click += new System.EventHandler(this.btnCompress_Click);
//
// btnCopyJson
//
this.btnCopyJson.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCopyJson.Location = new System.Drawing.Point(677, 0);
this.btnCopyJson.Name = "btnCopyJson";
this.btnCopyJson.Size = new System.Drawing.Size(90, 25);
this.btnCopyJson.TabIndex = 3;
this.btnCopyJson.Text = "复制结果";
this.btnCopyJson.UseVisualStyleBackColor = true;
this.btnCopyJson.Click += new System.EventHandler(this.btnCopyJson_Click);
//
// tbJsonOutput
//
this.tbJsonOutput.BackColor = System.Drawing.SystemColors.ControlLightLight;
this.tbJsonOutput.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbJsonOutput.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.tbJsonOutput.Location = new System.Drawing.Point(9, 281);
this.tbJsonOutput.Multiline = true;
this.tbJsonOutput.Name = "tbJsonOutput";
this.tbJsonOutput.ReadOnly = true;
this.tbJsonOutput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.tbJsonOutput.Size = new System.Drawing.Size(768, 253);
this.tbJsonOutput.TabIndex = 3;
this.tbJsonOutput.WordWrap = false;
//
// wTextFormatter
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 580);
this.Controls.Add(this.tabControl);
this.MinimumSize = new System.Drawing.Size(640, 420);
this.Name = "wTextFormatter";
this.Text = "文本格式化";
this.tabControl.ResumeLayout(false);
this.tabSql.ResumeLayout(false);
this.tlpSql.ResumeLayout(false);
this.tlpSql.PerformLayout();
this.panelSqlInputHeader.ResumeLayout(false);
this.panelSqlInputHeader.PerformLayout();
this.panelSqlResult.ResumeLayout(false);
this.panelSqlResult.PerformLayout();
this.tabJson.ResumeLayout(false);
this.tlpJson.ResumeLayout(false);
this.tlpJson.PerformLayout();
this.panelJsonInputHeader.ResumeLayout(false);
this.panelJsonInputHeader.PerformLayout();
this.panelJsonResult.ResumeLayout(false);
this.panelJsonResult.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage tabSql;
private System.Windows.Forms.TabPage tabJson;
// ---- SQL tab 控件 ----
private System.Windows.Forms.TableLayoutPanel tlpSql;
private System.Windows.Forms.Panel panelSqlInputHeader;
private System.Windows.Forms.Label lblDialect;
private System.Windows.Forms.ComboBox cbSqlDialect;
private System.Windows.Forms.Button btnFormatSql;
private System.Windows.Forms.Label lblSqlInput;
private System.Windows.Forms.TextBox tbSqlInput;
private System.Windows.Forms.Panel panelSqlResult;
private System.Windows.Forms.Label lblSqlOutput;
private System.Windows.Forms.Button btnCopySql;
private System.Windows.Forms.TextBox tbSqlOutput;
// ---- JSON tab 控件 ----
private System.Windows.Forms.TableLayoutPanel tlpJson;
private System.Windows.Forms.Panel panelJsonInputHeader;
private System.Windows.Forms.Button btnUnescape;
private System.Windows.Forms.Button btnEscape;
private System.Windows.Forms.Button btnFormatJson;
private System.Windows.Forms.Label lblJsonInput;
private System.Windows.Forms.TextBox tbJsonInput;
private System.Windows.Forms.Panel panelJsonResult;
private System.Windows.Forms.Label lblJsonOutput;
private System.Windows.Forms.Button btnExpand;
private System.Windows.Forms.Button btnCompress;
private System.Windows.Forms.Button btnCopyJson;
private System.Windows.Forms.TextBox tbJsonOutput;
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
//
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Tools.MyCommon;
using Tools.MyUtils;
namespace Tools.UI
{
public partial class wTextFormatter : wBasic
{
/// <summary>
/// 当前选择的SQL方言
/// </summary>
private string SelectedDialect => this.cbSqlDialect.SelectedItem?.ToString() ?? "SqlServer";
public wTextFormatter()
{
InitializeComponent();
InitDialectCombo();
}
/// <summary>
/// 初始化SQL方言下拉框
/// </summary>
private void InitDialectCombo()
{
this.cbSqlDialect.Items.AddRange(new object[]
{
"SqlServer",
"MySQL",
"PostgreSQL",
"Oracle",
"SQLite"
});
this.cbSqlDialect.SelectedIndex = 0;
}
// ==================== SQL Tab ====================
/// <summary>
/// 按钮事件:SQL格式化
/// </summary>
private void btnFormatSql_Click(object sender, EventArgs e)
{
string input = this.tbSqlInput.Text.Trim();
if (string.IsNullOrWhiteSpace(input))
{
new wMsgBox().Show("请输入SQL文本");
return;
}
try
{
this.tbSqlOutput.Text = SqlFormatHelper.Format(input);
this.tbSqlOutput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("SQL格式化失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:复制SQL结果
/// </summary>
private void btnCopySql_Click(object sender, EventArgs e)
{
CopyToClipboard(this.tbSqlOutput);
}
// ==================== JSON Tab ====================
/// <summary>
/// 按钮事件:JSON格式化
/// </summary>
private void btnFormatJson_Click(object sender, EventArgs e)
{
string input = this.tbJsonInput.Text.Trim();
if (string.IsNullOrWhiteSpace(input))
{
new wMsgBox().Show("请输入JSON文本");
return;
}
try
{
this.tbJsonOutput.Text = FormatJson(input);
this.tbJsonOutput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("JSON格式化失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:去除转义 — 将输入中的JSON转义字符还原
/// </summary>
private void btnUnescape_Click(object sender, EventArgs e)
{
string input = this.tbJsonInput.Text.Trim();
if (string.IsNullOrWhiteSpace(input))
{
new wMsgBox().Show("请输入JSON文本");
return;
}
try
{
this.tbJsonInput.Text = UnescapeJson(input);
this.tbJsonInput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("去除转义失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:转义 — 将输入文本转为JSON字符串字面量(对引号/换行等添加\转义)
/// </summary>
private void btnEscape_Click(object sender, EventArgs e)
{
string input = this.tbJsonInput.Text.Trim();
if (string.IsNullOrWhiteSpace(input))
{
new wMsgBox().Show("请输入JSON文本");
return;
}
try
{
this.tbJsonInput.Text = EscapeJson(input);
this.tbJsonInput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("转义失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:压缩 — 将格式化结果压缩为单行紧凑JSON
/// </summary>
private void btnCompress_Click(object sender, EventArgs e)
{
string output = this.tbJsonOutput.Text.Trim();
if (string.IsNullOrWhiteSpace(output))
{
new wMsgBox().Show("请先格式化JSON");
return;
}
try
{
this.tbJsonOutput.Text = CompressJson(output);
this.tbJsonOutput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("压缩失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:展开 — 将压缩的单行JSON重新格式化为缩进多行
/// </summary>
private void btnExpand_Click(object sender, EventArgs e)
{
string output = this.tbJsonOutput.Text.Trim();
if (string.IsNullOrWhiteSpace(output))
{
new wMsgBox().Show("请先输入或格式化JSON");
return;
}
try
{
this.tbJsonOutput.Text = FormatJson(output);
this.tbJsonOutput.Select(0, 0);
}
catch (Exception ex)
{
new wMsgBox().Show("展开失败:" + ex.Message);
}
}
/// <summary>
/// 按钮事件:复制JSON结果
/// </summary>
private void btnCopyJson_Click(object sender, EventArgs e)
{
CopyToClipboard(this.tbJsonOutput);
}
// ==================== 公共方法 ====================
/// <summary>
/// 复制文本框内容到剪贴板
/// </summary>
private static void CopyToClipboard(TextBox textBox)
{
if (!string.IsNullOrWhiteSpace(textBox.Text))
{
Clipboard.SetText(textBox.Text);
textBox.Focus();
textBox.SelectAll();
}
}
/// <summary>
/// 格式化JSON文本
/// </summary>
private static string FormatJson(string json)
{
JToken token = JToken.Parse(json);
return token.ToString(Formatting.Indented);
}
/// <summary>
/// 去除JSON转义 — 将 \" \n \t \\ 等转义字符还原为原始字符
/// </summary>
private static string UnescapeJson(string json)
{
// 若输入整体是一个JSON字符串字面量(首尾双引号),则解析取出内部值
if (json.Length >= 2 && json[0] == '"' && json[json.Length - 1] == '"')
{
try
{
JToken token = JToken.Parse(json);
if (token.Type == JTokenType.String)
return token.Value<string>();
}
catch { /* 解析失败则回退到逐字符替换 */ }
}
// 回退方案:逐字符还原常见转义
var sb = new StringBuilder(json.Length);
for (int i = 0; i < json.Length; i++)
{
if (json[i] == '\\' && i + 1 < json.Length)
{
char next = json[i + 1];
switch (next)
{
case '"': sb.Append('"'); i++; break;
case '\\': sb.Append('\\'); i++; break;
case '/': sb.Append('/'); i++; break;
case 'n': sb.Append('\n'); i++; break;
case 't': sb.Append('\t'); i++; break;
case 'r': sb.Append('\r'); i++; break;
case 'b': sb.Append('\b'); i++; break;
case 'f': sb.Append('\f'); i++; break;
case 'u':
if (i + 5 < json.Length)
{
string hex = json.Substring(i + 2, 4);
if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out int code))
{
sb.Append((char)code);
i += 5;
}
else { sb.Append(json[i]); }
}
else { sb.Append(json[i]); }
break;
default: sb.Append(json[i]); break;
}
}
else
{
sb.Append(json[i]);
}
}
return sb.ToString();
}
/// <summary>
/// 转义 — 将普通文本转为JSON字符串字面量(加外层引号, 转义内部控制字符)
/// </summary>
private static string EscapeJson(string text)
{
var sb = new StringBuilder(text.Length + 2);
sb.Append('"');
foreach (char c in text)
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\n': sb.Append("\\n"); break;
case '\t': sb.Append("\\t"); break;
case '\r': sb.Append("\\r"); break;
case '\b': sb.Append("\\b"); break;
case '\f': sb.Append("\\f"); break;
default:
if (c < 0x20)
sb.AppendFormat("\\u{0:X4}", (int)c);
else
sb.Append(c);
break;
}
}
sb.Append('"');
return sb.ToString();
}
/// <summary>
/// 压缩JSON — 去除所有空白,输出单行紧凑格式
/// </summary>
private static string CompressJson(string json)
{
JToken token = JToken.Parse(json);
return token.ToString(Formatting.None);
}
/// <summary>
/// SQL格式化辅助类
/// 格式化规则参考 db_helper:
/// - 复合关键字(LEFT JOIN, GROUP BY等)作为整体换行
/// - 短字段列表(≤3)内联, 长字段列表(≥4)每字段一行
/// - JOIN + ON 同行
/// - WHERE 首个条件与WHERE同行, 后续AND/OR换行缩进
/// - GROUP BY / ORDER BY 紧凑同行
/// - 子查询缩进
/// </summary>
private static class SqlFormatHelper
{
private const int IndentSize = 4;
private const int MaxInputLength = 10485760; // 10MB 上限
private const int MaxIndent = 200; // 最大缩进空格数
private const int MaxTokenCount = 200000; // 最大 Token 数(输入≤10MB)
// ---- 复合关键字定义 (组成对, 合并为单个 Token) ----
// key: 前缀关键字, value: 可组合的后续关键字集合
private static readonly Dictionary<string, HashSet<string>> CompoundPairs = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase)
{
{ "LEFT", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN", "OUTER" } },
{ "RIGHT", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN", "OUTER" } },
{ "INNER", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN" } },
{ "CROSS", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN" } },
{ "FULL", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN", "OUTER" } },
{ "OUTER", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JOIN" } },
{ "GROUP", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "BY" } },
{ "ORDER", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "BY" } },
{ "INSERT", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "INTO" } },
{ "DELETE", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "FROM" } },
{ "UNION", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ALL" } },
{ "FETCH", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "NEXT" } },
{ "IF", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "NOT" } },
{ "CREATE", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "OR" } },
{ "NOT", new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "EXISTS" } },
};
// 需要独立换行的子句关键字(复合形式优先)
private static readonly HashSet<string> ClauseStarters = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "HAVING",
"INSERT INTO", "UPDATE", "DELETE FROM", "SET", "VALUES",
"CREATE", "ALTER", "DROP", "TRUNCATE",
"LEFT JOIN", "RIGHT JOIN", "INNER JOIN", "CROSS JOIN", "FULL JOIN",
"LEFT OUTER JOIN", "RIGHT OUTER JOIN", "FULL OUTER JOIN",
"UNION", "UNION ALL", "INTERSECT", "EXCEPT", "MINUS",
"LIMIT", "OFFSET", "FETCH NEXT",
"WITH", "RETURNING", "MERGE",
"AND", "OR",
"WHEN", "ELSE",
"BEGIN", "COMMIT", "ROLLBACK",
"DECLARE",
"GO",
};
// 所有需要大写的SQL关键字
private static readonly HashSet<string> AllKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"SELECT", "FROM", "WHERE", "AND", "OR", "NOT", "IN", "EXISTS", "BETWEEN", "LIKE", "IS", "NULL",
"JOIN", "INNER", "LEFT", "RIGHT", "FULL", "CROSS", "OUTER", "ON",
"GROUP", "BY", "ORDER", "ASC", "DESC", "HAVING",
"INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE",
"CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW", "PROCEDURE", "FUNCTION", "TRIGGER",
"UNION", "ALL", "INTERSECT", "EXCEPT", "MINUS",
"LIMIT", "OFFSET", "FETCH", "NEXT", "ROWS", "ONLY", "TOP", "PERCENT",
"CASE", "WHEN", "THEN", "ELSE", "END",
"AS", "DISTINCT", "COUNT", "SUM", "AVG", "MIN", "MAX",
"BEGIN", "COMMIT", "ROLLBACK", "TRANSACTION",
"DECLARE", "EXEC", "EXECUTE",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "CONSTRAINT",
"DEFAULT", "CHECK", "UNIQUE", "CASCADE",
"WITH", "RECURSIVE", "RETURNING",
"MERGE", "USING", "MATCHED",
"IF", "WHILE", "FOR", "RETURN",
"CAST", "CONVERT", "COALESCE", "NULLIF",
"OVER", "PARTITION", "ROW_NUMBER", "RANK", "DENSE_RANK",
"SCHEMA", "DATABASE", "USE",
"GRANT", "REVOKE", "DENY",
"TRUNCATE",
"EXPLAIN", "ANALYZE",
"REPLACE", "AUTOINCREMENT", "ROWID", "VACUUM",
"ILIKE", "LATERAL",
"CONNECT", "START", "PRIOR",
"NOCYCLE",
};
/// <summary>
/// 格式化SQL文本(入口)
/// </summary>
public static string Format(string sql)
{
if (string.IsNullOrWhiteSpace(sql))
return sql;
// 超长文本保护
if (sql.Length > MaxInputLength)
throw new InvalidOperationException(
$"输入文本过长({sql.Length}字符), 最大支持{MaxInputLength}字符。请分段处理。");
var tokens = Tokenize(sql);
if (tokens.Count > MaxTokenCount)
throw new InvalidOperationException(
$"Token数量({tokens.Count})超过上限({MaxTokenCount}),请分段处理。");
tokens = CombineCompounds(tokens);
var result = FormatTokens(tokens);
return result.ToString();
}
// ==================== Token 类型 ====================
private enum TokenType
{
Whitespace,
Keyword,
Identifier,
BracketedIdentifier,
QuotedIdentifier,
BacktickIdentifier,
StringLiteral,
Number,
Operator,
Comma,
OpenParen,
CloseParen,
Semicolon,
Dot,
Comment,
Unknown,
}
private struct Token
{
public TokenType Type;
public string Value;
public int Position;
public Token(TokenType type, string value, int position)
{
Type = type;
Value = value;
Position = position;
}
}
// ==================== Phase 1: Tokenize ====================
/// <summary>
/// 将SQL文本拆分为Token列表, 带完整数组边界保护
/// </summary>
private static List<Token> Tokenize(string sql)
{
var tokens = new List<Token>();
int len = sql.Length;
if (len == 0) return tokens;
int i = 0;
while (i < len)
{
// Token 数量上限检查(提前中断,避免 List 扩容触发数组越界)
if (tokens.Count >= MaxTokenCount)
throw new InvalidOperationException(
$"Token数量超过上限({MaxTokenCount}),请分段处理。当前已解析到位置{i}。");
// 防御: 确保索引不越界
if (i >= len) break;
char c = sql[i];
// 空白字符
if (char.IsWhiteSpace(c))
{
int start = i;
while (i < len && char.IsWhiteSpace(sql[i]))
i++;
tokens.Add(new Token(TokenType.Whitespace, SubstringSafe(sql, start, i - start), start));
continue;
}
// 行注释 --
if (c == '-' && SafeCharAt(sql, i + 1, len) == '-')
{
int start = i;
while (i < len && sql[i] != '\n' && sql[i] != '\r')
i++;
tokens.Add(new Token(TokenType.Comment, SubstringSafe(sql, start, i - start), start));
continue;
}
// 块注释 /* */
if (c == '/' && SafeCharAt(sql, i + 1, len) == '*')
{
int start = i;
i += 2;
while (i + 1 < len && !(sql[i] == '*' && sql[i + 1] == '/'))
i++;
if (i + 1 < len) i += 2; // 跳过结束符 */
tokens.Add(new Token(TokenType.Comment, SubstringSafe(sql, start, i - start), start));
continue;
}
// 字符串字面量 'text'
if (c == '\'')
{
int start = i;
i++;
while (i < len)
{
if (sql[i] == '\'')
{
if (SafeCharAt(sql, i + 1, len) == '\'')
{
i += 2; // 转义的单引号
}
else
{
i++;
break;
}
}
else
{
i++;
}
}
tokens.Add(new Token(TokenType.StringLiteral, SubstringSafe(sql, start, i - start), start));
continue;
}
// 括号标识符 [name]
if (c == '[')
{
int start = i;
i++;
while (i < len && sql[i] != ']')
i++;
if (i < len) i++; // skip ]
tokens.Add(new Token(TokenType.BracketedIdentifier, SubstringSafe(sql, start, i - start), start));
continue;
}
// 反引号标识符 `name` (MySQL)
if (c == '`')
{
int start = i;
i++;
while (i < len && sql[i] != '`')
i++;
if (i < len) i++;
tokens.Add(new Token(TokenType.BacktickIdentifier, SubstringSafe(sql, start, i - start), start));
continue;
}
// 双引号标识符 "name" (PostgreSQL/Oracle)
if (c == '"')
{
int start = i;
i++;
while (i < len)
{
if (sql[i] == '"')
{
if (SafeCharAt(sql, i + 1, len) == '"')
{
i += 2;
}
else
{
i++;
break;
}
}
else
{
i++;
}
}
tokens.Add(new Token(TokenType.QuotedIdentifier, SubstringSafe(sql, start, i - start), start));
continue;
}
// 数字
if (char.IsDigit(c))
{
int start = i;
while (i < len && (char.IsDigit(sql[i]) || sql[i] == '.'))
i++;
tokens.Add(new Token(TokenType.Number, SubstringSafe(sql, start, i - start), start));
continue;
}
// 单字符符号
if (c == ',') { tokens.Add(new Token(TokenType.Comma, ",", i)); i++; continue; }
if (c == ';') { tokens.Add(new Token(TokenType.Semicolon, ";", i)); i++; continue; }
if (c == '.') { tokens.Add(new Token(TokenType.Dot, ".", i)); i++; continue; }
if (c == '(') { tokens.Add(new Token(TokenType.OpenParen, "(", i)); i++; continue; }
if (c == ')') { tokens.Add(new Token(TokenType.CloseParen, ")", i)); i++; continue; }
// 运算符 (仅匹配已知运算符字符)
if (IsOperatorChar(c))
{
int start = i;
if (i + 1 < len && IsSecondOpChar(sql[i + 1]) && IsCompoundOpFirst(c))
{
i += 2;
}
else
{
i++;
}
tokens.Add(new Token(TokenType.Operator, SubstringSafe(sql, start, i - start), start));
continue;
}
// 常规标识符 (含 @ # : 前缀, 用于变量/参数/命名参数等)
if (char.IsLetter(c) || c == '_' || c == '@' || c == '#' || c == ':')
{
int start = i;
// 先消费前缀字符 @ # : 避免死循环
if (c == '@' || c == '#' || c == ':')
i++;
while (i < len && (char.IsLetterOrDigit(sql[i]) || sql[i] == '_' || sql[i] == '$'))
i++;
string word = SubstringSafe(sql, start, i - start);
if (AllKeywords.Contains(word))
tokens.Add(new Token(TokenType.Keyword, word, start));
else
tokens.Add(new Token(TokenType.Identifier, word, start));
continue;
}
// 其他字符(原样保留)
tokens.Add(new Token(TokenType.Unknown, c.ToString(), i));
i++;
}
return tokens;
}
// ---- 边界安全辅助方法 ----
/// <summary>安全获取指定位置字符, 越界返回 '\0'</summary>
private static char SafeCharAt(string s, int index, int len)
{
return (index >= 0 && index < len) ? s[index] : '\0';
}
/// <summary>安全截取子串, 防止越界</summary>
private static string SubstringSafe(string s, int start, int length)
{
if (start < 0) start = 0;
if (start >= s.Length) return string.Empty;
int maxLen = s.Length - start;
if (length < 0) length = 0;
if (length > maxLen) length = maxLen;
return s.Substring(start, length);
}
private static bool IsOperatorChar(char c)
{
return c == '=' || c == '<' || c == '>' || c == '!' || c == '+'
|| c == '-' || c == '*' || c == '/' || c == '%' || c == '&'
|| c == '|' || c == '^' || c == '~';
}
private static bool IsSecondOpChar(char c)
{
return c == '=' || c == '>';
}
private static bool IsCompoundOpFirst(char c)
{
return c == '<' || c == '>' || c == '!' || c == '+'
|| c == '-' || c == '*' || c == '/';
}
// ==================== Phase 2: 合并复合关键字 ====================
/// <summary>
/// 将相邻的组成关键字合并为复合关键字 Token。
/// 自动跳过中间的空白 Token,确保 "INNER ␣ JOIN" → "INNER JOIN"。
/// 例: LEFT + JOIN → "LEFT JOIN", GROUP + BY → "GROUP BY"
/// 支持三词合并: LEFT + OUTER + JOIN → "LEFT OUTER JOIN"
/// </summary>
private static List<Token> CombineCompounds(List<Token> tokens)
{
if (tokens.Count < 2) return tokens;
var result = new List<Token>(tokens.Count);
int maxIdx = tokens.Count;
for (int i = 0; i < maxIdx; i++)
{
if (i >= maxIdx) break;
Token curr = tokens[i];
// 非关键字直接保留
if (curr.Type != TokenType.Keyword)
{
result.Add(curr);
continue;
}
// 找到下一个关键字索引(跳过空白)
int nextKw = NextKeywordIdxSkipWs(tokens, i + 1);
if (nextKw < 0)
{
result.Add(curr);
continue;
}
string nextVal = tokens[nextKw].Value;
// 尝试三词合并: LEFT + OUTER + JOIN
if (CompoundPairs.TryGetValue(curr.Value, out var set1)
&& set1.Contains(nextVal))
{
int nextNextKw = NextKeywordIdxSkipWs(tokens, nextKw + 1);
if (nextNextKw >= 0
&& IsKeywordMatch(tokens, nextNextKw, "JOIN")
&& CompoundPairs.TryGetValue(nextVal, out var innerSet)
&& innerSet.Contains("JOIN"))
{
string combined = string.Join(" ",
curr.Value.ToUpperInvariant(),
nextVal.ToUpperInvariant(),
tokens[nextNextKw].Value.ToUpperInvariant());
result.Add(new Token(TokenType.Keyword, combined, curr.Position));
// 将中间被跳过的空白 Token 也补入(保持位置信息)
AppendWsBetween(tokens, i + 1, nextNextKw, result);
i = nextNextKw;
continue;
}
}
// 尝试两词合并
if (CompoundPairs.TryGetValue(curr.Value, out var set2)
&& set2.Contains(nextVal))
{
string combined = curr.Value.ToUpperInvariant() + " "
+ nextVal.ToUpperInvariant();
result.Add(new Token(TokenType.Keyword, combined, curr.Position));
AppendWsBetween(tokens, i + 1, nextKw, result);
i = nextKw;
continue;
}
result.Add(curr);
}
return result;
}
/// <summary>
/// 从 startIdx 开始查找下一个关键字 token(跳过空白,遇非空白非关键字则停止)
/// </summary>
private static int NextKeywordIdxSkipWs(List<Token> tokens, int startIdx)
{
for (int i = startIdx; i < tokens.Count; i++)
{
if (tokens[i].Type == TokenType.Keyword)
return i;
if (tokens[i].Type != TokenType.Whitespace)
return -1; // 遇到实际内容(非空白),停止
}
return -1;
}
/// <summary>
/// 将范围 (from, to] 内的空白 Token 追加到结果列表
/// </summary>
private static void AppendWsBetween(List<Token> tokens, int from, int to, List<Token> result)
{
for (int i = from; i <= to; i++)
{
if (tokens[i].Type == TokenType.Whitespace)
result.Add(tokens[i]);
}
}
/// <summary>检查指定位置 Token 是否匹配给定关键字(忽略大小写)</summary>
private static bool IsKeywordMatch(List<Token> tokens, int index, string expected)
{
if (index < 0 || index >= tokens.Count) return false;
var t = tokens[index];
return t.Type == TokenType.Keyword
&& string.Equals(t.Value, expected, StringComparison.OrdinalIgnoreCase);
}
// ==================== Phase 3: 格式化 ====================
/// <summary>当前 SQL 子句上下文</summary>
private enum ClauseCtx
{
None,
SelectList, // SELECT 字段列表
FromClause, // FROM 子句
JoinClause, // JOIN 之后, ON 之前
OnCondition, // ON 条件
WhereClause, // WHERE 条件
GroupBy, // GROUP BY
OrderBy, // ORDER BY
SetClause, // SET
ValuesClause, // VALUES
CaseWhen, // CASE WHEN
}
/// <summary>
/// 将 Token 列表格式化为 db_helper 风格 SQL
/// </summary>
private static StringBuilder FormatTokens(List<Token> tokens)
{
var sb = new StringBuilder();
int indent = 0;
bool newline = true;
TokenType prevType = TokenType.Whitespace;
string prevValue = "";
ClauseCtx ctx = ClauseCtx.None;
int maxIdx = tokens.Count;
for (int ti = 0; ti < maxIdx; ti++)
{
if (ti >= maxIdx) break;
Token token = tokens[ti];
switch (token.Type)
{
case TokenType.Whitespace:
continue;
case TokenType.Keyword:
{
string kw = token.Value.ToUpperInvariant();
// 点号后的关键字视为普通标识符,不触发子句逻辑
if (prevValue.Equals(".", StringComparison.Ordinal))
{
EmitInline(sb, ref newline, indent, token.Value);
prevType = TokenType.Identifier;
prevValue = token.Value;
continue;
}
// ---- AND / OR: WHERE 中换行缩进, ON 中同行 ----
if (kw == "AND" || kw == "OR")
{
if (ctx == ClauseCtx.WhereClause)
{
// db_helper: WHERE 首个条件同行, AND/OR 换行缩进
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, indent + IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
if (ctx == ClauseCtx.OnCondition)
{
// db_helper: JOIN ON 中的短 AND/OR 同行
if (sb.Length > 0 && sb[sb.Length - 1] != ' ')
sb.Append(' ');
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// 其他位置: 换行缩进
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, indent + IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- ON: 跟在 JOIN 同行 ----
if (kw == "ON")
{
// db_helper: JOIN + ON 同行
sb.Append(' ');
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = ClauseCtx.OnCondition;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- WHEN / ELSE / THEN / END: CASE 子句 ----
if (kw == "WHEN" || kw == "ELSE")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, indent + IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = ClauseCtx.CaseWhen;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
if (kw == "THEN")
{
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
if (kw == "END")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, indent);
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- JOIN 系列关键字(已经合并为复合关键字) ----
if (IsJoinKeyword(kw))
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = ClauseCtx.JoinClause;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- SET / VALUES ----
if (kw == "SET" || kw == "VALUES")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = kw == "SET" ? ClauseCtx.SetClause : ClauseCtx.ValuesClause;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- GROUP BY / ORDER BY / HAVING: 同行紧凑 ----
if (kw == "GROUP BY" || kw == "ORDER BY" || kw == "HAVING")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = kw == "GROUP BY" ? ClauseCtx.GroupBy : ClauseCtx.OrderBy;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- WHERE ----
if (kw == "WHERE")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = ClauseCtx.WhereClause;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- FROM ----
if (kw == "FROM")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
ctx = ClauseCtx.FromClause;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- SELECT / INSERT / UPDATE / DELETE / CREATE / ALTER / DROP / WITH / MERGE / TRUNCATE / DECLARE / BEGIN / GO ----
if (IsTopLevelKeyword(kw))
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, 0);
sb.Append(kw);
sb.Append(' ');
newline = false;
indent = IndentSize;
ctx = kw == "SELECT" ? ClauseCtx.SelectList : ClauseCtx.None;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- UNION / INTERSECT / EXCEPT / MINUS: 集合操作, 前后空行 ----
if (kw == "UNION" || kw == "UNION ALL" || kw == "INTERSECT" || kw == "EXCEPT" || kw == "MINUS")
{
if (!newline) { sb.AppendLine(); newline = true; }
sb.AppendLine();
newline = true;
WriteIndent(sb, ref newline, 0);
sb.Append(kw);
sb.AppendLine();
newline = true;
indent = IndentSize;
ctx = ClauseCtx.None;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- LIMIT / OFFSET / FETCH NEXT ----
if (kw == "LIMIT" || kw == "OFFSET" || kw == "FETCH NEXT")
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, IndentSize);
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- 其他 ClauseStarters 中的关键字 ----
if (ClauseStarters.Contains(kw))
{
if (!newline) { sb.AppendLine(); newline = true; }
WriteIndent(sb, ref newline, indent);
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
continue;
}
// ---- 非子句关键字(AS / IN / NOT / NULL / BY 等): 内联 ----
if (NeedsLeadingSpace(prevType))
sb.Append(' ');
sb.Append(kw);
sb.Append(' ');
newline = false;
prevType = TokenType.Keyword;
prevValue = kw;
break;
}
case TokenType.Comma:
{
sb.Append(',');
// 逗号后换行缩进
sb.AppendLine();
newline = true;
prevType = TokenType.Comma;
prevValue = ",";
break;
}
case TokenType.OpenParen:
sb.Append('(');
indent = Math.Min(indent + IndentSize, MaxIndent);
newline = false;
prevType = TokenType.OpenParen;
prevValue = "(";
break;
case TokenType.CloseParen:
indent = Math.Max(0, indent - IndentSize);
if (newline) { WriteIndent(sb, ref newline, indent); }
sb.Append(')');
newline = false;
prevType = TokenType.CloseParen;
prevValue = ")";
break;
case TokenType.Semicolon:
sb.Append(';');
sb.AppendLine();
newline = true;
indent = 0;
ctx = ClauseCtx.None;
prevType = TokenType.Semicolon;
prevValue = ";";
break;
case TokenType.Dot:
sb.Append('.');
newline = false;
prevType = TokenType.Dot;
prevValue = ".";
break;
case TokenType.StringLiteral:
case TokenType.BracketedIdentifier:
case TokenType.QuotedIdentifier:
case TokenType.BacktickIdentifier:
case TokenType.Number:
case TokenType.Identifier:
if (newline) { WriteIndent(sb, ref newline, indent); }
// 仅由当前 token 根据前驱类型决定是否补前导空格
// Keyword/Operator 已在自身末尾追加空格,故排除,避免双空格
if (NeedsLeadingSpace(prevType))
sb.Append(' ');
sb.Append(token.Value);
newline = false;
prevType = token.Type;
prevValue = token.Value;
break;
case TokenType.Operator:
if (newline) { WriteIndent(sb, ref newline, indent); }
sb.Append(' ');
sb.Append(token.Value);
sb.Append(' ');
newline = false;
prevType = TokenType.Operator;
prevValue = token.Value;
break;
case TokenType.Comment:
if (!newline) sb.Append(' ');
sb.Append(token.Value);
newline = false;
prevType = TokenType.Comment;
prevValue = token.Value;
break;
default:
if (newline) { WriteIndent(sb, ref newline, indent); }
sb.Append(token.Value);
newline = false;
prevType = token.Type;
prevValue = token.Value;
break;
}
}
return new StringBuilder(sb.ToString().Trim());
}
// ---- 格式化辅助 ----
private static void WriteIndent(StringBuilder sb, ref bool newline, int spaces)
{
if (!newline) return;
int safeSpaces = spaces;
if (safeSpaces < 0) safeSpaces = 0;
if (safeSpaces > MaxIndent) safeSpaces = MaxIndent;
sb.Append(new string(' ', safeSpaces));
newline = false;
}
private static void EmitInline(StringBuilder sb, ref bool newline, int indent, string value)
{
if (newline) WriteIndent(sb, ref newline, indent);
sb.Append(value);
newline = false;
}
private static bool IsJoinKeyword(string kw)
{
return kw.EndsWith(" JOIN", StringComparison.OrdinalIgnoreCase)
|| kw.Equals("JOIN", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// 判断前一个 token 类型是否需要与当前词之间补空格。
/// 排除 Keyword/Operator/Semicolon/Dot — 它们已在自身末尾追加了空格。
/// OpenParen 不需要后置空格 (如 "CAST(" 之后不应有空格)。
/// </summary>
private static bool NeedsLeadingSpace(TokenType prevType)
{
return prevType == TokenType.Identifier
|| prevType == TokenType.Number
|| prevType == TokenType.StringLiteral
|| prevType == TokenType.BracketedIdentifier
|| prevType == TokenType.QuotedIdentifier
|| prevType == TokenType.BacktickIdentifier
|| prevType == TokenType.CloseParen
|| prevType == TokenType.Comment;
}
private static bool IsTopLevelKeyword(string kw)
{
return kw == "SELECT" || kw == "INSERT" || kw == "INSERT INTO"
|| kw == "UPDATE" || kw == "DELETE" || kw == "DELETE FROM"
|| kw == "CREATE" || kw == "ALTER" || kw == "DROP"
|| kw == "WITH" || kw == "MERGE" || kw == "TRUNCATE"
|| kw == "DECLARE" || kw == "BEGIN" || kw == "GO"
|| kw == "RETURN" || kw == "IF";
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
\ No newline at end of file
...@@ -45,6 +45,7 @@ namespace Tools ...@@ -45,6 +45,7 @@ namespace Tools
this.CreateModule(Tools_uni, typeof(UI.wTaskBoard), "任务看板", "tr:任务看板的简易化工具,免除翻页困扰"), this.CreateModule(Tools_uni, typeof(UI.wTaskBoard), "任务看板", "tr:任务看板的简易化工具,免除翻页困扰"),
this.CreateModule(Tools_uni, typeof(UI.wLoginPwd), "密码解析", "tr:用于登陆密码的解密加密"), this.CreateModule(Tools_uni, typeof(UI.wLoginPwd), "密码解析", "tr:用于登陆密码的解密加密"),
this.CreateModule(Tools_uni, typeof(WindowsSearch.FrmSearch), "文件搜索", "lxj:用于搜索文件内容"), this.CreateModule(Tools_uni, typeof(WindowsSearch.FrmSearch), "文件搜索", "lxj:用于搜索文件内容"),
this.CreateModule(Tools_uni, typeof(UI.wTextFormatter), "文本格式化", "tr:SQL和JSON文本格式化工具"),
//------------------------------- //-------------------------------
this.CreateModule(Tools_imp, typeof(UpdateExecuter.frmMain), "更新执行工具", "lxj:用于客户更新和执行SQL脚本"), this.CreateModule(Tools_imp, typeof(UpdateExecuter.frmMain), "更新执行工具", "lxj:用于客户更新和执行SQL脚本"),
this.CreateModule(Tools_imp, typeof(UI.wEmptyTable), "清空表", "tr:输入主表、自动清空关联表的工具"), this.CreateModule(Tools_imp, typeof(UI.wEmptyTable), "清空表", "tr:输入主表、自动清空关联表的工具"),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment