using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using HtmlAgilityPack;
using OpenXmlPowerTools;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;

namespace InsertEQ
{
    class Program
    {
        static void Main(string[] args)
        {
            var sourceHtmlFi = new FileInfo("../../Original.html");            
                        
            // 使用HtmlAgilityPack将html转换为格式良好的xml
            HtmlDocument hdoc = new HtmlDocument();
            hdoc.Load(sourceHtmlFi.FullName, Encoding.UTF8);
            hdoc.OptionOutputAsXml = true;
            StringBuilder sbXml=new StringBuilder();
            using (StringWriter writer = new StringWriter(sbXml))
            {
                hdoc.Save(writer);
            }

            // 对一些特殊字符进行转义
            sbXml.Replace("&amp;", "&");
            sbXml.Replace("&nbsp;", "\xA0");
            sbXml.Replace("&quot;", "\"");
            sbXml.Replace("&lt;", "~lt;");
            sbXml.Replace("&gt;", "~gt;");
            sbXml.Replace("&#", "~#");
            sbXml.Replace("&", "&amp;");
            sbXml.Replace("~lt;", "&lt;");
            sbXml.Replace("~gt;", "&gt;");
            sbXml.Replace("~#", "&#");

            // 使用HtmlAgilityPack对xml进行进一步处理
            hdoc.LoadHtml(sbXml.ToString());

            HtmlNodeCollection eqs = hdoc.DocumentNode.SelectNodes("//eq");

            // 提取xml中的所有<math></math>标签
            HtmlNodeCollection mathMLs = hdoc.DocumentNode.SelectNodes("//math");
            // 将结果保存在ommls集合中
            List<DocumentFormat.OpenXml.Math.OfficeMath> ommls = new List<DocumentFormat.OpenXml.Math.OfficeMath>();
            if (mathMLs != null)
            {
                // MML2OMML.xsl文件位于目录：%ProgramFiles%\Microsoft Office\Office12\
                XslCompiledTransform xslTransform = new XslCompiledTransform();
                xslTransform.Load("../../MML2OMML.xsl");

                foreach (HtmlNode math in mathMLs)
                {
                    // 将MathML转换为OMML(Office math markup language)
                    using (TextReader tr = new StringReader(math.OuterHtml))
                    {
                        using (XmlReader reader = XmlReader.Create(tr))
                        {
                            using (MemoryStream ms1 = new MemoryStream())
                            {
                                XmlWriterSettings settings = xslTransform.OutputSettings.Clone();
                                settings.ConformanceLevel = ConformanceLevel.Fragment;
                                settings.OmitXmlDeclaration = true;

                                XmlWriter xw = XmlWriter.Create(ms1, settings);

                                xslTransform.Transform(reader, xw);
                                ms1.Seek(0, SeekOrigin.Begin);

                                StreamReader sr = new StreamReader(ms1, Encoding.UTF8);
                                ommls.Add(new DocumentFormat.OpenXml.Math.OfficeMath(sr.ReadToEnd()));
                            }
                        }
                    }
                }

                // 将<math></math>中的内容替换为"<b>////<b>"
                HtmlNode newNode = HtmlNode.CreateNode("<b>////</b>");
                foreach (HtmlNode math in mathMLs)
                {
                    math.ParentNode.ReplaceChild(newNode, math);
                }              

            }

            // 提取xml中的所有<eq></eq>标签            
            if (eqs != null)
            {
                // 将<eq></eq>替换为"<b>/////<b>"
                HtmlNode newNode4Eq = HtmlNode.CreateNode("<b>/////</b>");
                foreach (HtmlNode eq in eqs)
                {
                    eq.ParentNode.ReplaceChild(newNode4Eq, eq);
                }
            }

            // HtmlToWmlConverter要求元素不能有命名空间
            XElement html = (XElement)ConvertToNoNamespace(XElement.Parse(hdoc.DocumentNode.OuterHtml));
            
            // 使用OpenXml PowerTools将html转换为word
            HtmlToWmlConverterSettings c_settings = HtmlToWmlConverter.GetDefaultSettings();
            // 设置包含图片文件的路径
            c_settings.BaseUriForImages = "../../";
            // 默认的字体大小为14pt，即小四号，而中文宋体五号字对应10.5pt
            c_settings.DefaultFontSize = 10.5d;
            //emptyDocument也可以不设置，即在ConvertHtmlToWml方法中的将这个参数设置为null，HtmlToWmlConverter会自动在内部创建一个默认的WmlDocument实例
            WmlDocument emptyDocument = new WmlDocument("../../template.docx");
            WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, "", "", html, c_settings, emptyDocument, null);

