Index: xword/ContentFiltering/Html/CSSUtil.cs =================================================================== --- xword/ContentFiltering/Html/CSSUtil.cs (revision 22344) +++ xword/ContentFiltering/Html/CSSUtil.cs (working copy) @@ -106,6 +106,73 @@ } + /// + /// Groups CSS selectors (CSS classes and ids) with the same properties to minify the generated CSS content. + /// + public static Hashtable GroupCSSSelectors(Hashtable existingCSSSelectors) + { + Hashtable optimizedCSSSelectors = existingCSSSelectors; + string cssPropsStr; + string[] cssProperties; + string[] separator = new string[1] { ";" }; + List cssPropsList = new List(); + + //sort css properties from each selector in alphabetic order + ICollection cssClassesKeys = optimizedCSSSelectors.Keys; + + //need an extra list in order to alter cssClassesKeys + //(can not alter elements while iterate the collection) + List xofficeCssClasses = new List(); + foreach (string key in cssClassesKeys) + { + xofficeCssClasses.Add(key); + } + + foreach (string key in xofficeCssClasses) + { + cssPropsStr = ((string)optimizedCSSSelectors[key]).Replace('{', ' ').Replace('}', ' ').Trim(); + cssProperties = cssPropsStr.Split(separator, StringSplitOptions.RemoveEmptyEntries); + cssPropsList.Clear(); + foreach (string property in cssProperties) + { + cssPropsList.Add(property.Trim() + ";"); + } + cssPropsList.Sort(); + cssPropsStr = "{"; + foreach (string property in cssPropsList) + { + cssPropsStr += property; + } + cssPropsStr += "}"; + optimizedCSSSelectors[key] = cssPropsStr; + } + + //compare CSS selectors and group redundant ones + //using an inverted hash of the optimizedCSSSelectors + Hashtable invertedHash = new Hashtable(optimizedCSSSelectors.Count); + foreach (string key in optimizedCSSSelectors.Keys) + { + string val = (string)optimizedCSSSelectors[key]; + string cssClass = ""; + if (invertedHash.ContainsKey(val)) + { + cssClass = (string)invertedHash[val]; + cssClass += ", "; + } + cssClass += key; + invertedHash[val] = cssClass; + } + optimizedCSSSelectors.Clear(); + foreach (string properties in invertedHash.Keys) + { + string groupedClasses = (string)invertedHash[properties]; + string props = properties.Replace('{', ' ').Replace('}', ' ').Trim(); + optimizedCSSSelectors.Add(groupedClasses, props); + } + + return optimizedCSSSelectors; + } + } } Index: xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs =================================================================== --- xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs (revision 22344) +++ xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs (working copy) @@ -6,6 +6,7 @@ using System.Xml; using System.Collections; using System.Text.RegularExpressions; +using ContentFiltering.Html; namespace ContentFiltering.Office.Word.Filters { @@ -17,7 +18,7 @@ private int counter = 0; private Hashtable cssClasses; private ConversionManager manager; - + public LocalToWebStyleFilter(ConversionManager manager) { this.manager = manager; @@ -28,7 +29,7 @@ #region IDOMFilter Members /// - /// Extracts the CSS inline styles, optimizes CSS and adds CSS classes in the head section for current page + /// Extracts the inline styles, optimizes CSS and adds CSS classes in the head section for current page /// /// A reference to a XmlDocument instance. public void Filter(ref System.Xml.XmlDocument xmlDoc) @@ -40,20 +41,25 @@ head = xmlDoc.CreateNode(XmlNodeType.Element, "head", xmlDoc.NamespaceURI); body.ParentNode.InsertBefore(head, body); } - ConvertCSSClassesToInlineStyles(ref head, ref body, ref xmlDoc); + + //step1: inline CSS for existing CSS classes and ids, for better manipulation at step2 and step3 + CSSUtil.InlineCSS(ref xmlDoc); + + //step2: convert all inlined CSS to CSS classes + //(including, but not limited to, those generated at step1) body = ConvertInlineStylesToCssClasses(body, ref xmlDoc); - OptimizeCssClasses(); + + //step3: optimize CSS by grouping selectors with the same properties + cssClasses = CSSUtil.GroupCSSSelectors(cssClasses); + InsertCssClassesInHeader(ref head, ref xmlDoc); } #endregion IDOMFilter Members - private void ConvertCSSClassesToInlineStyles(ref XmlNode head, ref XmlNode body, ref XmlDocument xmlDoc) - { - //TODO: XOFFICE-125 - } + /// /// Extracts inline styles and replaces them with CSS classes. /// @@ -168,70 +174,8 @@ return acceptedProperties.ToString(); } - /// - /// Groups CSS classes with the same properties to minify the generated CSS content. - /// - private void OptimizeCssClasses() - { - string cssPropsStr; - string[] cssProperties; - string[] separator = new string[1] { ";" }; - List cssPropsList = new List(); - //sort css properties from each class in alphabetic order - ICollection cssClassesKeys = cssClasses.Keys; - //need an extra list in order to alter cssClassesKeys - //(can not alter elements while iterate the collection) - List xofficeCssClasses = new List(); - foreach (string key in cssClassesKeys) - { - xofficeCssClasses.Add(key); - } - - foreach (string key in xofficeCssClasses) - { - cssPropsStr = ((string)cssClasses[key]).Replace('{', ' ').Replace('}', ' ').Trim(); - cssProperties = cssPropsStr.Split(separator, StringSplitOptions.RemoveEmptyEntries); - cssPropsList.Clear(); - foreach (string property in cssProperties) - { - cssPropsList.Add(property.Trim() + ";"); - } - cssPropsList.Sort(); - cssPropsStr = "{"; - foreach (string property in cssPropsList) - { - cssPropsStr += property; - } - cssPropsStr += "}"; - cssClasses[key] = cssPropsStr; - } - - //compare CSS classes and group redundant ones - //using an inverted hash of the cssClasses - Hashtable invertedHash = new Hashtable(cssClasses.Count); - foreach (string key in cssClasses.Keys) - { - string val = (string)cssClasses[key]; - string cssClass = ""; - if (invertedHash.ContainsKey(val)) - { - cssClass = (string)invertedHash[val]; - cssClass += ", "; - } - cssClass += key; - invertedHash[val] = cssClass; - } - cssClasses.Clear(); - foreach (string properties in invertedHash.Keys) - { - string groupedClasses = (string)invertedHash[properties]; - string props = properties.Replace('{', ' ').Replace('}', ' ').Trim(); - cssClasses.Add(groupedClasses, props); - } - } - private void InsertCssClassesInHeader(ref XmlNode headNode, ref XmlDocument xmlDoc) { XmlNode styleNode = xmlDoc.CreateNode(XmlNodeType.Element, "style", xmlDoc.NamespaceURI); Index: xword/ContentFiltering/Test/Html/CSSUtilTest.cs =================================================================== --- xword/ContentFiltering/Test/Html/CSSUtilTest.cs (revision 22344) +++ xword/ContentFiltering/Test/Html/CSSUtilTest.cs (working copy) @@ -6,6 +6,7 @@ using System.Xml; using ContentFiltering.Html; using ContentFiltering.Test.Util; +using System.Collections; namespace ContentFiltering.Test.Html { @@ -102,7 +103,7 @@ long stopTicks = DateTime.Now.Ticks; //inline operation duration, in miliseconds - double inlineTimeMS = (1.0*stopTicks - startTicks) / 10000; + double inlineTimeMS = (1.0 * stopTicks - startTicks) / 10000; //inline operation should take less than 500 miliseconds @@ -169,6 +170,65 @@ sb.Append("

