C# - XML - 压缩

4
我有一个情况,需要生成一个XML文件提交到一个webservice,有时由于数据量太大会超过30mb或50mb。
我需要使用c#、.net framework 4.0来压缩文件,而是压缩其中一个节点中的大部分数据。 我不知道如何操作...是否有人可以给我提供一个例子,请告诉我如何完成这个任务。
XML文件看起来像这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<HeaderTalk xmlns="http://www.w3schools.com/xml">
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
  <Class>CHAR-CLM</Class>      
</MessageDetails>
<SenderDetails>
  <IDAuthentication>
    <SenderID>aaaaaa</SenderID>
    <Authentication>
      <Method>MD5</Method>
      <Role>principal</Role>
      <Value>a3MweCsv60kkAgzEpXeCqQ==</Value>
    </Authentication>
  </IDAuthentication>
  <EmailAddress>Someone@somewhere.com</EmailAddress>
</SenderDetails>
</Header>
<TalkDetails>
  <ChannelRouting>
   <Channel>
     <URI>1953</URI>
     <Product>My product</Product>
     <Version>2.0</Version>
    </Channel>
</ChannelRouting>
</TalkDetails>
<Body>
   <envelope xmlns="http://www.w3schools.com/xml/">       
     <PeriodEnd>2013-08-13</PeriodEnd>
     <IRmark Type="generic">zZrxvJ7JmMNaOyrMs9ZOaRuihkg=</IRmark>
     <Sender>Individual</Sender>
     <Report>
       <AuthOfficial>
          <OffName>
            <Fore>B</Fore>
            <Sur>M</Sur>
          </OffName>
          <Phone>0123412345</Phone>
        </AuthOfficial>
    <DefaultCurrency>GBP</DefaultCurrency>
    <Claim>
      <OrgName>B</OrgName>
      <ref>AB12345</ref>
      <Repayment>
        <Account>
          <Donor>
            <Fore>Barry</Fore>
           </Donor>
            <Total>7.00</Total>              
        </Account>           
        <Account>
          <Donor>
            <Fore>Anthony</Fore>               
          </Donor>             
          <Total>20.00</Total>
        </Account>                  
      </Repayment>
      </Claim>
      </Report>
   </envelope>
 </Body>
</HeaderTalk>
CLAIM节点是我想要压缩的内容,因为它可以包含数百万条记录。我是编码新手,花了很长时间才生成这个XML,并一直在寻找一种压缩节点的方法,但我就是无法让它工作。结果需要与DefaultCurrency节点之前完全相同。
 </AuthOfficial>
 <DefaultCurrency>GBP</DefaultCurrency>
 <CompressedPart Type="zip">UEsDBBQAAAAIAFt690K1</CompressedPart>
 </Report>
 </envelope>
 </Body>
 </HeaderTalk>

或者
 </AuthOfficial>
 <DefaultCurrency>GBP</DefaultCurrency>
 <CompressedPart Type="gzip">UEsDBBQAAAAIAFt690K1</CompressedPart>
 </Report>
 </envelope>
 </Body>
 </HeaderTalk>

非常感谢大家的提前帮助。如果有人能建议我在哪里寻找并获得一些想法,那就更好了。

为了创建文件,我简单地通过遍历数据集并使用XmlElements编写节点,并将innertexts设置为我的值。

我用来编写的代码是...

//claim
XmlElement GovtSenderClaim = xmldoc.CreateElement("Claim");
XmlElement GovtSenderOrgname = xmldoc.CreateElement("OrgName");
GovtSenderOrgname.InnerText = Charity_name;
GovtSenderClaim.AppendChild(GovtSenderOrgname);

 XmlElement GovtSenderHMRCref = xmldoc.CreateElement("ref");
 GovtSenderHMRCref.InnerText = strref ;
 GovtSenderClaim.AppendChild(GovtSenderref);

 XmlElement GovtSenderRepayments = xmldoc.CreateElement("Repayment");
 while (reader.Read())
 {
  XmlElement GovtSenderAccount = xmldoc.CreateElement("Account");
  XmlElement GovtSenderDonor = xmldoc.CreateElement("Donor");

   XmlElement GovtSenderfore = xmldoc.CreateElement("Fore");
   GovtSenderfore.InnerText = reader["EmployeeName_first_name"].ToString();
   GovtSenderDonor.AppendChild(GovtSenderfore);

   GovtSenderAccount .AppendChild(GovtSenderDonor);

   XmlElement GovtSenderTotal = xmldoc.CreateElement("Total");
   GovtSenderTotal.InnerText = reader["Total"].ToString();

   GovtSenderAccount .AppendChild(GovtSenderTotal);

   GovtSenderRepayments.AppendChild(GovtSenderAccount );
 }
  GovtSenderClaim.AppendChild(GovtSenderRepayments);


   GovtSenderReport.AppendChild(GovtSenderClaim);