            // 将OMML公式插入到转换好的文件中
            using (MemoryStream memoryStream = new MemoryStream())
            {
                doc.WriteByteArray(memoryStream);

                using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(memoryStream, true))
                {
                    List<Text> replaceTexts = new List<Text>();
                    List<Text> replaceEqs = new List<Text>();
                    // 找到<w:b>////</w:b>，这也是我们要插入公式的地方。
                    // 此处用的是OpenXML的扩展方法，你也可以使用LINQ To XML完成同样的任务
                    foreach (var paragraph in wordDoc.MainDocumentPart.Document.Body.Descendants<Paragraph>())
                    {
                        foreach (var run in paragraph.Descendants<Run>())
                        {
                            Text text = run.Elements<Text>().Where(p => p.Text == "////").FirstOrDefault();
                            Text text4Eq = run.Elements<Text>().Where(p => p.Text == "/////").FirstOrDefault();
                            if (text != null)
                                replaceTexts.Add(text);
                            if (text4Eq != null)
                                replaceEqs.Add(text4Eq);

                        }
                    }

                    if (replaceTexts.Count() > 0)
                    {
                        int i = 0;
                        foreach (var replaceText in replaceTexts)
                        {
                            foreach (var currentRun in ommls[i].Descendants<DocumentFormat.OpenXml.Math.Run>())
                            {
                                // 添加字体信息
                                DocumentFormat.OpenXml.Wordprocessing.RunProperties runProperties2 =
                                  new DocumentFormat.OpenXml.Wordprocessing.RunProperties();

                                RunFonts runFonts2 = new RunFonts() { Ascii = "Cambria Math", HighAnsi = "Cambria Math" };
                                runProperties2.Append(runFonts2);

                                currentRun.InsertAt(runProperties2, 0);
                            }
                            replaceText.Parent.ReplaceChild(ommls[i], replaceText);
                            i += 1;
                        }
                    }

                    // 添加域代码公式
                    if (replaceEqs.Count() > 0)
                    {
                        int j = 0;
                        Regex reTriFun = new Regex("sin|cos|tan|cot|ctg|tg");
                        Regex reVar = new Regex("^[a-zA-Z]+|^[\u03B1-\u03C9]+");    // 判断是否英文或小写希腊
                        Regex reSup = new Regex("^\\^\\(", RegexOptions.IgnoreCase);    // 判断开头是否是^((上标)
                        Regex reSub = new Regex("^_\\(", RegexOptions.IgnoreCase);    // 判断开头是否是_((下标)

                        foreach (var replaceEq in replaceEqs)
                        {
                            // 添加域代码公式
                            // Begin
                            Run run1 = new Run();
                            FieldChar fieldChar1 = new FieldChar() { FieldCharType = FieldCharValues.Begin };
                            run1.Append(fieldChar1);
                            replaceEq.Parent.InsertAfterSelf(run1);

                            Run run2 = new Run();
                            FieldCode fieldCode2 = new FieldCode() { Space = SpaceProcessingModeValues.Preserve };
                            fieldCode2.Text = "EQ ";
                            run2.Append(fieldCode2);
                            run1.InsertAfterSelf(run2);

                            // End
                            Run run3 = new Run();
                            FieldChar fieldChar3 = new FieldChar() { FieldCharType = FieldCharValues.End };
                            run3.Append(fieldChar3);
                            run2.InsertAfterSelf(run3);

                            string eq = eqs[j].InnerText;
                            string[] eqElements = eq.Split('#');

                            // Begin和End之间的内容
                            for (int i = 0; i < eqElements.Length; i++)
                            {
                                Run run = new Run();

                                // 上标
                                if (reSup.IsMatch(eqElements[i]))
                                {
                                    RunProperties runProperties = new RunProperties();
                                    VerticalTextAlignment verticalTextAlignment = new VerticalTextAlignment() { Val = VerticalPositionValues.Superscript };
                                    runProperties.Append(verticalTextAlignment);
                                    run.Append(runProperties);

                                    // 去掉前两个字符^(，最后一个字符)
                                    eqElements[i] = eqElements[i].Substring(2, eqElements[i].Length - 3);
                                }
                                // 下标
                                else if (reSub.IsMatch(eqElements[i]))
                                {
                                    RunProperties runProperties = new RunProperties();
                                    VerticalTextAlignment verticalTextAlignment = new VerticalTextAlignment() { Val = VerticalPositionValues.Subscript };
                                    runProperties.Append(verticalTextAlignment);
                                    run.Append(runProperties);

                                    // 去掉前两个字符_(，最后一个字符)
                                    eqElements[i] = eqElements[i].Substring(2, eqElements[i].Length - 3);
                                }
                                // 英文和小写希腊字母，但不包含(sin、cos、……)
                                else if (!reTriFun.IsMatch(eqElements[i]) && reVar.IsMatch(eqElements[i]))
                                {
                                    RunProperties runProperties = new RunProperties();
                                    //RunFonts runFonts = new RunFonts() { Hint = FontTypeHintValues.EastAsia };
                                    //runProperties.Append(runFonts);
                                    Italic italic = new Italic();
                                    runProperties.Append(italic);
                                    run.Append(runProperties);
                                }

                                FieldCode fieldCode = new FieldCode() { Space = SpaceProcessingModeValues.Preserve };
                                fieldCode.Text = eqElements[i];
                                run.Append(fieldCode);

                                run3.InsertBeforeSelf(run);
                            }

                            replaceEq.Parent.Remove();

                            j += 1;
                        }
                    }
                }
                File.WriteAllBytes("../../finalWord.docx", memoryStream.ToArray());
                //wordDoc.SaveAs("../../finalWord.docx");
            }
        }

        private static object ConvertToNoNamespace(XNode node)
        {
            XElement element = node as XElement;
            if (element != null)
            {
                return new XElement(element.Name.LocalName,
                    element.Attributes().Where(a => !a.IsNamespaceDeclaration),
                    element.Nodes().Select(n => ConvertToNoNamespace(n)));
            }
            return node;
        }

        static string defaultCss =
        @"html, address,
