ASCU_ALL/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs
2020-09-04 12:49:15 +05:00

606 lines
19 KiB
C#
Raw Blame History

#region MigraDoc - Creating Documents on the Fly
//
// Authors:
// Stefan Lange
// Klaus Potzesny
// David Stephensen
//
// Copyright (c) 2001-2017 empira Software GmbH, Cologne Area (Germany)
//
// http://www.pdfsharp.com
// http://www.migradoc.com
// http://sourceforge.net/projects/pdfsharp
//
// 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.
#endregion
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using MigraDoc.DocumentObjectModel.publics;
namespace MigraDoc.DocumentObjectModel
{
/// <summary>
/// Object to be passed to the Serialize function of a DocumentObject to convert
/// it into DDL.
/// </summary>
public sealed class Serializer
{
/// <summary>
/// A Serializer object for converting MDDOM into DDL.
/// </summary>
/// <param name="textWriter">A TextWriter to write DDL in.</param>
/// <param name="indent">Indent of a new block. Default is 2.</param>
/// <param name="initialIndent">Initial indent to start with.</param>
public Serializer(TextWriter textWriter, int indent, int initialIndent)
{
if (textWriter == null)
throw new ArgumentNullException("textWriter");
_textWriter = textWriter;
_indent = indent;
_writeIndent = initialIndent;
if (textWriter is StreamWriter)
WriteStamp();
}
/// <summary>
/// Initializes a new instance of the Serializer class with the specified TextWriter.
/// </summary>
public Serializer(TextWriter textWriter) : this(textWriter, 2, 0) { }
/// <summary>
/// Initializes a new instance of the Serializer class with the specified TextWriter and indentation.
/// </summary>
public Serializer(TextWriter textWriter, int indent) : this(textWriter, indent, 0) { }
readonly TextWriter _textWriter;
/// <summary>
/// Gets or sets the indentation for a new indentation level.
/// </summary>
public int Indent
{
get { return _indent; }
set { _indent = value; }
}
int _indent = 2;
/// <summary>
/// Gets or sets the initial indentation which precede every line.
/// </summary>
public int InitialIndent
{
get { return _writeIndent; }
set { _writeIndent = value; }
}
int _writeIndent;
/// <summary>
/// Increases indent of DDL code.
/// </summary>
void IncreaseIndent()
{
_writeIndent += _indent;
}
/// <summary>
/// Decreases indent of DDL code.
/// </summary>
void DecreaseIndent()
{
_writeIndent -= _indent;
}
/// <summary>
/// Writes the header for a DDL file containing copyright and creation time information.
/// </summary>
public void WriteStamp()
{
if (_fWriteStamp)
{
WriteComment("Created by empira MigraDoc Document Object Model");
WriteComment(String.Format("generated file created {0:d} at {0:t}", DateTime.Now));
}
}
/// <summary>
/// Appends a string indented without line feed.
/// </summary>
public void Write(string str)
{
string wrappedStr = DoWordWrap(str);
if (wrappedStr.Length < str.Length && wrappedStr != "")
{
WriteLineToStream(wrappedStr);
Write(str.Substring(wrappedStr.Length));
}
else
WriteToStream(str);
CommitText();
}
/// <summary>
/// Writes a string indented with line feed.
/// </summary>
public void WriteLine(string str)
{
string wrappedStr = DoWordWrap(str);
if (wrappedStr.Length < str.Length)
{
WriteLineToStream(wrappedStr);
WriteLine(str.Substring(wrappedStr.Length));
}
else
WriteLineToStream(wrappedStr);
CommitText();
}
/// <summary>
/// Returns the part of the string str that fits into the line (up to 80 chars).
/// If Wordwrap is impossible it returns the input-string str itself.
/// </summary>
string DoWordWrap(string str)
{
if (str.Length + _writeIndent < LineBreakBeyond)
return str;
int idxCRLF = str.IndexOf("\x0D\x0A", StringComparison.Ordinal);
if (idxCRLF > 0 && idxCRLF + _writeIndent <= LineBreakBeyond)
return str.Substring(0, idxCRLF + 1);
int splitIndexBlank = str.Substring(0, LineBreakBeyond - _writeIndent).LastIndexOf(" ", StringComparison.Ordinal);
int splitIndexCRLF = str.Substring(0, LineBreakBeyond - _writeIndent).LastIndexOf("\x0D\x0A", StringComparison.Ordinal);
int splitIndex = Math.Max(splitIndexBlank, splitIndexCRLF);
if (splitIndex == -1)
splitIndex = Math.Min(str.IndexOf(" ", LineBreakBeyond - _writeIndent + 1, StringComparison.Ordinal),
str.IndexOf("\x0D\x0A", LineBreakBeyond - _writeIndent + 1, StringComparison.Ordinal));
return splitIndex > 0 ? str.Substring(0, splitIndex) : str;
}
/// <summary>
/// Writes an empty line.
/// </summary>
public void WriteLine()
{
WriteLine(String.Empty);
}
/// <summary>
/// Write a line without committing (without marking the text as serialized).
/// </summary>
public void WriteLineNoCommit(string str)
{
WriteLineToStream(str);
}
/// <summary>
/// Write a line without committing (without marking the text as serialized).
/// </summary>
public void WriteLineNoCommit()
{
WriteLineNoCommit(String.Empty);
}
/// <summary>
/// Writes a text as comment and automatically word-wraps it.
/// </summary>
public void WriteComment(string comment)
{
if (String.IsNullOrEmpty(comment))
return;
// if string contains CR/LF, split up recursively
int crlf = comment.IndexOf("\x0D\x0A", StringComparison.Ordinal);
if (crlf != -1)
{
WriteComment(comment.Substring(0, crlf));
WriteComment(comment.Substring(crlf + 2));
return;
}
CloseUpLine();
int len;
int chopBeyond = LineBreakBeyond - _indent - "// ".Length;
while ((len = comment.Length) > 0)
{
string wrt;
if (len <= chopBeyond)
{
wrt = "// " + comment;
comment = String.Empty;
}
else
{
int idxChop;
if ((idxChop = comment.LastIndexOf(' ', chopBeyond)) == -1 &&
(idxChop = comment.IndexOf(' ', chopBeyond)) == -1)
{
wrt = "// " + comment;
comment = String.Empty;
}
else
{
wrt = "// " + comment.Substring(0, idxChop);
comment = comment.Substring(idxChop + 1);
}
}
WriteLineToStream(wrt);
CommitText();
}
}
/// <summary>
/// Writes a line break if the current position is not at the beginning
/// of a new line.
/// </summary>
public void CloseUpLine()
{
if (_linePos > 0)
WriteLine();
}
/// <summary>
/// Effectively writes text to the stream. The text is automatically indented and
/// word-wrapped. A given text gets never word-wrapped to keep comments or string
/// literals unbroken.
/// </summary>
void WriteToStream(string text, bool fLineBreak, bool fAutoIndent)
{
// if string contains CR/LF, split up recursively
int crlf = text.IndexOf("\x0D\x0A", StringComparison.Ordinal);
if (crlf != -1)
{
WriteToStream(text.Substring(0, crlf), true, fAutoIndent);
WriteToStream(text.Substring(crlf + 2), fLineBreak, fAutoIndent);
return;
}
int len = text.Length;
if (len > 0)
{
if (_linePos > 0)
{
// does not work
// if (IsBlankRequired(this .lastChar, _text[0]))
// _text = "<22>" + _text;
}
else
{
if (fAutoIndent)
{
text = Indentation + text;
len += _writeIndent;
}
}
_textWriter.Write(text);
_linePos += len;
// wordwrap required?
if (_linePos > LineBreakBeyond)
{
fLineBreak = true;
//this .textWriter.Write("//<2F>"); // for debugging only
}
else
_lastChar = text[len - 1];
}
if (fLineBreak)
{
_textWriter.WriteLine(String.Empty); // what a line break is may depend on encoding
_linePos = 0;
_lastChar = '\x0A';
}
}
/// <summary>
/// Write the text into the stream without breaking it and adds an indentation to it.
/// </summary>
void WriteToStream(string text)
{
WriteToStream(text, false, true);
}
/// <summary>
/// Write a line to the stream.
/// </summary>
void WriteLineToStream(string text)
{
WriteToStream(text, true, true);
}
///// <summary>
///// Mighty function to figure out if a blank is required as separator.
///// // Does not work without context...
///// </summary>
//bool IsBlankRequired(char left, char right)
//{
// if (left == ' ' || right == ' ')
// return false;
// // 1st try
// bool leftLetterOrDigit = Char.IsLetterOrDigit(left);
// bool rightLetterOrDigit = Char.IsLetterOrDigit(right);
// if (leftLetterOrDigit && rightLetterOrDigit)
// return true;
// return false;
//}
/// <summary>
/// Start attribute part.
/// </summary>
public int BeginAttributes()
{
int pos = Position;
WriteLineNoCommit("[");
IncreaseIndent();
BeginBlock();
return pos;
}
/// <summary>
/// Start attribute part.
/// </summary>
public int BeginAttributes(string str)
{
int pos = Position;
WriteLineNoCommit(str);
WriteLineNoCommit("[");
IncreaseIndent();
BeginBlock();
return pos;
}
/// <summary>
/// End attribute part.
/// </summary>
public bool EndAttributes()
{
DecreaseIndent();
WriteLineNoCommit("]");
return EndBlock();
}
/// <summary>
/// End attribute part.
/// </summary>
public bool EndAttributes(int pos)
{
bool commit = EndAttributes();
if (!commit)
Position = pos;
return commit;
}
/// <summary>
/// Write attribute of type Unit, Color, int, float, double, bool, string or enum.
/// </summary>
public void WriteSimpleAttribute(string valueName, object value)
{
INullableValue ival = value as INullableValue;
if (ival != null)
value = ival.GetValue();
Type type = value.GetType();
if (type == typeof(Unit))
{
string strUnit = value.ToString();
if (((Unit)value).Type == UnitType.Point)
WriteLine(valueName + " = " + strUnit);
else
WriteLine(valueName + " = \"" + strUnit + "\"");
}
else if (type == typeof(float))
{
WriteLine(valueName + " = " + ((float)value).ToString(CultureInfo.InvariantCulture));
}
else if (type == typeof(double))
{
WriteLine(valueName + " = " + ((double)value).ToString(CultureInfo.InvariantCulture));
}
else if (type == typeof(bool))
{
WriteLine(valueName + " = " + value.ToString().ToLower());
}
else if (type == typeof(string))
{
StringBuilder sb = new StringBuilder(value.ToString());
sb.Replace("\\", "\\\\");
sb.Replace("\"", "\\\"");
WriteLine(valueName + " = \"" + sb + "\"");
}
#if !NETFX_CORE
else if (type == typeof(int) || type.BaseType == typeof(Enum) || type == typeof(Color))
#else
else if (type == typeof(int) || type.GetTypeInfo().BaseType == typeof(Enum) || type == typeof(Color))
#endif
{
WriteLine(valueName + " = " + value);
}
else
{
string message = String.Format("Type '{0}' of value '{1}' not supported", type, valueName);
Debug.Assert(false, message);
}
}
/// <summary>
/// Start content part.
/// </summary>
public int BeginContent()
{
int pos = Position;
WriteLineNoCommit("{");
IncreaseIndent();
BeginBlock();
return pos;
}
/// <summary>
/// Start content part.
/// </summary>
public int BeginContent(string str)
{
int pos = Position;
WriteLineNoCommit(str);
WriteLineNoCommit("{");
IncreaseIndent();
BeginBlock();
return pos;
}
/// <summary>
/// End content part.
/// </summary>
public bool EndContent()
{
DecreaseIndent();
WriteLineNoCommit("}");
return EndBlock();
}
/// <summary>
/// End content part.
/// </summary>
public bool EndContent(int pos)
{
bool commit = EndContent();
if (!commit)
Position = pos;
return commit;
}
/// <summary>
/// Starts a new nesting block.
/// </summary>
public int BeginBlock()
{
int pos = Position;
if (_stackIdx + 1 >= _commitTextStack.Length)
throw new ArgumentException("Block nesting level exhausted.");
_stackIdx += 1;
_commitTextStack[_stackIdx] = false;
return pos;
}
/// <summary>
/// Ends a nesting block.
/// </summary>
public bool EndBlock()
{
if (_stackIdx <= 0)
throw new ArgumentException("Block nesting level underflow.");
_stackIdx -= 1;
if (_commitTextStack[_stackIdx + 1])
_commitTextStack[_stackIdx] = _commitTextStack[_stackIdx + 1];
return _commitTextStack[_stackIdx + 1];
}
/// <summary>
/// Ends a nesting block.
/// </summary>
public bool EndBlock(int pos)
{
bool commit = EndBlock();
if (!commit)
Position = pos;
return commit;
}
/// <summary>
/// Gets or sets the position within the underlying stream.
/// </summary>
int Position
{
get
{
_textWriter.Flush();
StreamWriter streamWriter = _textWriter as StreamWriter;
if (streamWriter != null)
return (int)streamWriter.BaseStream.Position;
StringWriter stringWriter = _textWriter as StringWriter;
if (stringWriter != null)
return stringWriter.GetStringBuilder().Length;
return 0;
}
set
{
_textWriter.Flush();
StreamWriter streamWriter = _textWriter as StreamWriter;
if (streamWriter != null)
streamWriter.BaseStream.SetLength(value);
else
{
StringWriter stringWriter = _textWriter as StringWriter;
if (stringWriter != null)
stringWriter.GetStringBuilder().Length = value;
}
}
}
/// <summary>
/// Flushes the buffers of the underlying text writer.
/// </summary>
public void Flush()
{
_textWriter.Flush();
}
/// <summary>
/// Returns an indent string of blanks.
/// </summary>
static string Ind(int indent)
{
return new String(' ', indent);
}
/// <summary>
/// Gets an indent string of current indent.
/// </summary>
string Indentation
{
get { return Ind(_writeIndent); }
}
/// <summary>
/// Marks the current block as 'committed'. That means the block contains
/// serialized data.
/// </summary>
private void CommitText()
{
_commitTextStack[_stackIdx] = true;
}
int _stackIdx;
readonly bool[] _commitTextStack = new bool[32];
int _linePos;
const int LineBreakBeyond = 200;
char _lastChar;
bool _fWriteStamp = false;
}
}