C#实现简易XML阅读器(含源文件和exe文件)

本文档介绍了一个XML阅读器程序,旨在提供易用的XML文件读写、编辑、格式对齐和高亮显示功能。程序包括查找和替换功能,采用DFA算法提升搜索效率,并支持循环查找。此外,还支持拖拽打开文件和启动参数处理,确保用户能便捷地操作XML文档。
摘要由CSDN通过智能技术生成

功能

本程序的主要目的是便于阅读XML文件。在网上复制一些XML文本时,常常出现格式错误,更有甚者,整个文本就一行,一行有几百个字符。这些奇奇怪怪的格式大大增加了理解难度,本程序将模仿记事本,提供基础的文件读写,编辑功能,并在此基础上增加了格式对齐和高亮功能。

<?xml version="1.0" encoding="utf-8" ?> 
<Class> 
 <Student> 
  <ID>1111</ID> 
  <name>Happy</name> 
 </Student> 
 <Student> 
  <ID>2222</ID> 
  <name>Sad</name> 
 </Student> 
</Class>

界面设计

主界面分为菜单栏和输入框

 文件读写

新建FileIO.cs,引入命名空间System.IO

static class FileIO
{
    public static string ReadFile(string path)
    {
        if (path == null) return null;
        string line;
        string s = "";
        StreamReader streamReader = null;
        try
        {
            streamReader = new StreamReader(path);
            while((line = streamReader.ReadLine()) != null)
            {
                s += line + "\n";
            }
        }
        catch(Exception ex)
        {
            s = null;
        }
        finally
        {
            if (streamReader != null) streamReader.Dispose();
        }
        return s;
    }

    public static bool WriteFile(string path,string content)
    {
        StreamWriter streamWriter = null;
        try
        {
            streamWriter = new StreamWriter(path);
            streamWriter.Write(content);
        }
        catch(Exception ex)
        {
            return false;
        }
        finally
        {
            if (streamWriter != null) streamWriter.Dispose();
        }
        return true;
    }
}

ReadFile函数根据传入的地址,读取文件内容,WriteFile则负责保存文件,返回的布尔值用于判断是否保存成功。有人可能会对WriteFile里的catch产生疑惑,在catch里直接return了,那finally的代码不就无法执行了吗?实际上在执行return之前,会先把要return的数据保存,然后执行finally里的语句,最后再return刚刚保存的参数。例如我在catch里return a,而a=1,即使我在finally里把a赋值成2,那最后返回的仍然是1.

查找和替换

 DFA算法可以极大提高长字符串的查找效率

