作者: 魔法软糖

日期: 2020-02-27

引言

*************************************

.ini 文件是Initialization File的缩写,即配置文件 。是windows的系统配置文件所采用的存储格式。

它具有方便易用的特点,和注册表的键值有着类似的功能,各类应用程序也经常使用INI保存各种配置和选项。

在简单只需要读取的场合,调用WINDOWS API就行,但在复杂的需要可视化编辑时,就需要建立自己的处理类了。

如何实现自己的INI类

首先我们需要了解

◇  INI的格式

◇  典型INI文件

;项目注释
[.ShellClassInfo]
InfoTip=有图标的文件夹
;图标资源
IconResource="C:\Windows\system32\SHELL32.dll",4
#文件夹视图
[ViewState]
Mode=
Vid=
FolderType=General
#尾部注释

一个典型INI文件由节、注释和节下面的项组成,而项为键=值的形式。

INI文件的注释符号有两种,规范为;分号,实际有些地方用#井号。

◇  保留注释

为了在修改INI文件时不丢失任何信息,所以需要保存INI文件中所有有效元素、包括注释甚至是无效行。

为了实现这个目的,将所有注释和无效行都归属于它之后的有效元素。

以上面的desktop.ini为例,

  • 第一行 ;项目注释归属于[.ShellClassInfo]节
  • 第四行;图标资源归属于IconResource=项
  • #文件夹视图归属于[ViewState]节
  • 最后的#尾部注释归属于整个INI文档的EndText

◇  INIItem类

表示INI文件中节点下面的项,它拥有三个属性:名称Name、值Value和注释Comment

     /// <summary>
/// 拥有名称、值和注释
/// </summary>
public class INIItem {
/// <summary>
/// 实例化INIItem。指定名称、值和注释。
/// </summary>
/// <param name="vName"></param>
/// <param name="vValue"></param>
/// <param name="vComment"></param>
public INIItem(string vName, string vValue, string vComment = "") {
Name = vName;
Value = vValue;
Comment = vComment;
}
/// <summary>
/// 项名称。例如 Color = 202,104,0 中的 Color
/// </summary>
public string Name { get; set; }
/// <summary>
/// 值内容。例如 Color = 202,104,0 中的 202,104,0
/// </summary>
public string Value { get; set; }
/// <summary>
/// 位于前面的所有注释行。一般以 ; 开头
/// </summary>
public string Comment { get; set; }
/// <summary>
/// 返回 INIItem 的文本形式。〈<see cref="string"/>〉
/// <para>Name=Value</para>
/// </summary>
/// <returns>〈string〉返回 INIItem 的文本形式。</returns>
public override string ToString() {
return Name + INI.U等号 + Value;
}
}

◇  ININode类

表示INI文件中的一个节点,它拥有项列表List{Of INIItem}、名称Name和注释Comment。

     /// <summary>
/// 表示INI文件的一个节点,它拥有一个项目列表,还拥有名称和注释
/// <para></para>
/// </summary>
public class ININode {
/// <summary>
/// 实例化ININode。指定初始的名称和注释。
/// </summary>
/// <param name="vName"></param>
/// <param name="vComment"></param>
public ININode(string vName, string vComment) { Name = vName; Comment = vComment; Items = new List<INIItem>(); }
/// <summary>
/// 节点名称。例如 [Config]
/// </summary>
public string Name { get; set; }
/// <summary>
/// 位于前面的所有注释行。一般以 ; 开头
/// </summary>
public string Comment { get; set; }
/// <summary>
/// 含有的项列表
/// </summary>
public List<INIItem> Items { get; set; }
/// <summary>
/// 向本节点添加新项。
/// </summary>
/// <param name="vName"></param>
/// <param name="vValue"></param>
/// <param name="vComment"></param>
/// <returns></returns>
public INIItem New(string vName, string vValue, string vComment = "") {
var k = new INIItem(vName, vValue, vComment);
Items.Add(k);
return k;
}
/// <summary>
/// 返回 ININode的文本形式。〈<see cref="string"/>〉
/// <para>[Name]</para>
/// </summary>
/// <returns>〈string〉返回 ININode 的文本形式。</returns>
public override string ToString() {
return INI.U左括号 + Name + INI.U右括号;
}
}

◇  INI类

它表示整个INI文件的全部内,拥有List{Of ININode}、EndText、FileName、StartLine等属性

     /// <summary>
