Index: xword/ContentFiltering/Html/CSSUtil.cs
===================================================================
--- xword/ContentFiltering/Html/CSSUtil.cs (revision 22628)
+++ xword/ContentFiltering/Html/CSSUtil.cs (working copy)
@@ -19,9 +19,9 @@
/// A reference to an XmlDocument
.
public static void InlineCSS(ref XmlDocument xmlDoc)
{
- XmlNodeList styleNodes = xmlDoc.GetElementsByTagName("style");
+
XmlNodeList allElements = xmlDoc.GetElementsByTagName("*");
- Hashtable identifiedCSSClassesAndIDs = ExtractCSSClassesAndIDs(styleNodes);
+ Hashtable identifiedCSSClassesAndIDs = ExtractCSSClassesAndIDs(ref xmlDoc);
foreach (XmlNode element in allElements)
{
@@ -30,19 +30,32 @@
if (classAttribute != null)
{
- string cssClassName = classAttribute.Value;
+ char[] whiteSpace = { ' ' };
+ string[] cssClassNames = ("" + classAttribute.Value).Split(whiteSpace, StringSplitOptions.RemoveEmptyEntries);
- if (identifiedCSSClassesAndIDs.ContainsKey(cssClassName))
+ foreach (string cssClassName in cssClassNames)
{
- XmlAttribute styleAttribute = null;
- styleAttribute = element.Attributes["style"];
- if (styleAttribute == null)
+ if (identifiedCSSClassesAndIDs.ContainsKey(cssClassName))
{
- styleAttribute = element.Attributes.Append(xmlDoc.CreateAttribute("style"));
- styleAttribute.Value = "";
+ XmlAttribute styleAttribute = null;
+ styleAttribute = element.Attributes["style"];
+ if (styleAttribute == null)
+ {
+ styleAttribute = element.Attributes.Append(xmlDoc.CreateAttribute("style"));
+ styleAttribute.Value = "";
+ }
+ styleAttribute.Value += identifiedCSSClassesAndIDs[cssClassName];
+
+ //remove inlined CSS class
+ classAttribute.Value = classAttribute.Value.Replace(cssClassName, "").Trim();
+
+ if (classAttribute.Value.Length < 1)
+ {
+ element.Attributes.Remove(classAttribute);
+ }
}
- styleAttribute.Value += identifiedCSSClassesAndIDs[cssClassName];
}
+
}
if (idAttribute != null)
@@ -64,12 +77,17 @@
}
///
- /// Extracts the CSS classes and ids from the 'style' nodes.
+ /// Extracts and removes the CSS classes and ids from the 'style' nodes.
+ /// Deletes all the style nodes and creates a new one with unparsed CSS.
///
- /// A list of style nodes from the document.
+ /// A reference to an XmlDocument
.
/// A hashtable with CSS classes names (and CSS ids names) and their properties.
- private static Hashtable ExtractCSSClassesAndIDs(XmlNodeList styleNodes)
+ private static Hashtable ExtractCSSClassesAndIDs(ref XmlDocument xmlDoc)
{
+ XmlNodeList styleNodes = xmlDoc.GetElementsByTagName("style");
+ StringBuilder preservedCSS = new StringBuilder();
+
+ //extract CSS classes and ids
Hashtable identifiedCSSClassesAndIDs = new Hashtable();
foreach (XmlNode styleNode in styleNodes)
{
@@ -90,19 +108,25 @@
//clean whitespaces (spaces, tabs, new lines) from CSS properites
Regex whiteSpaceRegex = new Regex("\\s+", RegexOptions.Singleline | RegexOptions.Multiline);
- properties = whiteSpaceRegex.Replace(properties, "");
+ properties = whiteSpaceRegex.Replace(properties, " ");
char[] comma = { ',' };
string[] cssNames = cssClass.Substring(0, firstBrace).Split(comma);
foreach (string className in cssNames)
{
- string cname=className.Trim();
- //only if it's a CSS class name or CSS id, and does not have pseudoselectors
- if ((cname.IndexOf('.') == 0 || cname.IndexOf('#') == 0) && cname.IndexOf(':')<0)
+ string cname = className.Trim();
+ //only if it's a CSS class name or CSS id
+ //and it's not cascaded CSS
+ //and does not have pseudoselectors
+ if ((cname.IndexOf('.') == 0 || cname.IndexOf('#') == 0) && cname.IndexOf(':') < 0 && cname.IndexOf(' ') < 0)
{
//do not include the dot in the CSS class name or the pound in CSS id
classesNames.Add(cname.Substring(1));
}
+ else //since we can not handle that CSS, preserve it
+ {
+ preservedCSS.Append(cssClass).Append("}").Append(Environment.NewLine);
+ }
}
foreach (string identifiedClassName in classesNames)
@@ -112,13 +136,32 @@
{
currentProperties = identifiedCSSClassesAndIDs[identifiedClassName].ToString();
}
-
+
currentProperties += properties;
identifiedCSSClassesAndIDs.Remove(identifiedClassName);
identifiedCSSClassesAndIDs.Add(identifiedClassName, currentProperties);
}
}
}
+ //remove style nodes
+ List styleNodesToRemove = new List();
+ foreach (XmlNode styleNode in styleNodes)
+ {
+ styleNodesToRemove.Add(styleNode);
+ }
+ foreach (XmlNode styleNodeToRemove in styleNodesToRemove)
+ {
+ styleNodeToRemove.ParentNode.RemoveChild(styleNodeToRemove);
+ }
+
+ //only one style node, with the preserved CSS
+ if (preservedCSS.ToString().Length > 0)
+ {
+ XmlNode remainingStyleNode = xmlDoc.CreateElement("style");
+ remainingStyleNode.InnerText = preservedCSS.ToString().Trim();
+ xmlDoc.GetElementsByTagName("head")[0].AppendChild(remainingStyleNode);
+ }
+
return identifiedCSSClassesAndIDs;
}
@@ -338,6 +381,6 @@
"text-shadow", "top", "vertical-align", "visibility", "white-space", "width",
"word-break", "word-spacing", "z-index"
};
-
+
}
}
Index: xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs
===================================================================
--- xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs (revision 22628)
+++ xword/ContentFiltering/Office/Word/Filters/LocalToWebStyleFilter.cs (working copy)
@@ -45,7 +45,7 @@
//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
+ //step2: convert all inlined style to CSS classes
//(including, but not limited to, those generated at step1)
body = CSSUtil.ConvertInlineStylesToCssClasses(body, ref xmlDoc, ref counter, ref cssClasses);
@@ -66,8 +66,18 @@
/// A reference to the XmlDocument
.
private void InsertCssClassesInHeader(ref XmlNode headNode, ref XmlDocument xmlDoc)
{
- XmlNode styleNode = xmlDoc.CreateNode(XmlNodeType.Element, "style", xmlDoc.NamespaceURI);
+ XmlNode styleNode = null;
+ if (xmlDoc.GetElementsByTagName("style") != null)
+ {
+ styleNode = xmlDoc.GetElementsByTagName("style")[0];
+ }
+ if (styleNode == null)
+ {
+ styleNode = xmlDoc.CreateNode(XmlNodeType.Element, "style", xmlDoc.NamespaceURI);
+ headNode.AppendChild(styleNode);
+ }
+
string value = "";
foreach (Object key in cssClasses.Keys)
Index: xword/ContentFiltering/StyleSheetExtensions/SSXManager.cs
===================================================================
--- xword/ContentFiltering/StyleSheetExtensions/SSXManager.cs (revision 22628)
+++ xword/ContentFiltering/StyleSheetExtensions/SSXManager.cs (working copy)
@@ -129,7 +129,7 @@
///
/// Adds to server SSX objects for the current page.
///
- public void AddStyleSheetExtensions()
+ public void UploadStyleSheetExtensions()
{
IXWikiClient client = pageConverter.XWikiClient;
int i = 0;
Index: xword/ContentFiltering/Test/Office/Word/Filters/LocalToWebStyleFilterTest.cs
===================================================================
--- xword/ContentFiltering/Test/Office/Word/Filters/LocalToWebStyleFilterTest.cs (revision 22628)
+++ xword/ContentFiltering/Test/Office/Word/Filters/LocalToWebStyleFilterTest.cs (working copy)
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
using NUnit.Framework;
using System.Xml;
using ContentFiltering.Test.Util;
@@ -20,6 +19,7 @@
private string initialHTML;
private string expectedHTML;
private XmlDocument initialXmlDoc;
+ private XmlDocument expectedXmlDoc;
///
/// Default constructor.
@@ -30,6 +30,7 @@
initialHTML = "";
expectedHTML = "";
initialXmlDoc = new XmlDocument();
+ expectedXmlDoc = new XmlDocument();
}
///
@@ -38,32 +39,50 @@
[TestFixtureSetUp]
public void TestSetup()
{
- initialHTML = ""
- + "Some content
"
+ initialHTML = ""
+ + ""
+ + ""
+ + ""
+ + "Verdana content
"
+ "some code
"
- + "More content
"
+ + "More verdana content
"
+ "more code
"
+ + " errors text
"
+ "";
+ initialXmlDoc.LoadXml(initialHTML);
- expectedHTML = ""
+ expectedHTML = ""
+ + ""
+ ""
- + "Some content
"
+ + "Verdana content
"
+ "some code
"
- + "More content
"
+ + "More verdana content
"
+ "more code
"
+ + "errors text
"
+ "";
- initialXmlDoc.LoadXml(initialHTML);
+ expectedXmlDoc.LoadXml(expectedHTML);
}
///
/// Tests the LocalToWebStyle filter:
- /// - No inline styles.
- /// - Only 'xoffice[0-9]+' CSS classes.
- /// - Exactly 4 'xoffice[0-9]+' CSS classes, grouped in 2 parts (optimized).
+ /// Only one 'style' node;
+ /// No inline styles;
+ /// Only 'xoffice[0-9]+' CSS classes.
///
[Test]
public void TestLocalToWebStyleFilter()
@@ -73,6 +92,9 @@
new LocalToWebStyleFilter(manager).Filter(ref initialXmlDoc);
+ initialXmlDoc.Normalize();
+ expectedXmlDoc.Normalize();
+
XmlNodeList allNodes = initialXmlDoc.GetElementsByTagName("*");
foreach (XmlNode node in allNodes)
@@ -101,16 +123,30 @@
}
}
+ Assert.IsFalse(foundInlineStyles);
+ Assert.IsFalse(foundNonXOfficeClasses);
+
+ XmlNodeList totalStyleNodes = initialXmlDoc.GetElementsByTagName("style");
+ Assert.IsNotNull(totalStyleNodes);
+ Assert.IsTrue(totalStyleNodes.Count == 1);
+
XmlNode styleNode = initialXmlDoc.GetElementsByTagName("style")[0];
+ Assert.IsNotNull(styleNode);
+
string cssContent = ExtractStyleContent(styleNode);
- Assert.IsFalse(foundInlineStyles);
- Assert.IsFalse(foundNonXOfficeClasses);
- Assert.IsNotNull(styleNode);
- Assert.IsTrue(CountCSSClasses(cssContent) == 4);
- Assert.IsTrue(OptimizedCSSClasses(cssContent));
+ int cssClassesCount = CountCSSClasses(cssContent);
+
+ Assert.IsTrue(cssClassesCount == 5);
+ //Assert.IsTrue(OptimizedCSSClasses(cssContent));
+ Assert.IsTrue(XmlDocComparator.AreIdentical(initialXmlDoc, expectedXmlDoc));
}
+ ///
+ /// Extracts the CSS content from a style node.
+ ///
+ /// A style XmlNode
.
+ /// The string representing the CSS content.
private string ExtractStyleContent(XmlNode styleNode)
{
string cssContent = "";
@@ -125,27 +161,30 @@
}
///
- /// Counts the CSS classes from a CSS content.
+ /// Counts the 'xoffice' CSS classes from a CSS content.
///
/// The CSS content.
- /// Number of CSS classes found.
+ /// Number of 'xoffice' CSS classes found.
private int CountCSSClasses(string cssContent)
{
int count = 0;
int startIndex = 0;
- while (startIndex != -1)
+ do
{
- while (startIndex >= 0)
+ startIndex = cssContent.IndexOf(".xoffice", startIndex);
+ //if found, search from the next position
+ if (startIndex >= 0)
{
- startIndex = cssContent.IndexOf(".xoffice", startIndex);
+ startIndex++;
count++;
}
- }
+
+ } while (startIndex >= 0);
return count;
}
///
- /// Verifies the CSS content four classes grouped in 2 parts.
+ /// Verifies the CSS content has five classes grouped in 3 parts.
///
/// The CSS content.
/// TRUE if CSS seems to be optimized.
@@ -154,9 +193,9 @@
bool foundOptimizedCSS = false;
char[] separator = new char[] { '}' };
string[] groups = cssContent.Split(separator, StringSplitOptions.RemoveEmptyEntries);
- if (groups.Length == 2)
+ if (groups.Length == 3)
{
- foundOptimizedCSS = (CountCSSClasses(groups[0]) == 2) && (CountCSSClasses(groups[1]) == 2);
+ foundOptimizedCSS = (CountCSSClasses(groups[0]) + CountCSSClasses(groups[1]) + CountCSSClasses(groups[2])) == 5;
}
return foundOptimizedCSS;
}
Index: xword/ContentFiltering/Test/Office/Word/Filters/WebToLocalStyleFilterTest.cs
===================================================================
--- xword/ContentFiltering/Test/Office/Word/Filters/WebToLocalStyleFilterTest.cs (revision 22628)
+++ xword/ContentFiltering/Test/Office/Word/Filters/WebToLocalStyleFilterTest.cs (working copy)
@@ -51,19 +51,11 @@
+ "";
- expectedHTML = "TITLE"
-
- //the 'style' node should be inserted in the head section
- + ""
+ expectedHTML = "TITLE"
+ ""
-
- //the CSS should be inlined
- + "Text0
"
+
+ //the CSS should be inlined, the classes for inlined CSS should be removed
+ + "Text0
"
+ "Text1
"
+ ""
Index: xword/ContentFiltering/Test/Util/XmlDocComparator.cs
===================================================================
--- xword/ContentFiltering/Test/Util/XmlDocComparator.cs (revision 22628)
+++ xword/ContentFiltering/Test/Util/XmlDocComparator.cs (working copy)
@@ -4,6 +4,7 @@
using System.Text;
using System.Xml;
using System.Collections;
+using System.Text.RegularExpressions;
namespace ContentFiltering.Test.Util
{
@@ -14,12 +15,16 @@
/// Returns TRUE if the xml dcouments have the same nodes, in the same position with the exact attributes.
///
/// True if the xml dcouments have the same nodes, in the same position with the exact attributes.
- public static bool AreIdentical(XmlDocument xmlDoc1,XmlDocument xmlDoc2)
+ public static bool AreIdentical(XmlDocument xmlDoc1, XmlDocument xmlDoc2)
{
+ //normalize the documents to avoid adjacent XmlText nodes.
+ xmlDoc1.Normalize();
+ xmlDoc2.Normalize();
+
XmlNodeList nodeList1 = xmlDoc1.ChildNodes;
XmlNodeList nodeList2 = xmlDoc2.ChildNodes;
bool same = true;
-
+
if (nodeList1.Count != nodeList2.Count)
{
return false;
@@ -37,7 +42,7 @@
private static bool CompareNodes(XmlNode node1, XmlNode node2)
{
//compare properties
- if (node1.Attributes == null||node2.Attributes==null)
+ if (node1.Attributes == null || node2.Attributes == null)
{
bool nullAttributes = (node1.Attributes == null && node2.Attributes == null);
if (!nullAttributes)
@@ -73,7 +78,17 @@
return false;
}
- if ((""+node1.Value).Trim() != (""+node2.Value).Trim())
+ //the content may have extra spaces or new lines
+
+ string value1 = ("" + node1.Value).Trim().Replace(Environment.NewLine, "");
+ string value2 = ("" + node2.Value).Trim().Replace(Environment.NewLine, "");
+
+ //replace consecutive whitespaces with one space
+ Regex whiteSpaces = new Regex("\\s+", RegexOptions.Singleline | RegexOptions.Multiline);
+ value1 = whiteSpaces.Replace(value1, " ");
+ value2 = whiteSpaces.Replace(value2, " ");
+
+ if (value1 != value2)
{
Console.WriteLine("Nodes value: " + node1.Value + "!=" + node2.Value);
return false;
Index: xword/XWord/AddinActions.cs
===================================================================
--- xword/XWord/AddinActions.cs (revision 22628)
+++ xword/XWord/AddinActions.cs (working copy)
@@ -18,6 +18,7 @@
using XWord.VstoExtensions;
using XWiki.Logging;
using ContentFiltering.Office.Word.Cleaners;
+using ContentFiltering.StyleSheetExtensions;
namespace XWord
{
@@ -358,8 +359,10 @@
/// The full name of the wiki page.
/// The contant to be saved.
/// The wiki syntax of the saved page.
- private void SavePage(String pageName, ref String pageContent, String syntax)
+ /// TRUE if the page was saved successfully.
+ private bool SavePage(String pageName, ref String pageContent, String syntax)
{
+ bool saveSucceeded = false;
SaveGrammarAndSpellingSettings();
DisableGrammarAndSpellingChecking();
@@ -373,9 +376,11 @@
{
Log.Error("Failed to save page " + pageName + "on server " + addin.serverURL);
UserNotifier.Error("There was an error on the server when trying to save the page");
+ saveSucceeded = false;
}
else
{
+ saveSucceeded = true;
//mark the page from wiki structure as published
bool markedDone = false;
foreach (Space sp in addin.wiki.spaces)
@@ -398,6 +403,8 @@
}
RestoreGrammarAndSpellingSettings();
+
+ return saveSucceeded;
}
///
@@ -487,6 +494,9 @@
addin.currentPageFullName, Path.GetFileName(contentFilePath), addin.Client);
}
cleanHTML = pageConverter.ConvertFromWordToWeb(cleanHTML);
+
+ SSXManager ssxManager = SSXManager.BuildFromLocalHTML(pageConverter, cleanHTML);
+
cleanHTML = new BodyContentExtractor().Clean(cleanHTML);
//openHTMLDocument(addin.currentLocalFilePath);
@@ -502,7 +512,11 @@
byte[] wikiContent = null;
wikiContent = Encoding.Convert(Encoding.Unicode, iso, content);
cleanHTML = iso.GetString(wikiContent);
- SavePage(addin.currentPageFullName, ref cleanHTML, addin.AddinStatus.Syntax);
+
+ if (SavePage(addin.currentPageFullName, ref cleanHTML, addin.AddinStatus.Syntax))
+ {
+ ssxManager.UploadStyleSheetExtensions();
+ }
}
catch (COMException ex)
{