而其余的节点要关闭。。

你能修改这个 Web 服务吗? - Dustin Kingen
如果 Web 服务支持 gzipdeflate 编码,您可以尝试发送压缩文件而无需更改数据。 - Gene
根据粗略计算,gzip 可以将您的 50 MB 压缩为约 10 MB(可能更多,不太可能更少),但是您还需要对其进行 base64 编码,这将使大小增加到约 13.5 MB。这样的节省足够好吗? - Jim Mischel
嗨,Romoku..这是第三方服务,所以我不能修改Web服务。 - user2664502
4个回答

2
我需要使用C#、.NET Framework 4.0来压缩文件,最好使用其中一个节点。你可以使用GZip压缩,例如:
public static void Compress(FileInfo fileToCompress)
        {
            using (FileStream originalFileStream = fileToCompress.OpenRead())
            {
                if ((File.GetAttributes(fileToCompress.FullName) & FileAttributes.Hidden) != FileAttributes.Hidden & fileToCompress.Extension != ".gz")
                {
                    using (FileStream compressedFileStream = File.Create(fileToCompress.FullName + ".gz"))
                    {
                        using (GZipStream compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
                        {
                            originalFileStream.CopyTo(compressionStream);
                            Console.WriteLine("Compressed {0} from {1} to {2} bytes.",
                                fileToCompress.Name, fileToCompress.Length.ToString(), compressedFileStream.Length.ToString());
                        }
                    }
                }
            }
        }

        public static void Decompress(FileInfo fileToDecompress)
        {
            using (FileStream originalFileStream = fileToDecompress.OpenRead())
            {
                string currentFileName = fileToDecompress.FullName;
                string newFileName = currentFileName.Remove(currentFileName.Length - fileToDecompress.Extension.Length);

                using (FileStream decompressedFileStream = File.Create(newFileName))
                {
                    using (GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress))
                    {
                        decompressionStream.CopyTo(decompressedFileStream);
                        Console.WriteLine("Decompressed: {0}", fileToDecompress.Name);
                    }
                }
            }
        }

另一种可能的方法是使用Deflate。请参见此处。 GZipStream和Deflate流之间的主要区别在于,GZipStream将添加CRC以确保数据没有错误。

为什么不使用“Deflate”?通常它更好。 - xanatos
@xanatos 有很多种方法。底部还添加了Deflate的链接 :) - Ehsan
嗨Ehsan,但这个会压缩整个xml文件,我只想压缩xml文件中的一个NODE。 - user2664502
  1. .NET GZip只是在Deflate基础上添加了几个字节的头部,因此大小并没有太大的区别。
  2. OP正在询问如何压缩文件的一部分,将Claim节点替换为CompressedPart节点。这与您建议的压缩整个文件有很大不同。
- Jim Mischel

2
你可以尝试这个方法:它只会压缩你选择的节点。与你所需求的略有不同,因为它将替换元素的内容,但保留元素及其属性不变。
{
    // You are using a namespace! 
    XNamespace ns = "http://www.w3schools.com/xml/";

    var xml2 = XDocument.Parse(xml);

    // Compress
    {
        // Will compress all the XElement that are called Claim
        // You should probably select the XElement in a better way
        var nodes = from p in xml2.Descendants(ns + "Claim") select p;

        foreach (XElement el in nodes)
        {
            CompressElementContent(el);
        }
    }

    // Decompress
    {
        // Will decompress all the XElement that are called Claim
        // You should probably select the XElement in a better way
        var nodes = from p in xml2.Descendants(ns + "Claim") select p;

        foreach (XElement el in nodes)
        {
            DecompressElementContent(el);
        }
    }
}