/// 表示INI文件。拥有读取和写入文件的方法。
/// <para>储存在 <see cref="List{ININode}"/>&lt;<see cref="ININode"/>&gt;</para>
/// </summary>
public class INI {
/// <summary>
/// 实例化INI文件。
/// </summary>
public INI() { } #region "↓全局常量"
/// <summary>注释的标准符号</summary>
public static string U注释 = ";";
/// <summary>注释的标准符号2</summary>
public static string U注释2 = "#";
/// <summary>节左括号的标准符号</summary>
public static string U左括号 = "[";
/// <summary>节右括号的标准符号</summary>
public static string U右括号 = "]";
/// <summary>连接项和值的标准符号</summary>
public static string U等号 = "=";
/// <summary>读取或写入时忽略无意义的备注行(不包括注释)。</summary>
public static bool 忽略备注 = false;
/// <summary>读取的上个文件的有效行数(不包括注释)。</summary>
public static int 上次读取的有效行数 = ;
#endregion /// <summary>
/// 所有节点
/// <para>每个节点含有项、值和注释,当项名称为空字符串时,整条语句视为注释</para>
/// </summary>
public List<ININode> Nodes { get; set; } = new List<ININode>();
/// <summary>
/// 附加在INI文件后无意义的文本
/// </summary>
public string EndText { get; set; } = "";
/// <summary>
/// 附加在INI文件第一行的作者信息等文本
/// <para>其中的换行符将被替换为两个空格</para>
/// </summary>
public string StartLine { get; set; } = "";
/// <summary>
/// 读取INI时获得的FileName。
/// <para>写入文档时可以使用这个名字,也可以不使用这个名字。</para>
/// </summary>
public string FileName { get; set; } = "";
/// <summary>
/// 向本INI文件添加新节点。
/// </summary>
/// <param name="vName"></param>
/// <param name="vComment"></param>
/// <returns></returns>
public ININode New(string vName, string vComment = "") {
var k = new ININode(vName, vComment);
Nodes.Add(k);
return k;
}
}

如何写入INI文件

  1. 首先遍历每个节点,写入节点的注释节点名称(套个括号)
  2. 然后遍历每个节点下面的,写入项的注释项的名称=值
  3. 写入尾部注释

以下是写入代码

         #region "写入文件"

         /// <summary>将文档写入指定路径
/// </summary>
/// <param name="path">指定路径</param>
public bool 写入文档(string path, Encoding encoding = null) {
try {
if (encoding == null) { encoding = Encoding.Default; }
using (StreamWriter SW = new StreamWriter(path)) {
SW.Write(ToString());
}
} catch (Exception) {
return false;
}
return true;
}
/// <summary>
/// 将INI文档转化为文本格式,会生成整个文档。
/// <para>注意:较大的文档可能会耗费大量时间</para>
/// </summary>
/// <returns></returns>
public override string ToString() {
StringBuilder sb = new StringBuilder();
if (StartLine.Length > ) { sb.AppendLine(StartLine.Replace("\r\n", " ")); }
for (int i = ; i < Nodes.Count; i++) {
var node = Nodes[i];
if (忽略备注 == false) { sb.Append(node.Comment); }
sb.AppendLine(node.ToString());
for (int j = ; j < node.Items.Count; j++) {
var item = node.Items[j];
if (忽略备注 == false) { sb.Append(item.Comment); }
sb.AppendLine(item.ToString());
}
}
if (EndText.Length > ) { sb.AppendLine(EndText); }
return sb.ToString();
} #endregion

如何读取INI文件

读取通常比写入复杂。软糖的代码也是逐行检查,多次调试才完成。