blockquote,
body, dd, div,
dl, dt, fieldset, form,
frame, frameset,
h1, h2, h3, h4,
h5, h6, noframes,
ol, p, ul, center,
dir, hr, menu, pre { display: block; unicode-bidi: embed }
li { display: list-item }
head { display: none }
table { display: table; }
tr { display: table-row }
thead { display: table-header-group }
tbody { display: table-row-group }
tfoot { display: table-footer-group }
col { display: table-column }
colgroup { display: table-column-group }
td, th { display: table-cell; }
caption { display: table-caption }
th { font-weight: bolder; text-align: center }
caption { text-align: center }
body { margin: auto; }
h1 { font-size: 2em; margin: auto; }
h2 { font-size: 1.5em; margin: auto; }
h3 { font-size: 1.17em; margin: auto; }
h4, p,
blockquote, ul,
fieldset, form,
ol, dl, dir,
menu { margin: auto }
a { color: blue; }
h5 { font-size: .83em; margin: auto }
h6 { font-size: .75em; margin: auto }
h1, h2, h3, h4,
h5, h6, b,
strong { font-weight: bolder }
blockquote { margin-left: 40px; margin-right: 40px }
i, cite, em,
var, address { font-style: italic }
pre, tt, code,
kbd, samp { font-family: monospace }
pre { white-space: pre }
button, textarea,
input, select { display: inline-block }
big { font-size: 1.17em }
small { font-size: .83em }
sub { vertical-align: sub }
sup { vertical-align: super }
table { border-spacing: 2px; }
thead, tbody,
tfoot { vertical-align: middle }
td, th, tr { vertical-align: inherit }
s, strike, del { text-decoration: line-through }
hr { border: 1px inset }
ol, ul, dir,
menu, dd { margin-left: 40px }
ol { list-style-type: decimal }
ol ul, ul ol,
ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
u, ins { text-decoration: underline }
br:before { content: ""\A""; white-space: pre-line }
center { text-align: center }
:link, :visited { text-decoration: underline }
:focus { outline: thin dotted invert }
/* Begin bidirectionality settings (do not change) */
BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }

";
    }
}