public static void CompressElementContent(XElement el)
{
    string content;

    using (var reader = el.CreateReader())
    {
        reader.MoveToContent();
        content = reader.ReadInnerXml();
    }

    using (var ms = new MemoryStream())
    {
        using (DeflateStream defl = new DeflateStream(ms, CompressionMode.Compress))
        {
            // So that the BOM isn't written we use build manually the encoder.
            // See for example https://dev59.com/KHE95IYBdhLWcg3wI6jt#2437780
            // But note that false is implicit in the parameterless constructor
            using (StreamWriter sw = new StreamWriter(defl, new UTF8Encoding()))
            {
                sw.Write(content);
            }
        }

        string base64 = Convert.ToBase64String(ms.ToArray());

        el.ReplaceAll(new XText(base64));
    }
}

public static void DecompressElementContent(XElement el)
{
    var reader = el.CreateReader();
    reader.MoveToContent();
    var content = reader.ReadInnerXml();

    var bytes = Convert.FromBase64String(content);

    using (var ms = new MemoryStream(bytes))
    {
        using (DeflateStream defl = new DeflateStream(ms, CompressionMode.Decompress))
        {
            using (StreamReader sr = new StreamReader(defl, Encoding.UTF8))
            {
                el.ReplaceAll(ParseXmlFragment(sr));
            }
        }
    }
}

public static IEnumerable<XNode> ParseXmlFragment(StreamReader sr)
{
    var settings = new XmlReaderSettings
    {
        ConformanceLevel = ConformanceLevel.Fragment
    };

    using (var xmlReader = XmlReader.Create(sr, settings))
    {
        xmlReader.MoveToContent();

        while (xmlReader.ReadState != ReadState.EndOfFile)
        {
            yield return XNode.ReadFrom(xmlReader);
        }
    }
}

解压缩是相当复杂的,因为替换 Xml 的内容很困难。最终,我通过在 ParseXmlFragment 中将 XNode 拆分为 Xnode,并在 DecompressElementContent 中使用 ReplaceAll 进行替换。另外,您的 XML 中有两个类似但不同的命名空间:http://www.w3schools.com/xml 和 http://www.w3schools.com/xml/。
这种变体将完全按照您的要求执行(因此它将创建一个 CompressedPart 节点),但不包括压缩类型属性。
{
    XNamespace ns = "http://www.w3schools.com/xml/";

    var xml2 = XDocument.Parse(xml);

    // Compress
    {
        // Here the ToList() is necessary, because we will replace the selected elements
        var nodes = (from p in xml2.Descendants(ns + "Claim") select p).ToList();

        foreach (XElement el in nodes)
        {
            CompressElementContent(el);
        }
    }

    // Decompress
    {
        // Here the ToList() is necessary, because we will replace the selected elements
        var nodes = (from p in xml2.Descendants("CompressedPart") select p).ToList();

        foreach (XElement el in nodes)
        {
            DecompressElementContent(el);
        }
    }
}

public static void CompressElementContent(XElement el)
{
    string content = el.ToString();

    using (var ms = new MemoryStream())
    {
        using (DeflateStream defl = new DeflateStream(ms, CompressionMode.Compress))
        {
            // So that the BOM isn't written we use build manually the encoder.
            using (StreamWriter sw = new StreamWriter(defl, new UTF8Encoding()))
            {
                sw.Write(content);
            }
        }

        string base64 = Convert.ToBase64String(ms.ToArray());

        var newEl = new XElement("CompressedPart", new XText(base64));
        el.ReplaceWith(newEl);
    }
}

public static void DecompressElementContent(XElement el)
{
    var reader = el.CreateReader();
    reader.MoveToContent();
    var content = reader.ReadInnerXml();

    var bytes = Convert.FromBase64String(content);

    using (var ms = new MemoryStream(bytes))
    {
        using (DeflateStream defl = new DeflateStream(ms, CompressionMode.Decompress))
        {
            using (StreamReader sr = new StreamReader(defl, Encoding.UTF8))
            {
                var newEl = XElement.Parse(sr.ReadToEnd());
                el.ReplaceWith(newEl);
            }
        }
    }
}

