mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Resource export sometimes needs framework-specific ResX type names. Exposing the WinForms and mscorlib assembly suffixes keeps the writer default-compatible while letting callers choose older resource type names when needed. Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
396 lines
13 KiB
C#
396 lines
13 KiB
C#
// Copyright (c) 2018 Daniel Grunwald
|
|
// This file is based on the Mono implementation of ResXResourceWriter.
|
|
// It is modified to add support for "ResourceSerializedObject" values.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// Copyright (c) 2004-2005 Novell, Inc.
|
|
//
|
|
// Authors:
|
|
// Duncan Mak [email protected]
|
|
// Gonzalo Paniagua Javier [email protected]
|
|
// Peter Bartok [email protected]
|
|
// Gary Barnett [email protected]
|
|
// includes code by Mike Krüger and Lluis Sanchez
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
using ICSharpCode.Decompiler.Metadata;
|
|
|
|
namespace ICSharpCode.Decompiler.Util
|
|
{
|
|
[SuppressMessage("Usage", "CA1063:Implement IDisposable Correctly",
|
|
Justification = "Ported from the Mono ResXResourceWriter implementation; preserved verbatim for fidelity with the upstream.")]
|
|
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed",
|
|
Justification = "By design: the writer doesn't own the underlying stream/textwriter passed in by the caller. Mono behavior preserved.")]
|
|
#if INSIDE_SYSTEM_WEB
|
|
internal
|
|
#else
|
|
public
|
|
#endif
|
|
class ResXResourceWriter : IDisposable
|
|
{
|
|
private string filename;
|
|
private Stream stream;
|
|
private TextWriter textwriter;
|
|
private XmlTextWriter writer;
|
|
private bool written;
|
|
private string base_path;
|
|
|
|
public static readonly string BinSerializedObjectMimeType = "application/x-microsoft.net.object.binary.base64";
|
|
public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64";
|
|
public static readonly string DefaultSerializedObjectMimeType = BinSerializedObjectMimeType;
|
|
public static readonly string ResMimeType = "text/microsoft-resx";
|
|
public static readonly string SoapSerializedObjectMimeType = "application/x-microsoft.net.object.soap.base64";
|
|
public static readonly string Version = "2.0";
|
|
|
|
public ResXResourceWriter(Stream stream)
|
|
{
|
|
if (stream == null)
|
|
throw new ArgumentNullException(nameof(stream));
|
|
|
|
if (!stream.CanWrite)
|
|
throw new ArgumentException("stream is not writable.", nameof(stream));
|
|
|
|
this.stream = stream;
|
|
}
|
|
|
|
public ResXResourceWriter(TextWriter textWriter)
|
|
{
|
|
if (textWriter == null)
|
|
throw new ArgumentNullException(nameof(textWriter));
|
|
|
|
this.textwriter = textWriter;
|
|
}
|
|
|
|
public ResXResourceWriter(string fileName)
|
|
{
|
|
if (fileName == null)
|
|
throw new ArgumentNullException(nameof(fileName));
|
|
|
|
this.filename = fileName;
|
|
}
|
|
|
|
~ResXResourceWriter()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
const string DefaultWinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
|
|
const string DefaultMSCorLibAssemblyName = ", mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
|
|
string winFormsAssemblyName = DefaultWinFormsAssemblyName;
|
|
string msCorLibAssemblyName = DefaultMSCorLibAssemblyName;
|
|
|
|
public string WinFormsAssemblyName {
|
|
get => winFormsAssemblyName;
|
|
set {
|
|
ThrowIfWritingStarted();
|
|
winFormsAssemblyName = value ?? throw new ArgumentNullException(nameof(value));
|
|
}
|
|
}
|
|
|
|
public string MSCorLibAssemblyName {
|
|
get => msCorLibAssemblyName;
|
|
set {
|
|
ThrowIfWritingStarted();
|
|
msCorLibAssemblyName = value ?? throw new ArgumentNullException(nameof(value));
|
|
}
|
|
}
|
|
|
|
void ThrowIfWritingStarted()
|
|
{
|
|
if (writer != null || written)
|
|
throw new InvalidOperationException("Assembly names cannot be changed after writing started.");
|
|
}
|
|
|
|
void InitWriter()
|
|
{
|
|
if (filename != null)
|
|
stream = File.Open(filename, FileMode.Create);
|
|
if (textwriter == null)
|
|
textwriter = new StreamWriter(stream, Encoding.UTF8);
|
|
|
|
writer = new XmlTextWriter(textwriter);
|
|
writer.Formatting = Formatting.Indented;
|
|
writer.WriteStartDocument();
|
|
writer.WriteStartElement("root");
|
|
writer.WriteRaw(schema);
|
|
WriteHeader("resmimetype", "text/microsoft-resx");
|
|
WriteHeader("version", "1.3");
|
|
WriteHeader("reader", "System.Resources.ResXResourceReader" + WinFormsAssemblyName);
|
|
WriteHeader("writer", "System.Resources.ResXResourceWriter" + WinFormsAssemblyName);
|
|
}
|
|
|
|
void WriteHeader(string name, string value)
|
|
{
|
|
writer.WriteStartElement("resheader");
|
|
writer.WriteAttributeString("name", name);
|
|
writer.WriteStartElement("value");
|
|
writer.WriteString(value);
|
|
writer.WriteEndElement();
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
void WriteNiceBase64(byte[] value, int offset, int length)
|
|
{
|
|
string base64 = Convert.ToBase64String(
|
|
value, offset, length,
|
|
Base64FormattingOptions.InsertLineBreaks);
|
|
writer.WriteString(base64);
|
|
}
|
|
|
|
void WriteBytes(string name, string type, byte[] value, int offset, int length, string comment)
|
|
{
|
|
writer.WriteStartElement("data");
|
|
writer.WriteAttributeString("name", name);
|
|
|
|
if (type != null)
|
|
{
|
|
writer.WriteAttributeString("type", type);
|
|
// byte[] should never get a mimetype, otherwise MS.NET won't be able
|
|
// to parse the data.
|
|
if (type != "System.Byte[]" + MSCorLibAssemblyName)
|
|
writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType);
|
|
writer.WriteStartElement("value");
|
|
WriteNiceBase64(value, offset, length);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType);
|
|
writer.WriteStartElement("value");
|
|
WriteNiceBase64(value, offset, length);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
|
|
if (!string.IsNullOrEmpty(comment))
|
|
{
|
|
writer.WriteStartElement("comment");
|
|
writer.WriteString(comment);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
void WriteString(string name, string value, string type, string comment)
|
|
{
|
|
writer.WriteStartElement("data");
|
|
writer.WriteAttributeString("name", name);
|
|
if (type != null)
|
|
{
|
|
writer.WriteAttributeString("type", type);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteAttributeString("xml:space", "preserve");
|
|
}
|
|
writer.WriteStartElement("value");
|
|
writer.WriteString(value);
|
|
writer.WriteEndElement();
|
|
if (!string.IsNullOrEmpty(comment))
|
|
{
|
|
writer.WriteStartElement("comment");
|
|
writer.WriteString(comment);
|
|
writer.WriteEndElement();
|
|
}
|
|
writer.WriteEndElement();
|
|
writer.WriteWhitespace("\n ");
|
|
}
|
|
|
|
public void AddResource(string name, byte[] value)
|
|
{
|
|
AddResource(name, value, string.Empty);
|
|
}
|
|
|
|
public void AddResource(string name, object value)
|
|
{
|
|
AddResource(name, value, string.Empty);
|
|
}
|
|
|
|
private void AddResource(string name, object value, string comment)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException(nameof(name));
|
|
|
|
if (written)
|
|
throw new InvalidOperationException("The resource is already generated.");
|
|
|
|
if (writer == null)
|
|
InitWriter();
|
|
|
|
switch (value)
|
|
{
|
|
case null:
|
|
// nulls written as ResXNullRef
|
|
WriteString(name, "", "System.Resources.ResXNullRef" + WinFormsAssemblyName, comment);
|
|
break;
|
|
case string s:
|
|
WriteString(name, s, null, comment);
|
|
break;
|
|
case bool bo:
|
|
WriteString(name, bo.ToString(CultureInfo.InvariantCulture), "System.Boolean" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case char ch:
|
|
WriteString(name, ch.ToString(CultureInfo.InvariantCulture), "System.Char" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case sbyte sb:
|
|
WriteString(name, sb.ToString(CultureInfo.InvariantCulture), "System.SByte" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case byte b:
|
|
WriteString(name, b.ToString(CultureInfo.InvariantCulture), "System.Byte" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case short sh:
|
|
WriteString(name, sh.ToString(CultureInfo.InvariantCulture), "System.Int16" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case ushort ush:
|
|
WriteString(name, ush.ToString(CultureInfo.InvariantCulture), "System.UInt16" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case int i:
|
|
WriteString(name, i.ToString(CultureInfo.InvariantCulture), "System.Int32" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case uint u:
|
|
WriteString(name, u.ToString(CultureInfo.InvariantCulture), "System.UInt32" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case long l:
|
|
WriteString(name, l.ToString(CultureInfo.InvariantCulture), "System.Int64" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case ulong ul:
|
|
WriteString(name, ul.ToString(CultureInfo.InvariantCulture), "System.UInt64" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case float f:
|
|
WriteString(name, f.ToString(CultureInfo.InvariantCulture), "System.Single" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case double d:
|
|
WriteString(name, d.ToString(CultureInfo.InvariantCulture), "System.Double" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case decimal m:
|
|
WriteString(name, m.ToString(CultureInfo.InvariantCulture), "System.Decimal" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case DateTime dt:
|
|
WriteString(name, dt.ToString(CultureInfo.InvariantCulture), "System.DateTime" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case TimeSpan sp:
|
|
WriteString(name, sp.ToString(), "System.TimeSpan" + MSCorLibAssemblyName, comment);
|
|
break;
|
|
case byte[] array:
|
|
WriteBytes(name, "System.Byte[]" + MSCorLibAssemblyName, array, 0, array.Length, comment);
|
|
break;
|
|
case MemoryStream memoryStream:
|
|
var arr = memoryStream.ToArray();
|
|
WriteBytes(name, null, arr, 0, arr.Length, comment);
|
|
break;
|
|
case ResourceSerializedObject rso:
|
|
var bytes = rso.GetBytes();
|
|
WriteBytes(name, rso.TypeName, bytes, 0, bytes.Length, comment);
|
|
break;
|
|
default:
|
|
throw new NotSupportedException($"Value '{value}' of type {value.GetType().FullName} is not supported by this version of ResXResourceWriter. Use byte arrays or streams instead.");
|
|
}
|
|
}
|
|
|
|
public void AddResource(string name, string value)
|
|
{
|
|
AddResource(name, value, string.Empty);
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
if (!written)
|
|
{
|
|
Generate();
|
|
}
|
|
|
|
if (writer != null)
|
|
{
|
|
writer.Close();
|
|
stream = null;
|
|
filename = null;
|
|
textwriter = null;
|
|
}
|
|
}
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
public void Generate()
|
|
{
|
|
if (writer == null)
|
|
InitWriter();
|
|
|
|
if (written)
|
|
throw new InvalidOperationException("The resource is already generated.");
|
|
|
|
written = true;
|
|
writer.WriteEndElement();
|
|
writer.Flush();
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
Close();
|
|
}
|
|
|
|
static readonly string schema = @"
|
|
<xsd:schema id='root' xmlns='' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
|
|
<xsd:element name='root' msdata:IsDataSet='true'>
|
|
<xsd:complexType>
|
|
<xsd:choice maxOccurs='unbounded'>
|
|
<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' msdata:Ordinal='1' />
|
|
<xsd:attribute name='type' type='xsd:string' msdata:Ordinal='3' />
|
|
<xsd:attribute name='mimetype' type='xsd:string' msdata:Ordinal='4' />
|
|
</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>
|
|
".Replace("'", "\"").Replace("\t", " ");
|
|
|
|
public string BasePath {
|
|
get { return base_path; }
|
|
set { base_path = value; }
|
|
}
|
|
}
|
|
}
|