zaaReloaded2/zaaReloaded2/Formatter/Formatter.cs

442 lines
15 KiB
C#
Executable File

/* Formatter.cs
* part of zaaReloaded2
*
* Copyright 2015-2017 Daniel Kraus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Office.Interop.Word;
using zaaReloaded2.LabModel;
using zaaReloaded2.Controller;
using zaaReloaded2.Controller.Elements;
namespace zaaReloaded2.Formatter
{
/// <summary>
/// Formats and writes a <see cref="Laboratory"/> to a Word document.
/// </summary>
public class Formatter
{
#region Properties
/// <summary>
/// Gets or sets the Settings that this Formatter works with.
/// </summary>
public Settings Settings { get; set; }
/// <summary>
/// Gets or sets the <see cref="Laboratory"/> that shall be
/// formatted.
/// </summary>
public Laboratory Laboratory
{
[DebuggerStepThrough]
get
{
return _laboratory;
}
set
{
_laboratory = value;
_timePointFormatters = new TimePointFormatterDictionary();
foreach (TimePoint tp in _laboratory.TimePoints.Values)
{
_timePointFormatters[tp.TimeStamp] =
new TimePointFormatter(tp, Settings.ReferenceStyle, Settings.AbnormalStyle);
}
}
}
/// <summary>
/// Gets the current working set of TimePointFormatters.
/// </summary>
public Dictionary<DateTime, TimePointFormatter> WorkingTimePoints { get; private set; }
/// <summary>
/// Is true if this Formatter object carries a Laboratory with
/// at least one time point, and if there are Settings to work with.
/// </summary>
public bool CanRun
{
get
{
return (Settings != null)
&& (Laboratory != null)
&& (Laboratory.TimePoints.Count > 0);
}
}
#endregion
#region Constructors
public Formatter()
{
Settings = new Settings();
_secondaryBuffer = new DocumentWriter();
_primaryBuffer = new DocumentWriter(_secondaryBuffer);
}
public Formatter(Document document)
: this()
{
Document = document;
_secondaryBuffer.Document = document;
}
#endregion
#region Public methods
/// <summary>
/// Writes some text to the current document.
/// Does nothing if there is no current document.
/// </summary>
/// <param name="text">Text to write to the current document.
/// </param>
public void Write(string text)
{
_primaryBuffer.Write(text);
}
/// <summary>
/// Writes a paragraph to the document.
/// </summary>
/// <param name="text"></param>
public void WriteParagraph(string text)
{
_primaryBuffer.WriteLine(text);
}
/// <summary>
/// Formats the laboratory and writes it to a Word document.
/// </summary>
/// <param name="document">Word document to write to (at the
/// current position of the cursor).</param>
public void Run()
{
if (!CanRun)
{
if (Settings == null)
throw new InvalidOperationException("No settings data to work with.");
if ((Laboratory == null) || Laboratory.TimePoints.Count == 0)
throw new NoLaboratoryDataException("No laboratory data to format.");
throw new InvalidOperationException("Cannot run formatter.");
}
// Create undo record and styles prior to iterating over the elements
// because a column switching element might trigger output to the
// document.
Helpers.StartUndo("Laborformatierung");
CreateStyles();
int current = 0;
while (current < Settings.Elements.Count)
{
// If there are FormatElements in the first level of the
// elements list, collect all consecutive ones and process
// them for each individual time point.
if (Settings.Elements[current] is FormatElementBase)
{
int notAFormatElement = CollectFormatElements(current);
IList<FormatElementBase> list = Settings.Elements
.Skip(current)
.Take(notAFormatElement - current)
.Cast<FormatElementBase>().ToList();
ProcessAllTimePoints(list);
current = notAFormatElement;
}
else
{
// The current element is not derived from FormatElementBase;
// so go ahead and 'run' it.
Settings.Elements[current].Run(this);
current++;
}
}
_secondaryBuffer.Flush();
Helpers.EndUndo();
}
/// <summary>
/// Selects one time point per day in the laboratory.
/// </summary>
public void ProcessEachDay(ControlElementBase controlElement)
{
IEnumerable<DateTime> days = _timePointFormatters.Keys.Select(k => k.Date).Distinct();
foreach (DateTime day in days)
{
ProcessDay(
controlElement,
_timePointFormatters
.Where(kv => kv.Key.Date == day.Date)
.ToDictionary(kv => kv.Key, kv => kv.Value)
);
}
}
/// <summary>
/// Selects all time points for the first day in the
/// laboratory.
/// </summary>
public void ProcessFirstDay(ControlElementBase controlElement)
{
DateTime first = _timePointFormatters.First().Key;
ProcessDay(
controlElement,
_timePointFormatters
.Where(kv => kv.Key.Date == first.Date)
.ToDictionary(kv => kv.Key, kv => kv.Value)
);
}
/// <summary>
/// Selects all time points for the first day in the
/// laboratory.
/// </summary>
public void ProcessLastDay(ControlElementBase controlElement)
{
DateTime last = _timePointFormatters.Last().Key;
ProcessDay(
controlElement,
_timePointFormatters
.Where(kv => kv.Key.Date == last.Date)
.ToDictionary(kv => kv.Key, kv => kv.Value)
);
}
/// <summary>
/// Processes the FormatElementBase children of <paramref name="controlElement"/>
/// for each individual time point.
/// </summary>
/// <param name="controlElement">ControlElementBase descendant whose
/// FormatElementBase children to process.</param>
public void ProcessAllTimePoints(ControlElementBase controlElement)
{
ProcessAllTimePoints(controlElement.Children);
}
/// <summary>
/// Inserts a table with two columns into the document.
/// </summary>
public void InsertTwoColumns()
{
_secondaryBuffer.Flush();
if (Document != null)
{
Range r = Document.ActiveWindow.Selection.Range;
_table = Document.Tables.Add(r, NumRows: 1, NumColumns: 2);
_table.AllowAutoFit = true;
_table.AutoFitBehavior(WdAutoFitBehavior.wdAutoFitWindow);
_table.PreferredWidthType = WdPreferredWidthType.wdPreferredWidthPercent;
_table.PreferredWidth = 100;
_table.Borders.Enable = 0;
}
}
/// <summary>
/// Moves the insertion point to the next column in a layout
/// table.
/// </summary>
public void NextColumn()
{
if (_table == null)
{
throw new InvalidOperationException(
"Kann nicht zur nächsten Spalte wechseln, da bislang keine Tabelle eingefügt wurde.");
}
_secondaryBuffer.Flush();
Document.ActiveWindow.Selection.MoveRight(WdUnits.wdCell);
}
/// <summary>
/// Creates a paragraph and character styles in the document.
/// </summary>
public void CreateStyles()
{
if (Document != null)
{
Logger.Info("CreateStyles");
Style style;
// Don't see a better way to check for the existence of a particular
// paragraph style than by using a try...catch construction.
try
{
style = Document.Styles[Properties.Settings.Default.StyleParagraph];
Logger.Info("CreateStyles: Found paragraph style in document");
}
catch
{
Logger.Info("CreateStyles: Need to create paragraph style");
// Add default paragraph style for laboratory
style = Document.Styles.Add(Properties.Settings.Default.StyleParagraph);
style.Font.Size = 10; // pt
style.Font.Bold = 0;
style.Font.Italic = 0;
style.Font.Underline = 0;
style.ParagraphFormat.SpaceAfter = 0;
style.ParagraphFormat.SpaceBefore = 0;
style.ParagraphFormat.LeftIndent = 36; // pt
style.ParagraphFormat.FirstLineIndent = -36; // pt
style.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphJustify;
}
Bovender.ComHelpers.ReleaseComObject(style);
try
{
style = Document.Styles[Properties.Settings.Default.StyleHeader];
Logger.Info("CreateStyles: Found header style in document");
}
catch
{
Logger.Info("CreateStyles: Need to create header style");
// Add header paragraph style for laboratory
style = Document.Styles.Add(Properties.Settings.Default.StyleHeader);
style.Font.Size = 10; // pt
style.Font.Bold = 1;
style.Font.Italic = 0;
style.Font.Underline = WdUnderline.wdUnderlineSingle;
style.ParagraphFormat.SpaceAfter = 0;
style.ParagraphFormat.SpaceBefore = 12;
style.ParagraphFormat.LeftIndent = 36; // pt
style.ParagraphFormat.FirstLineIndent = -36; // pt
style.ParagraphFormat.Alignment = WdParagraphAlignment.wdAlignParagraphJustify;
style.set_NextParagraphStyle(Document.Styles[Properties.Settings.Default.StyleParagraph]);
}
Bovender.ComHelpers.ReleaseComObject(style);
/*
try
{
style = Document.Styles[Properties.Settings.Default.StyleAbnormal];
}
catch
{
// Add character style for abnormal parameters
style = Document.Styles.Add(
Properties.Settings.Default.StyleAbnormal,
WdStyleType.wdStyleTypeCharacter);
style.Font.Bold = 1;
}
*/
}
}
#endregion
#region Protected methods
/// <summary>
/// Collects all consecutive FormatElements from Settings.Elements.
/// </summary>
/// <returns>Index of the first element that is not a FormatElement.
/// </returns>
protected int CollectFormatElements(int startIndex)
{
int i = startIndex;
while (i < Settings.Elements.Count)
{
if (!(Settings.Elements[i] is FormatElementBase))
{
break;
}
i++;
}
return i;
}
protected void ProcessElements(IList<FormatElementBase> formatElements)
{
if (formatElements != null)
{
foreach (ElementBase element in formatElements)
{
element.Run(this);
}
}
}
protected void ProcessAllTimePoints(IList<FormatElementBase> formatElements)
{
for (int i = 0; i < _timePointFormatters.Count; i++)
{
WorkingTimePoints = _timePointFormatters
.Skip(i)
.Take(1)
.ToDictionary(kv => kv.Key, kv => kv.Value);
ProcessElements(formatElements);
if (_primaryBuffer.HasBufferedText)
{
_primaryBuffer.Prepend(
WorkingTimePoints.First().Value.GetDateAndTimeHeader()
);
}
_primaryBuffer.Flush();
}
}
protected void ProcessDay(
ControlElementBase controlElement,
Dictionary<DateTime, TimePointFormatter> workingTimePoints)
{
if (workingTimePoints == null)
throw new ArgumentNullException("workingTimePoints");
WorkingTimePoints = workingTimePoints;
ProcessElements(controlElement.Children);
if (_primaryBuffer.HasBufferedText)
{
_primaryBuffer.Prepend(
WorkingTimePoints.First().Value.GetDateHeader());
}
_primaryBuffer.Flush();
}
#endregion
#region Protected properties
/// <summary>
/// Gets the working Word document.
/// </summary>
protected Document Document { get; set; }
#endregion
#region Fields
TimePointFormatterDictionary _timePointFormatters;
Laboratory _laboratory;
DocumentWriter _primaryBuffer;
DocumentWriter _secondaryBuffer;
Table _table;
#endregion
#region Class logger
private static NLog.Logger Logger { get { return _logger.Value; } }
private static readonly Lazy<NLog.Logger> _logger = new Lazy<NLog.Logger>(() => NLog.LogManager.GetCurrentClassLogger());
#endregion
}
}