/*******************************************************************************
 * You may amend and distribute as you like, but don't remove this header!
 *
 * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
 * See http://www.codeplex.com/EPPlus for details.
 *
 * Copyright (C) 2011  Jan Källman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU Lesser General Public License for more details.
 *
 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
 *
 * All code and executables are provided "as is" with no warranty either express or implied. 
 * The author accepts no liability for any damage or loss of business that this product may cause.
 *
 * Code change notes:
 * 
 * Author				Change						Date
 * ******************************************************************************
 * Richard Tallent		Initial Release				2012-08-13
 *******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace OfficeOpenXml.Style
{
	public class ExcelRichTextHtmlUtility
	{

		/// <summary>
		/// Provides basic HTML support by converting well-behaved HTML into appropriate RichText blocks.
		/// HTML support is limited, and does not include font colors, sizes, or typefaces at this time,
		/// and also does not support CSS style attributes. It does support line breaks using the BR tag.
		///
		/// This routine parses the HTML into RegEx pairings of an HTML tag and the text until the NEXT 
		/// tag (if any). The tag is parsed to determine the setting change to be applied to the last set
		/// of settings, and if the text is not blank, a new block is added to rich text.
		/// </summary>
		/// <param name="range"></param>
		/// <param name="html">The HTML to parse into RichText</param>
		/// <param name="defaultFontName"></param>
		/// <param name="defaultFontSize"></param>

		public static void SetRichTextFromHtml(ExcelRange range, string html, string defaultFontName, short defaultFontSize)
		{
			// Reset the cell value, just in case there is an existing RichText value.
			range.Value = "";

			// Sanity check for blank values, skips creating Regex objects for performance.
			if (String.IsNullOrEmpty(html))
			{
				range.IsRichText = false;
				return;
			}

			// Change all BR tags to line breaks. http://epplus.codeplex.com/discussions/238692/
			// Cells with line breaks aren't necessarily considered rich text, so this is performed
			// before parsing the HTML tags.
			html = System.Text.RegularExpressions.Regex.Replace(html, @"<br[^>]*>", "\r\n", RegexOptions.Compiled | RegexOptions.IgnoreCase);

			string tag;
			string text;
			ExcelRichText thisrt = null;
			bool isFirst = true;

			// Get all pairs of legitimate tags and the text between them. This loop will
			// only execute if there is at least one start or end tag.
			foreach (Match m in System.Text.RegularExpressions.Regex.Matches(html, @"<(/?[a-z]+)[^<>]*>([\s\S]*?)(?=</?[a-z]+[^<>]*>|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase))
			{
				if (isFirst)
				{
					// On the very first match, set up the initial rich text object with
					// the defaults for the text BEFORE the match.
					range.IsRichText = true;
					thisrt = range.RichText.Add(CleanText(html.Substring(0, m.Index)));	// May be 0-length
					thisrt.Size = defaultFontSize;										// Set the default font size
					thisrt.FontName = defaultFontName;									// Set the default font name
					isFirst = false;
				}
				// Get the tag and the block of text until the NEXT tag or EOS. If there are HTML entities
				// encoded, unencode them, they should be passed to RichText as normal characters (other
				// than non-breaking spaces, which should be replaced with normal spaces, they break Excel.
				tag = m.Groups[1].Captures[0].Value;
				text = CleanText(m.Groups[2].Captures[0].Value);

				if (thisrt.Text == "")
				{
					// The most recent rich text block wasn't *actually* used last time around, so update
					// the text and keep it as the "current" block. This happens with the first block if
					// it starts with a tag, and may happen later if tags come one right after the other.
					thisrt.Text = text;
				}
				else
				{
					// The current rich text block has some text, so create a new one. RichText.Add()
					// automatically applies the settings from the previous block, other than vertical
					// alignment.
					thisrt = range.RichText.Add(text);
				}
				// Override the settings based on the current tag, keep all other settings.
				SetStyleFromTag(tag, thisrt);
			}

			if (thisrt == null)
			{
				// No HTML tags were found, so treat this as a normal text value.
				range.IsRichText = false;
				range.Value = CleanText(html);
			}
			else if (String.IsNullOrEmpty(thisrt.Text))
			{
				// Rich text was found, but the last node contains no text, so remove it. This can happen if,
				// say, the end of the string is an end tag or unsupported tag (common).
				range.RichText.Remove(thisrt);

				// Failsafe -- the HTML may be just tags, no text, in which case there may be no rich text
				// directives that remain. If that is the case, turn off rich text and treat this like a blank
				// cell value.
				if (range.RichText.Count == 0)
				{
					range.IsRichText = false;
					range.Value = "";
				}

			}

		}

		private static void SetStyleFromTag(string tag, ExcelRichText settings)
		{
			switch (tag.ToLower())
			{
				case "b":
				case "strong":
					settings.Bold = true;
					break;
				case "i":
				case "em":
					settings.Italic = true;
					break;
				case "u":
					settings.UnderLine = true;
					break;
				case "s":
				case "strike":
					settings.Strike = true;
					break;
				case "sup":
					settings.VerticalAlign = ExcelVerticalAlignmentFont.Superscript;
					break;
				case "sub":
					settings.VerticalAlign = ExcelVerticalAlignmentFont.Subscript;
					break;
				case "/b":
				case "/strong":
					settings.Bold = false;
					break;
				case "/i":
				case "/em":
					settings.Italic = false;
					break;
				case "/u":
					settings.UnderLine = false;
					break;
				case "/s":
				case "/strike":
					settings.Strike = false;
					break;
				case "/sup":
				case "/sub":
					settings.VerticalAlign = ExcelVerticalAlignmentFont.None;
					break;
				default:
					// unsupported HTML, no style change
					break;
			}
		}

		private static string CleanText(string s)
		{
			// Need to convert HTML entities (named or numbered) into actual Unicode characters
			s = System.Web.HttpUtility.HtmlDecode(s);
			// Remove any non-breaking spaces, kills Excel
			s = s.Replace("\u00A0", " ");
			return s;
		}

	}
}