Text with CSS ID ").Append(i).Append("

"); } return sb.ToString(); - } + } + + /// + /// Tests the GroupCSSSelectors method. + /// + [Test] + public void TestGroupCSSSelectors() + { + Hashtable initialCSSClasses = new Hashtable(); + Hashtable optimizedCSSClasses = new Hashtable(); + + //same initial CSS properties + string props1 = "font-family:sans-serif;font-size:100%;color:red;"; + string props2 = "color:red;font-size:100%;font-family:sans-serif;"; + + //the expected properties (ordered alphabetically) + string expectedProp = "color:red;font-family:sans-serif;font-size:100%"; + + //initial CSS selectors + string[] classes1 = { ".xoffice0", ".xoffice1", ".xoffice2", "textarea" }; + string[] classes2 = { ".xoffice3", ".xoffice4", ".myCssClass", "#one", "p#two" }; + + for (int i = 0; i < classes1.Length; i++) + { + initialCSSClasses.Add(classes1[i], props1); + } + + for (int i = 0; i < classes2.Length; i++) + { + initialCSSClasses.Add(classes2[i], props2); + } + + + optimizedCSSClasses = CSSUtil.GroupCSSSelectors(initialCSSClasses); + + ICollection optimizedKeys = optimizedCSSClasses.Keys; + + //all the selectors should be grouped + Assert.IsTrue(optimizedKeys.Count == 1); + + foreach (string key in optimizedKeys) + { + string val = optimizedCSSClasses[key].ToString(); + + //in the new key there must be all initial CSS selectors + foreach (string cssClass in classes1) + { + Assert.IsTrue(key.IndexOf(cssClass) >= 0); + } + foreach (string cssClass in classes2) + { + Assert.IsTrue(key.IndexOf(cssClass) >= 0); + } + + //expected properties are the same, but ordered alphabetically + Assert.IsTrue(val.IndexOf(expectedProp) >= 0); + } + + } + } }