非常感谢您,Xanatos。这让我有了一个开始。现在我只是尝试使用“ZIP”或“GZIP”的标准设置。 - user2664502
嗨,我不得不做出一些微小的改变才能使它正常工作。再次感谢你。 - user2664502

1
你所询问的是可能的,但有点复杂。你需要在内存中创建压缩节点,然后将其写入。我不知道你是如何编写XML的,所以我假设你有类似以下内容的东西:
open xml writer
write <MessageDetails>
write <SenderDetails>
write other nodes
write Claim node
write other stuff
close file

要编写您的声明节点,您需要编写到内存流中,然后进行Base64编码。生成的字符串是您写入文件作为<CompressedPart>的内容。
string compressedData;
using (MemoryStream ms = new MemoryStream())
{
    using (GZipStream gz = new GZipStream(CompressionMode.Compress, true))
    {
        using (XmlWriter writer = XmlWriter.Create(gz))
        {
            writer.WriteStartElement("Claim");
            // write claim stuff here
            writer.WriteEndElement();
        }
    }
    // now base64 encode the memory stream buffer
    byte[] buff = ms.GetBuffer();
    compressedData = Convert.ToBase64String(buff, 0, buff.Length);
}

您的数据现在在compressedData字符串中,您可以将其写入元素数据。

正如我在评论中所说,GZip通常会减少原始XML大小的80%,因此50 MB变为10 MB。但是,base64编码会使压缩后的大小增加33%。我预计结果应该大约为13.5 MB。

更新

根据您的额外代码,您想要做的事情看起来并不太困难。我认为您想做的是:

// do a bunch of stuff
GovtSenderClaim.AppendChild(GovtSenderRepayments);

// start of added code

// compress the GovtSenderClaim element
// This code writes the GovtSenderClaim element to a compressed MemoryStream.
// We then read the MemoryStream and create a base64 encoded representation.
string compressedData;
using (MemoryStream ms = new MemoryStream())
{
    using (GZipStream gz = new GZipStream(CompressionMode.Compress, true))
    {
        using (StreamWriter writer = StreamWriter(gz))
        {
            GovtSenderClaim.Save(writer);
        }
    }
    // now base64 encode the memory stream buffer
    byte[] buff = ms.ToArray();
    compressedData = Convert.ToBase64String(buff, 0, buff.Length);
}

// compressedData now contains the compressed Claim node, encoded in base64.

// create the CompressedPart element
XElement CompressedPart = xmldoc.CreateElement("CompressedPart");
CompressedPart.SetAttributeValue("Type", "gzip");
CompressedPart.SetValue(compressedData);

GovtSenderReport.AppendChild(CompressedPart);
// GovtSenderReport.AppendChild(GovtSenderClaim);

嗨Jim,我正在使用普通的Xmlelemnt、create element、appendchild等方法。我已经编辑了问题并添加了我所使用代码。谢谢。 - user2664502
嗨,吉姆,唯一的问题是我在创建文件时没有使用Xelement..还是用旧方法。所以没有xmlElement的SAVE。 - user2664502
@user2664502:对 Save 的调用将元素写入内存流。这就是压缩的过程。然后代码读取该内存流并创建 compressedData 字符串,该字符串被写入 CompressedPart 元素。我会在示例中添加一些注释。 - Jim Mischel

0

这是我所做的让它工作的方法...

public void compressTheData(string xml)
{
  XNamespace ns =  "http://www.w3schools.com/xml/";
  var xml2 = XDocument.Load(xml);   

  // Compress
  {
   var nodes = (from p in xml2.Descendants(ns + "Claim") select p).ToList();
    foreach (XElement el in nodes)
    {      
        CompressElementContent(el);           
    }
}
xml2.Save(xml);   
}


public static void CompressElementContent(XElement el)
{
  string content = el.ToString();    

  using (var ms = new MemoryStream())
  {
    using (GZipStream defl = new GZipStream(ms, CompressionMode.Compress))
    {           
        using (StreamWriter sw = new StreamWriter(defl))
        {
            sw.Write(content); 
        }
    }
    string base64 = Convert.ToBase64String(ms.ToArray());  
    XElement newEl = new XElement("CompressedPart", new XText(base64));
    XAttribute attrib = new XAttribute("Type", "gzip");
    newEl.Add(attrib);
    el.ReplaceWith(newEl);
  }
 }

感谢大家的意见。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接