流程如下:

  1. 首先定义一些局部变量来记录当前分析的节、项、已经累积的备注、是否为有效行
  2. 逐行读取,首先判断是否开头为;#,如果是,添加到备注,加回车符,设为有效行。
  3. 判断开头是否为[,如果是则作为节来读取,进一步分析,如果[A]这种形式,设置当前节,设为有效行,如果[B缺少反括号,进行下一步流程,尚无法判断是[B=K这种项还是纯粹无意义的无效行。
  4. 判断是否含有=,如果是则作为项来读取
  5. 如果未标记为有效行,通通加入备注
  6. 如果读完全文,备注不为空,则加入到INI.EndText中作为结尾注释。

代码

 #region "读取文件"
/// <summary>
/// 从指定路径和字符编码的文件中读取文档内容,以此生成本文档。
/// </summary>
/// <param name="路径">完整的路径字符串</param>
/// <param name="encoding">编码格式:默认自动识别。(对于无bom可能识别错误)</param>
public bool 读取文档(string 路径, Encoding encoding = null) {
if (File.Exists(路径) == false) { return false; }
try {
if (encoding == null) { encoding = TXT.GetFileEncodeType(路径); }
using (StreamReader SR = new StreamReader(路径, encoding)) {
bool 返回结果 = 读取文档(new StringReader(SR.ReadToEnd()));
SR.Close();
return 返回结果;
}
} catch (Exception) {
return false;
}
} /// <summary>
/// 从 <see cref="StringReader"/> 中读取文档内容,以此生成本文档。
/// </summary>
/// <param name="MyStringReader">StringReader,可以由string或StreamReader.ReadToEnd()来生成。</param>
/// <returns>〈bool〉返回是否读取成功。</returns>
public bool 读取文档(StringReader MyStringReader) {
/// <summary>正在分析的节</summary>
ININode 当前节 = null;
/// <summary>正在分析的项</summary>
INIItem 当前项 = null;
/// <summary>正在分析的节名</summary>
string 当前节名 = null;
/// <summary>正在分析的项名</summary>
string 当前项名 = null;
/// <summary>累计读取的属性行的计数</summary>
int 计数 = ;
/// <summary>该行是合法有效的行,还是无法识别的行。(无法识别作为备注处理)</summary>
bool 有效行 = false;
/// <summary>该行去掉空格和Tab符的文本长度</summary>
int 有效文本长度;
/// <summary>每个实体前的注释</summary>
string 备注 = "";
// * 循环读取每行内容 *
while (true) {
string 行文本 = MyStringReader.ReadLine();
if (行文本 == null) { if (备注.Length > ) { EndText = 备注; } 上次读取的有效行数 = 计数; break; } else {
string 行; 有效行 = false;
// * 获取 去掉空格和Tab符的文本 *
行 = 行文本.Trim(' ', '\t');
// * 获取 去掉空格和Tab符的文本的长度 *
有效文本长度 = 行.Length;
// * 检测注释符 *
if (行文本.Contains(U注释)) {
int 注释位置 = 行文本.IndexOf(U注释);
行 = 行文本.Substring(, 注释位置);
int 注释开始位置 = 注释位置 + U注释.Length - ;
int 注释长度 = 行文本.Length - 注释开始位置;
if (注释长度 > ) {
if (备注.Length > ) { 备注 += "\r\n"; }
备注 += 行文本.Substring(注释开始位置, 注释长度);
}
有效行 = true;
}
if (行文本.Contains(U注释2)) {
int 注释位置 = 行文本.IndexOf(U注释2);
行 = 行文本.Substring(, 注释位置);
int 注释开始位置 = 注释位置 + U注释2.Length - ;
int 注释长度 = 行文本.Length - 注释开始位置;
if (注释长度 > ) {
if (备注.Length > ) { 备注 += "\r\n"; }
备注 += 行文本.Substring(注释开始位置, 注释长度);
}
有效行 = true;
}
// * 检查开头字符 *
if (行.Length >= ) {
//[类型定义]====首字符:U节首[
if (行[] == U左括号[]) {
int 右括号位置 = 行.IndexOf(U右括号[], );
if (右括号位置 > ) {
当前节名 = 行.Substring(, 右括号位置 - );
当前节 = New(当前节名, 备注);
备注 = "";
计数 += ;
有效行 = true;
}
}
//项定义====含有等号的行
// -> 获取赋值符号位置
int 赋值符位置 = 行.IndexOf(U等号, );
if (赋值符位置 > ) {
// -> 获得名称和值,并新建项
当前项名 = 行.Substring(, 赋值符位置).Trim(' ', '\t');
string 值 = 行.Substring(赋值符位置 + , 行.Length - 赋值符位置 - ).Trim(' ', '\t');
if (当前节 != null) {
当前项 = 当前节.New(当前项名, 值, 备注);
备注 = "";
计数 += ;
有效行 = true;
}
}
}
// * 无效行作为备注处理 *
if (有效行 == false) {
if (忽略备注 == false) {
if (行文本.Length == ) { 备注 += "\r\n"; } else { 备注 += 行文本 + "\r\n"; }
}
}
}
}
return true;
} #endregion

◇  编码问题

 /// <summary>
/// 通过文件的头部开始的两个字节来区分一个文件属于哪种编码。
/// 如果文件长度不足2字节,则返回null
/// 当FF FE时,是Unicode;
/// 当FE FF时,是BigEndianUnicode;
/// 当EF BB时,是UTF-8;
/// 当它不为这些时,则是ANSI编码。
/// </summary>
public static Encoding GetFileEncodeType(string filename) {
FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
Byte[] buffer = br.ReadBytes();
if (buffer.Length < ) { return null; }
if (buffer[] >= 0xEF) {
if (buffer[] == 0xEF && buffer[] == 0xBB) {
return Encoding.UTF8;
} else if (buffer[] == 0xFE && buffer[] == 0xFF) {
return Encoding.BigEndianUnicode;
} else if (buffer[] == 0xFF && buffer[] == 0xFE) {
return Encoding.Unicode;
} else {
return Encoding.Default;
}
} else {
return Encoding.Default;
}
}

窗体读取INI演示

 ◇  演示效果

◇ INIListView类

用一个辅助类将INI文件内容显示到ListView来展现效果。

给每个节点添加一个Group组,将节点本身和下辖的项都放进组。

当鼠标选中某项时,判断该item的Key和Group即可知道它属于哪个节点,名称是什么。

     public class INIListView {
public ListView 视图;
public Color 节颜色 = Color.FromArgb(, , );
public Color 节底色 = Color.FromArgb(, , );
public void 绑定控件(ListView ListView) {
视图 = ListView;
初始化();
}
public void 载入数据(INI ini) {
初始化组(ini);
初始化数据(ini);
} private void 初始化() {
视图.View = View.Tile;
视图.ShowGroups = true;
初始化列();
} private void 初始化列() {
视图.Columns.Clear();
视图.Columns.Add("A", "名称", );
视图.Columns.Add("B", "值", );
视图.Columns.Add("C", "注释", );
}
private void 初始化组(INI ini) {
if (ini == null) { return; }
for (int i = ; i < ini.Nodes.Count; i++) {
string nodeName = ini.Nodes[i].Name;
int cc = ini.Nodes[i].Items.Count;
string nodeTitle = string.Format("{0} ({1})", nodeName, cc);
视图.Groups.Add(nodeName, nodeTitle);
}
} private void 初始化数据(INI ini) {
视图.Items.Clear(); if (ini == null) { return; }
for (int i = ; i < ini.Nodes.Count; i++) {
string nodeName = ini.Nodes[i].Name;
var nodeitem = 视图.Items.Add(nodeName, "["+nodeName+"]",);
nodeitem.ForeColor = 节颜色;
nodeitem.BackColor = 节底色; nodeitem.Group = 视图.Groups[nodeName]; for (int j = ; j < ini.Nodes[i].Items.Count; j++) {
var iniitem = ini.Nodes[i].Items[j];
string name = iniitem.Name;
string value = iniitem.Value;
string comment = iniitem.Comment;
var item = 视图.Items.Add(name, name);
item.Group = 视图.Groups[nodeName];
item.SubItems.Add(value);
item.SubItems.Add(comment);
}
}
} }

窗体上拖一个ListView(数据视图)和OpenFileDialog(openINIFileDialog)、和Button(按钮_读取文件)

     public partial class 编辑窗体 : Form {
INIListView INIListView = new INIListView();
INI 当前文档; public 编辑窗体() {
InitializeComponent();
} private void 编辑窗体_Load(object sender, EventArgs e) {
Width = ;
Height = ;
初始化数据视图();
openINIFileDialog.InitialDirectory = Environment.CurrentDirectory;
}
private void 初始化数据视图() {
INIListView.绑定控件(数据视图);
} private void 按钮_读取文件_Click(object sender, EventArgs e) {
var result = openINIFileDialog.ShowDialog();
if (result == DialogResult.OK) {
当前文档 = new INI();
var 读取结果 = 当前文档.读取文档(openINIFileDialog.FileName);
INIListView.载入数据(当前文档);
} } private void 视图_1_Click(object sender, EventArgs e) {
数据视图.View = View.Details;
} private void 视图_2_Click(object sender, EventArgs e) {
数据视图.View = View.Tile;
} private void 视图_3_Click(object sender, EventArgs e) {
数据视图.View = View.List;
} private void 视图_4_Click(object sender, EventArgs e) {
数据视图.View = View.SmallIcon;
} private void 视图_5_Click(object sender, EventArgs e) {
数据视图.View = View.LargeIcon;
}
}

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

结语:本文实现了INI文件的构造、读取和写入。

实际上通过扩展可以实现更强大的数据格式。

C# 如何实现完整的INI文件读写类的更多相关文章

  1. [IO] C# INI文件读写类与源码下载 (转载)

    /// <summary> /// 类说明:INI文件读写类. /// 编 码 人:苏飞 /// 联系方式:361983679 /// 更新网站:[url]http://www.sufei ...

  2. C# INI文件读写类

    public class Ini { // 声明INI文件的写操作函数 WritePrivateProfileString() [System.Runtime.InteropServices.DllI ...

  3. INI文件读写类

    public class INIClass { public string inipath; [DllImport("kernel32")] private static exte ...

  4. QSettings配置读写-win注册表操作-ini文件读写

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QSettings配置读写-win注册表操作-ini文件读写     本文地址:http:// ...

  5. C#对INI文件读写

    C#本身没有对INI格式文件的操作类,可以自定义一个IniFile类进行INI文件读写. using System; using System.Collections.Generic; using S ...

  6. Ini文件操作类

    /// <summary> /// Ini文件操作类 /// </summary> public class Ini { // 声明INI文件的写操作函数 WritePriva ...

  7. Ini文件帮助类

    .ini文件是什么 .ini 文件是Initialization File的缩写,就是初始化文件.在Windows系统中,其是配置文件所采用的存储格式(主要是system.ini,win.ini,sy ...

  8. VC++ 实现INI文件读写操作

    转载:https://blog.csdn.net/fan380485838/article/details/73188420 在实际项目开发中,会用ini配置文件,在此总结一下对ini读写操作 一:读 ...

  9. 封装 INI 文件读写函数

    delphi读写ini文件实例 //--两个过程,主要实现:窗体关闭的时候,文件保存界面信息:窗体创建的时候,程序读取文件文件保存的信息. //--首先要uses IniFiles(单元) //--窗 ...

随机推荐

  1. 自建CDN Xnign产品指标

    Xnign-X1 Xnign-X1 性能参数 参考值 L7 HTTP RPS (128并发请求) 250W QPS L7 HTTP CPS (128并发请求) 110W QPS L7 HTTP RPS ...

  2. .net core 3.1 DbFirst mysql

    这是一套完全配置正确的方式 创建项目此步骤省略 打开nuget 搜索 Pomelo.EntityFrameworkCore.MySql 添加完毕该引用之后nuget 搜索 Microsoft.Enti ...

  3. CTF中关于XXE(XML外部实体注入)题目两道

    题目:UNCTF-Do you like xml? 链接:http://112.74.37.15:8008/ hint:weak password (弱密码) 1.观察后下载图片拖进WINHEX发现提 ...

  4. 「 从0到1学习微服务SpringCloud 」07 RabbitMq的基本使用

    系列文章(更新ing): 「 从0到1学习微服务SpringCloud 」01 一起来学呀! 「 从0到1学习微服务SpringCloud 」02 Eureka服务注册与发现 「 从0到1学习微服务S ...

  5. (转) exp1-3://一次有趣的XSS漏洞挖掘分析(3)最终篇

      这真是最后一次了.真的再不逗这个程序员了.和预期一样,勤奋的程序员今天又更新程序了.因为前面写的payload都有一个致命的弱点,就是document.write()会完全破坏DOM结构.而且再“ ...

  6. CSS动效集锦,视觉魔法的碰撞与融合(三)

    本文讲述的原理和相关demo 扇形DIV的使用——实现雷达扫描图 DIV环形布局—实现loading圈 动画的向量合成—实现抛物线动画 无限滚动动画—实现跑马灯效果 perspective和trans ...

  7. scikit-learn基础

    一.scikit-learn基础 sklearn.ensemble模块有两种基于决策树的算法----随机森林和极端随机树

  8. 使用Route Prefix 使用属性路由 精通ASP-NET-MVC-5-弗瑞曼

  9. mysql5.7的基本使用

    mysql的基本使用:最简单的增删改查 (建议用类似记事本的东西写代码,错了容易改) 以下就是这篇文章的代码 一,增和查   CREATE DATABASE one; 新建了一个名为one的数据库 S ...

  10. python下的selenium和chrome driver的安装

    selenium是一款支持多种语言.多种浏览器.多个平台的开源web自动化测试软件,测试人员可用python.java等语言编写自动化脚本,使得浏览器可以完全按照你的指令运行,大大节省了测试人员用鼠标 ...