public bool textHasChanged = true;
public bool wordHasChanged = true;
private char[] word;
private char[] text;
private bool GetWord()
{
    if(main.richTextBox1.Text.Length == 0)
    {
        MessageBox.Show("文本内容为空!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(textBox1.Text.Length == 0)
    {
        MessageBox.Show("查找内容为空!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if (textHasChanged)
    {
        if (checkBox1.Checked)//不区分大小写
        {
            text = main.richTextBox1.Text.ToLower().ToCharArray();
        }
        else
        {
            text = main.richTextBox1.Text.ToCharArray();
        }
        textHasChanged = false;
    }
    if (wordHasChanged)
    {
        if (checkBox1.Checked)//不区分大小写
        {
            word = textBox1.Text.ToLower().ToCharArray();
        }
        else
        {
            word = textBox1.Text.ToCharArray();
        }
        wordHasChanged = false;
    }
    return true;
}

首先定义char数组word和text,分别表示需要查找的内容和全部文本,使用ToCharArray()将string转化成char数组,在实际使用中,常常会连续使用多次查找功能,如果每次查找时都要转换以下,会增大系统开销,因此需要设定布尔变量来表示字符串内容是否被修改,仅当用户手动修改了word或者text内容时才重新转换。

其中“查找”按钮就是简单的统计字符串在文本中出现了多少次,并选中第一次出现的位置,实现较为容易,不展示源码。“上一处”和“下一处”则要根据鼠标光标的位置寻找上一个或下一个出现的位置

private void button2_Click(object sender, EventArgs e)//向下查找
{
    if (!GetWord()) return;
    int i, j;
    bool flag;
    if (checkBox1.Checked)
    {
        flag = main.richTextBox1.SelectedText.ToLower().Equals(textBox1.Text.ToLower());
    }
    else
    {
        flag = main.richTextBox1.SelectedText.Equals(textBox1.Text);
    }
    if (flag)
    {
        i = main.richTextBox1.SelectionStart + main.richTextBox1.SelectionLength;
    }
    else
    {
        i = main.richTextBox1.SelectionStart;
    }
    if (i == text.Length) i = 0;
    int maxSearchLength = text.Length;
    int alreadySearchLength = 0;
    while (i < text.Length && alreadySearchLength <= maxSearchLength)
    {
        j = 0;
        while (i + j < text.Length && text[i + j] == word[j])
        {
            j++;
            if (j == word.Length)
            {
                main.richTextBox1.Select(i, j);
                main.Focus();
                return;
            }
        }
        i += j + 1;
        alreadySearchLength += j + 1;
        if (i >= text.Length && checkBox2.Checked) i = 0;
    }
    MessageBox.Show("未找到!", "查找结果", MessageBoxButtons.OK, MessageBoxIcon.None);
}

查找之前使用Getword()来获取最新的char数组,此时还要考虑一种情况,假设用户需要查找"abc",并且用户当前已经选中"abc",就需要将起始位置设置为光标位置的后3格。同时如果用户勾选了"循环",则变量 i 超出文本长度时,需要将它设置为0,但是这又会引起另一个问题:死循环。为了防止出现死循环,可以设置一个变量alreadySearchLength,这个变量记录已经查找过的字符串长度,当这个变量超过文本长度时,说明已经遍历了全部字符,则退出循环。

查找采用了DFA算法,将需要查找的字符串的首字符跟text数组比较,遇到相同的再比较下一个,这样可以减少查找所用时间。

向下查找和向上查找代码大致相同,但是向上查找不需要判断当前选中字符串是否就是需要查找的字符串。

private void button3_Click(object sender, EventArgs e)//向上查找
{
    if (!GetWord()) return;
    int i = main.richTextBox1.SelectionStart,j;
    if (i == 0 && checkBox2.Checked) i = text.Length - 1;
    if (i == text.Length) i = text.Length - 1;
    int maxSearchLength = text.Length;
    int alreadySearchLength = 0;
    while (i >= 0 && alreadySearchLength <= maxSearchLength)
    {
        j = 0;
        while (i - j >= 0 && text[i - j] == word[word.Length - 1 - j])
        {
            j++;
            if (j == word.Length)
            {
                main.richTextBox1.Select(i - j + 1, j);
                main.Focus();
                return;
            }
        }
        i -= j + 1;
        alreadySearchLength += j + 1;
        if (i <= 0 && checkBox2.Checked) i = text.Length - 1;
    }
    MessageBox.Show("未找到!", "查找结果", MessageBoxButtons.OK, MessageBoxIcon.None);
}

private void button1_Click(object sender, EventArgs e)//替换
{
    if (checkBox1.Checked)
    {
        if (main.richTextBox1.SelectedText.ToLower().Equals(textBox1.Text.ToLower()))
        {
            int position = main.richTextBox1.SelectionStart + textBox2.Text.Length;
            ReplaceWord(main.richTextBox1.SelectionStart, main.richTextBox1.SelectionLength);
            main.richTextBox1.Select(position, 0);
            int start = SearchNext();
            if(start >= 0)
            {
                main.richTextBox1.Select(start, textBox1.Text.Length);
            }
            main.Focus();
        }
        else
        {
            int start = SearchNext();
            if(start < 0)
            {
                ShowNotFoundDialog();
            }
            else
            {
                main.richTextBox1.Select(start, textBox1.Text.Length);
                main.Focus();
            }
        }
    }
    else
    {
        if (main.richTextBox1.SelectedText.Equals(textBox1.Text))
        {
            int position = main.richTextBox1.SelectionStart + textBox2.Text.Length;
            ReplaceWord(main.richTextBox1.SelectionStart, main.richTextBox1.SelectionLength);
            main.richTextBox1.Select(position, 0);
            int start = SearchNext();
            if (start >= 0)
            {
                main.richTextBox1.Select(start, textBox1.Text.Length);
            }
            main.Focus();
        }
        else
        {
            int start = SearchNext();
            if (start < 0)
            {
                ShowNotFoundDialog();
            }
            else
            {
                main.richTextBox1.Select(start, textBox1.Text.Length);
                main.Focus();
            }
        }
    }
}

替换比起查找较为复杂,首先是判断当前选中字符串是否就是要替换的内容,如果是,替换当前选中的文本,并选中下一个出现的位置;如果不是,直接选中下一个出现的位置。替换是利用字符串截取功能实现的,因此在替换前需要先保存光标位置,替换后再重设光标位置。

对齐与高亮

对齐可以使用C#自带的XmlTextWriter来实现,注意命名空间为System.Xml

高亮比之前的查找和替换简单,只需要根据尖括号来查找就行,并将尖括号里的内容设置为蓝色

if (richTextBox1.Text.Trim().Equals(""))
{
    MessageBox.Show("内容为空.", "对齐");
    return;
}
try
{
    XmlDocument document = new XmlDocument();
    document.LoadXml(richTextBox1.Text);
    MemoryStream memoryStream = new MemoryStream();
    XmlTextWriter writer = new XmlTextWriter(memoryStream, null)
    {
        Formatting = Formatting.Indented
    };
    document.Save(writer);
    StreamReader streamReader = new StreamReader(memoryStream);
    memoryStream.Position = 0;
    string xmlString = streamReader.ReadToEnd();
    streamReader.Close();
    memoryStream.Close();
    richTextBox1.Text = xmlString;
}
catch(Exception ex)
{
    MessageBox.Show(ex.Message);
}

启动参数

为了实现快捷打开文本文件,需要程序读取启动参数并在启动后立即打开参数指定的文件

public Form1(string[] args)
{
    InitializeComponent();
    if(args.Length != 0)
    {
        filePath = args[0];
        if (!filePath.EndsWith(".txt"))
        {
            switch(MessageBox.Show("XML Reader仅支持文本文件,是否以文本形式打开" + filePath + "?", "XML Reader", MessageBoxButtons.YesNoCancel, MessageBoxIcon.None))
            {
                case DialogResult.OK:
                    break;
                case DialogResult.No:
                    filePath = null;
                    return;
                case DialogResult.Cancel:
                    System.Environment.Exit(0);
                    break;
            }
        }
        string content = FileIO.ReadFile(filePath);
        if (content == null)
        {
            filePath = null;
        }
        else
        {
            LoadXML(content);
        }
    }
}

注意string[] args并不是自动生成的,而是我手动加上去的,首先在程序入口点Program.Main里获取args,然后再传递给主窗体

static class Program
{
    /// <summary>
    /// 应用程序的主入口点。
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1(args));
    }
}

拖拽事件

当用户手动拖入某个文件时,快捷打开该文件

事件窗口里并没有为我们提供拖拽事件,因此我们需要自己手动定义一个

richTextBox1.AllowDrop = true;
richTextBox1.DragDrop += new DragEventHandler(richTextBox1_DragDrop);

改写DragDrop函数

private void richTextBox1_DragDrop(object sender, DragEventArgs e)
{
    string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
    if (files.Length == 0) return;
    string file = files[0];
    if (Path.GetExtension(file).Equals(".txt"))
    {
        string s = FileIO.ReadFile(file);
        if(s == null)
        {
            MessageBox.Show("读取失败.", "XML Reader", MessageBoxButtons.OK, MessageBoxIcon.Information);
            return;
        }
        else
        {
            if (!isSaved)
            {
                DialogResult result = MessageBox.Show("你想将更改保存到" + Text + "吗?", "XML Reader", MessageBoxButtons.YesNoCancel, MessageBoxIcon.None);
                switch (result)
                {
                    case DialogResult.OK:
                        保存ToolStripMenuItem_Click(null, null);
                        break;
                    case DialogResult.No:
                        break;
                    case DialogResult.Cancel:
                        return;
                }
            }
            LoadXML(s);
        }
    }
    else
    {
        MessageBox.Show("该类型的文件不被支持.", "XML Reader");
    }
}

最终成果

 源文件

EXE程序:https://dearx.lanzoui.com/ioNRKrez29c

源文件:https://dearx.lanzoui.com/i7bWzrez2fi

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dear_Xuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值