606 lines
19 KiB
C#
606 lines
19 KiB
C#
#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;
|
||
}
|
||
}
|