PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/CompositeC1/Website/Composite/services/WysiwygEditor/XhtmlTransformations.asmx

#
ASP.NET | 800 lines | 657 code | 143 blank | 0 comment | 100 complexity | 917c03c7e768e083185af60dff30c839 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <%@ WebService Language="C#" Class="Composite.Services.XhtmlTransformations" %>
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Threading;
  8. using System.Web;
  9. using System.Web.Services;
  10. using System.Web.Services.Protocols;
  11. using System.Xml.Linq;
  12. using Composite.C1Console.Security;
  13. using Composite.C1Console.Users;
  14. using Composite.Core;
  15. using Composite.Core.Extensions;
  16. using Composite.Core.Linq;
  17. using Composite.Core.WebClient.Renderings;
  18. using Composite.Data.DynamicTypes;
  19. using Composite.Functions;
  20. using Composite.Core.Logging;
  21. using Composite.Core.ResourceSystem;
  22. using Composite.Core.WebClient;
  23. using Composite.Core.WebClient.Services.WysiwygEditor;
  24. using Composite.Core.Xml;
  25. using Composite.Data;
  26. using Composite.Core.Types;
  27. namespace Composite.Services
  28. {
  29. public class XhtmlTransformationResult
  30. {
  31. public string XhtmlFragment { get; set; }
  32. public string Warnings { get; set; }
  33. }
  34. public class FunctionInfo
  35. {
  36. public string FunctionMarkup { get; set; }
  37. public bool RequireConfiguration { get; set; }
  38. }
  39. [WebService(Namespace = "http://www.composite.net/ns/management")]
  40. [SoapDocumentService(RoutingStyle = SoapServiceRoutingStyle.RequestElement)]
  41. public class XhtmlTransformations : System.Web.Services.WebService
  42. {
  43. private const string _markupWysiwygRepresentationAlt = "\n\n\n\n\n\n "; // like this so IE will make loading images have some width and height
  44. [WebMethod]
  45. public XhtmlTransformationResult TinyContentToStructuredContent(string htmlFragment)
  46. {
  47. try
  48. {
  49. string warnings = "";
  50. string xsltPath = Server.MapPath("..\\..\\transformations\\WysiwygEditor_TinyContentToStructuredContent.xsl");
  51. XDocument structuredResult;
  52. try
  53. {
  54. var xsltParameters = new Dictionary<string, string>
  55. {
  56. {"requesthostname", HttpContext.Current.Request.Url.Host},
  57. {"requestport", HttpContext.Current.Request.Url.Port.ToString()},
  58. {"requestscheme", HttpContext.Current.Request.Url.Scheme},
  59. {"requestapppath", UrlUtils.PublicRootPath}
  60. };
  61. structuredResult = MarkupTransformationServices.RepairXhtmlAndTransform(WrapInnerBody(htmlFragment), xsltPath, xsltParameters, out warnings);
  62. }
  63. catch (Exception ex)
  64. {
  65. throw new InvalidOperationException("Parse failed for \n" + htmlFragment, ex);
  66. }
  67. List<XElement> htmlWysiwygImages = structuredResult
  68. .Descendants(Namespaces.Xhtml + "img")
  69. .Where(e => e.Attribute("data-markup") != null
  70. && e.Attribute("class") != null
  71. && e.Attribute("class").Value.Contains("compositeHtmlWysiwygRepresentation")).ToList();
  72. foreach (var htmlWysiwygImageElement in htmlWysiwygImages)
  73. {
  74. try
  75. {
  76. string html = htmlWysiwygImageElement.Attribute("data-markup").Value;
  77. XElement functionElement = XElement.Parse(html);
  78. if (IsFunctionAloneInParagraph(htmlWysiwygImageElement))
  79. {
  80. htmlWysiwygImageElement.Parent.ReplaceWith(functionElement);
  81. }
  82. else
  83. {
  84. htmlWysiwygImageElement.ReplaceWith(functionElement);
  85. }
  86. }
  87. catch (Exception ex)
  88. {
  89. htmlWysiwygImageElement.ReplaceWith(new XText("HTML PARSE FAILED: " + ex.Message));
  90. }
  91. }
  92. List<XElement> functionImages =
  93. structuredResult
  94. .Descendants()
  95. .Where(e => e.Name.LocalName == "img"
  96. && e.Attribute("data-markup") != null
  97. && e.Attribute("class") != null
  98. && e.Attribute("class").Value.Contains("compositeFunctionWysiwygRepresentation")).ToList();
  99. foreach (var functionImageElement in functionImages)
  100. {
  101. var nextNode = functionImageElement.NextNode;
  102. // Removing "&#160;" symbols that may appear between function call images
  103. if (nextNode != null
  104. && nextNode.NextNode != null
  105. && nextNode is XText
  106. && nextNode.NextNode is XElement
  107. && string.IsNullOrWhiteSpace((nextNode as XText).Value.Replace("&#160;", ""))
  108. && functionImages.Contains(nextNode.NextNode as XElement))
  109. {
  110. nextNode.Remove();
  111. }
  112. // Replacing function call images with function markup
  113. try
  114. {
  115. string functionMarkup = functionImageElement.Attribute("data-markup").Value;
  116. XElement functionElement = XElement.Parse(functionMarkup);
  117. if (IsFunctionAloneInParagraph(functionImageElement))
  118. {
  119. functionImageElement.Parent.ReplaceWith(functionElement);
  120. }
  121. else
  122. {
  123. functionImageElement.ReplaceWith(functionElement);
  124. }
  125. }
  126. catch (Exception ex)
  127. {
  128. functionImageElement.ReplaceWith(new XText("FUNCTION MARKUP PARSE FAILED: " + ex.Message));
  129. }
  130. }
  131. IEnumerable<XElement> dataFieldReferenceImages =
  132. structuredResult.Descendants(Namespaces.Xhtml + "img")
  133. .Where(f => f.Attribute("class") != null
  134. && f.Attribute("class").Value.Contains("compositeFieldReferenceWysiwygRepresentation"));
  135. foreach (var referenceImageElement in dataFieldReferenceImages.ToList())
  136. {
  137. try
  138. {
  139. string[] parts = HttpUtility.UrlDecode(referenceImageElement.Attribute("data-markup").Value).Split('\\');
  140. string typeName = parts[0];
  141. string fieldName = parts[1];
  142. referenceImageElement.ReplaceWith(DynamicTypeMarkupServices.GetReferenceElement(fieldName, typeName));
  143. }
  144. catch (Exception ex)
  145. {
  146. referenceImageElement.ReplaceWith(new XText("FIELD REFERENCE MARKUP PARSE FAILED: " + ex.Message));
  147. }
  148. }
  149. FixTinyMceMalEncodingOfInternationalUrlHostNames(structuredResult);
  150. string bodyInnerXhtml = MarkupTransformationServices.OutputBodyDescendants(structuredResult);
  151. return new XhtmlTransformationResult
  152. {
  153. Warnings = warnings,
  154. XhtmlFragment = FixXhtmlFragment(bodyInnerXhtml)
  155. };
  156. }
  157. catch (Exception ex)
  158. {
  159. LoggingService.LogError("XhtmlTransformation", ex.ToString());
  160. throw;
  161. }
  162. }
  163. private static readonly List<XName> paragraphList = new List<XName>(){
  164. Namespaces.Xhtml + "p",
  165. Namespaces.Xhtml + "h1",
  166. Namespaces.Xhtml + "h2",
  167. Namespaces.Xhtml + "h3",
  168. Namespaces.Xhtml + "h4",
  169. Namespaces.Xhtml + "h5",
  170. Namespaces.Xhtml + "h6"};
  171. private static bool IsFunctionAloneInParagraph(XElement element)
  172. {
  173. if (element.ElementsBeforeSelf().Any(d => d.Name != Namespaces.Xhtml + "br")
  174. || element.ElementsAfterSelf().Any(d => d.Name != Namespaces.Xhtml + "br")
  175. || !paragraphList.Contains(element.Parent.Name)
  176. || element.Parent.Value.Replace("&#160;", "").Trim() != string.Empty)
  177. return false;
  178. return true;
  179. }
  180. private static string FixXhtmlFragment(string xhtmlFragment)
  181. {
  182. xhtmlFragment = Regex.Replace(xhtmlFragment, @"(\s)\r\n</script>", "$1</script>", RegexOptions.Multiline);
  183. return xhtmlFragment.Replace("\xA0", "&#160;").Replace("&nbsp;", "&#160;");
  184. }
  185. // Fixing issue where tiny
  186. private void FixTinyMceMalEncodingOfInternationalUrlHostNames(XDocument xhtmlDoc)
  187. {
  188. var urlAttributes = xhtmlDoc.Descendants().Attributes().Where(f => f.Value.StartsWith("http://") || f.Value.StartsWith("https://"));
  189. foreach (XAttribute urlAttribute in urlAttributes)
  190. {
  191. string url = urlAttribute.Value;
  192. string urlWithoutProtocol = url.Substring(url.IndexOf("//", StringComparison.Ordinal) + 2);
  193. string urlHostWithPort = (urlWithoutProtocol.Contains("/") ? urlWithoutProtocol.Substring(0, urlWithoutProtocol.IndexOf('/')) : urlWithoutProtocol);
  194. string urlHost = (urlHostWithPort.Contains(":") ? urlHostWithPort.Substring(0, urlHostWithPort.IndexOf(':')) : urlHostWithPort);
  195. if (urlHost != HttpUtility.UrlDecode(urlHost))
  196. {
  197. urlAttribute.Value = urlAttribute.Value.Replace(urlHost, HttpUtility.UrlDecode(urlHost));
  198. }
  199. }
  200. }
  201. [WebMethod]
  202. public XhtmlTransformationResult StructuredContentToTinyContent(string htmlFragment)
  203. {
  204. return StructuredContentToTinyContentMultiTemplate(htmlFragment, Guid.Empty, Guid.Empty, null, 0);
  205. }
  206. [WebMethod]
  207. public XhtmlTransformationResult StructuredContentToTinyContentMultiTemplate(string htmlFragment, Guid pageId, Guid pageTemplateId, string functionPreviewPlaceholderName, int width)
  208. {
  209. try
  210. {
  211. string warnings = "";
  212. string XhtmlPassXsltPath = Server.MapPath("..\\..\\transformations\\WysiwygEditor_StructuredContentToTinyContent.xsl");
  213. string html = WrapInnerBody(htmlFragment);
  214. XDocument xml = XDocument.Parse(html, LoadOptions.PreserveWhitespace);
  215. IEnumerable<XElement> functionRoots = xml
  216. .Descendants(Namespaces.Function10 + "function")
  217. .Where(f => f.Ancestors(Namespaces.Function10 + "function").Any() == false);
  218. foreach (var functionElement in functionRoots.ToList())
  219. {
  220. string cssSelector = BuildAncestorCssSelector(functionElement);
  221. functionElement.ReplaceWith(GetImageTagForFunctionCall(functionElement, pageId, pageTemplateId, functionPreviewPlaceholderName, cssSelector, width));
  222. }
  223. IEnumerable<XElement> dataFieldReferences = xml.Descendants(Namespaces.DynamicData10 + "fieldreference");
  224. foreach (var referenceElement in dataFieldReferences.ToList())
  225. {
  226. referenceElement.ReplaceWith(GetImageTagForDynamicDataFieldReference(referenceElement));
  227. }
  228. var unHandledHtmlElementNames = new List<XName>
  229. {
  230. Namespaces.Xhtml + "audio",
  231. Namespaces.Xhtml + "canvas",
  232. Namespaces.Xhtml + "embed",
  233. Namespaces.Xhtml + "iframe",
  234. Namespaces.Xhtml + "map",
  235. Namespaces.Xhtml + "object",
  236. Namespaces.Xhtml + "script",
  237. Namespaces.Xhtml + "noscript",
  238. Namespaces.Xhtml + "video"
  239. };
  240. IEnumerable<XElement> langElements = xml.Descendants().Where(f => f.Name.Namespace == Namespaces.Localization10);
  241. foreach (var langElement in langElements.ToList())
  242. {
  243. langElement.ReplaceWith(GetImageTagForLangElement(langElement));
  244. }
  245. IEnumerable<XElement> unHandledHtmlElements = xml.Descendants().Where(f => f.Name.Namespace != Namespaces.Xhtml || unHandledHtmlElementNames.Contains(f.Name));
  246. foreach (var unHandledHtmlElement in unHandledHtmlElements.ToList())
  247. {
  248. unHandledHtmlElement.ReplaceWith(GetImageTagForHtmlElement(unHandledHtmlElement));
  249. }
  250. var xsltParameters = new Dictionary<string, string>
  251. {
  252. {"requestapppath", UrlUtils.PublicRootPath}
  253. };
  254. XDocument structuredResult = MarkupTransformationServices.RepairXhtmlAndTransform(xml.ToString(), XhtmlPassXsltPath, xsltParameters, out warnings);
  255. string bodyInnerXhtml = MarkupTransformationServices.OutputBodyDescendants(structuredResult);
  256. return new XhtmlTransformationResult
  257. {
  258. Warnings = warnings,
  259. XhtmlFragment = FixXhtmlFragment(bodyInnerXhtml)
  260. };
  261. }
  262. catch (Exception ex)
  263. {
  264. Log.LogError("XhtmlTransformation", ex.ToString());
  265. throw;
  266. }
  267. }
  268. private string BuildAncestorCssSelector(XElement element)
  269. {
  270. var sb = new StringBuilder();
  271. foreach (var ancestor in element.Ancestors().Reverse())
  272. {
  273. if(ancestor.Name.LocalName == "html" || ancestor.Name.LocalName == "body") continue;
  274. if (sb.Length > 0)
  275. {
  276. sb.Append(" ");
  277. }
  278. sb.Append(ancestor.Name.LocalName);
  279. string cssClasses = (string) ancestor.Attribute("class") ?? "";
  280. foreach (var cssClass in cssClasses.Split(new [] {" "}, StringSplitOptions.RemoveEmptyEntries))
  281. {
  282. sb.Append(".").Append(cssClass);
  283. }
  284. }
  285. return sb.ToString();
  286. }
  287. [WebMethod]
  288. public string GetImageTagForFunctionCall(string functionMarkup)
  289. {
  290. return GetImageTagForFunctionCall2(functionMarkup, Guid.Empty, Guid.Empty, null, 0);
  291. }
  292. [WebMethod]
  293. public string GetImageTagForFunctionCall2(string functionMarkup, Guid functionPreviewPageId, Guid functionPreviewTemplateId, string functionPreviewPlaceholderName, int width)
  294. {
  295. XElement functionElement;
  296. try
  297. {
  298. functionElement = XElement.Parse(functionMarkup);
  299. }
  300. catch (Exception ex)
  301. {
  302. throw new ArgumentException("Unable to parse functionMarkup as XML", ex);
  303. }
  304. return GetImageTagForFunctionCall(functionElement, functionPreviewPageId, functionPreviewTemplateId, functionPreviewPlaceholderName, null, width)
  305. .ToString(SaveOptions.DisableFormatting);
  306. }
  307. [WebMethod]
  308. public FunctionInfo GetFunctionInfo(string functionName)
  309. {
  310. IFunction function = FunctionFacade.GetFunction(functionName);
  311. var functionRuntimeTreeNode = new FunctionRuntimeTreeNode(function);
  312. return new FunctionInfo
  313. {
  314. FunctionMarkup = functionRuntimeTreeNode.Serialize().ToString(),
  315. RequireConfiguration = function.ParameterProfiles.Any(p => !p.IsInjectedValue)
  316. };
  317. }
  318. private XElement GetImageTagForDynamicDataFieldReference(XElement fieldReferenceElement)
  319. {
  320. string typeName = fieldReferenceElement.Attribute("typemanagername").Value;
  321. Type type = TypeManager.GetType(typeName);
  322. if (!typeof(IData).IsAssignableFrom(type))
  323. {
  324. string fieldName = fieldReferenceElement.Attribute("fieldname").Value;
  325. return GetImageTagForDynamicDataFieldReference(fieldName, fieldName, type.AssemblyQualifiedName, type.AssemblyQualifiedName);
  326. }
  327. DataTypeDescriptor typeDescriptor;
  328. DataFieldDescriptor fieldDescriptor;
  329. if (!DynamicTypeMarkupServices.TryGetDescriptors(fieldReferenceElement, out typeDescriptor, out fieldDescriptor))
  330. {
  331. return null;
  332. }
  333. return GetImageTagForDynamicDataFieldReference(fieldDescriptor, typeDescriptor);
  334. }
  335. private XElement GetImageTagForHtmlElement(XElement element)
  336. {
  337. string description = element.ToString().Replace(" xmlns=\"http://www.w3.org/1999/xhtml\"", "");
  338. string title = "HTML block";
  339. var descriptionLines = description.Split('\n');
  340. if (descriptionLines.Count() > 6)
  341. {
  342. description = string.Format("{0}\n{1}\n{2}\n...\n{3}", descriptionLines[0], descriptionLines[1],
  343. descriptionLines[2], descriptionLines.Last());
  344. }
  345. string imageUrl = GetFunctionBoxImageUrl("html", title, description);
  346. return new XElement(Namespaces.Xhtml + "img",
  347. new XAttribute("alt", _markupWysiwygRepresentationAlt),
  348. new XAttribute("src", imageUrl),
  349. new XAttribute("class", "compositeHtmlWysiwygRepresentation"),
  350. new XAttribute("data-markup", element.ToString())
  351. );
  352. }
  353. /*
  354. <lang:switch xmlns:lang="http://www.composite.net/ns/localization/1.0">
  355. <lang:when culture="da-DK">DK<img src="/dk-logo.png" title="Dansk logo" /></lang:when>
  356. <lang:when culture="en-US">EN<img src="/us-logo.png" title="American logo" /></lang:when>
  357. <lang:default>No logo available</lang:default>
  358. </lang:switch>
  359. */
  360. private XElement GetImageTagForLangElement(XElement element)
  361. {
  362. XName switchName = Namespaces.Localization10 + "switch";
  363. XName stringName = Namespaces.Localization10 + "string";
  364. string title = "Language specific block";
  365. StringBuilder description = new StringBuilder();
  366. try
  367. {
  368. if (element.Name == stringName)
  369. {
  370. description.AppendLine(element.Attribute("key").Value);
  371. }
  372. if (element.Name == switchName)
  373. {
  374. foreach (var option in element.Elements().Where(e => e.Name.Namespace == Namespaces.Localization10))
  375. {
  376. int toGrab = Math.Min(35, option.Value.Length);
  377. string ellipsis = (option.Value.Length > 35 ? "..." : "");
  378. string descriptionContent = string.Format("{0}{1}",
  379. option.Value.Substring(0, toGrab),
  380. ellipsis);
  381. if (String.IsNullOrWhiteSpace(option.Value) && option.Nodes().Any())
  382. {
  383. description.Append("(html code)");
  384. }
  385. switch (option.Name.LocalName)
  386. {
  387. case "when":
  388. description.AppendFormat("{0}: {1}",
  389. option.Attribute("culture").Value,
  390. descriptionContent);
  391. break;
  392. case "default":
  393. description.AppendLine();
  394. description.AppendFormat("Default: {0}",
  395. descriptionContent);
  396. break;
  397. default:
  398. break;
  399. }
  400. description.AppendLine("\n\n\n\n\n\n"); /* wtf - do I need this? */
  401. }
  402. }
  403. }
  404. catch (Exception)
  405. {
  406. description.AppendLine("[ ERROR PARSING MARKUP ]");
  407. }
  408. string imageUrl = GetFunctionBoxImageUrl("html", title, description.ToString());
  409. return new XElement(Namespaces.Xhtml + "img",
  410. new XAttribute("alt", _markupWysiwygRepresentationAlt),
  411. new XAttribute("src", imageUrl),
  412. new XAttribute("class", "compositeHtmlWysiwygRepresentation"),
  413. new XAttribute("data-markup", element.ToString())
  414. );
  415. }
  416. private static string GetFunctionBoxImageUrl(string type, string title, string description)
  417. {
  418. string imageUrl = "~/Renderers/FunctionBox?type={0}&title={1}&description={2}&lang={3}&hash={4}".FormatWith(
  419. HttpUtility.UrlEncode(type, Encoding.UTF8),
  420. HttpUtility.UrlEncode(title, Encoding.UTF8),
  421. UrlUtils.ZipContent(description.Trim()),
  422. Thread.CurrentThread.CurrentUICulture.Name,
  423. FunctionPreview.GetFunctionPreviewHash()); // ZIPping description as it may contain xml tags f.e. <iframe />
  424. return UrlUtils.ResolvePublicUrl(imageUrl);
  425. }
  426. private static string GetFunctionBoxImageUrl_Markup(
  427. string type,
  428. string title,
  429. string description,
  430. string markup,
  431. Guid functionPreviewPageId,
  432. Guid functionPreviewTemplatePageId,
  433. string functionPreviewPlaceholderName,
  434. string functionPreviewCssSelector,
  435. int viewWidth,
  436. bool editable)
  437. {
  438. // TODO: cache ZipContent calls?
  439. string imageUrl = "~/Renderers/FunctionBox?type={0}&title={1}&description={2}&markup={3}&lang={4}&hash={5}".FormatWith(
  440. HttpUtility.UrlEncode(type, Encoding.UTF8),
  441. HttpUtility.UrlEncode(title, Encoding.UTF8),
  442. UrlUtils.ZipContent(description.Trim()), // ZIPping description as it may contain xml tags f.e. <iframe />
  443. UrlUtils.ZipContent(markup.Trim()),
  444. UserSettings.GetCurrentActiveLocaleCultureInfo(UserValidationFacade.GetUsername()),
  445. FunctionPreview.GetFunctionPreviewHash());
  446. if (functionPreviewPageId != Guid.Empty)
  447. {
  448. imageUrl += "&p=" + functionPreviewPageId;
  449. }
  450. if (functionPreviewTemplatePageId != Guid.Empty)
  451. {
  452. imageUrl += "&t=" + functionPreviewTemplatePageId;
  453. }
  454. if (!string.IsNullOrEmpty(functionPreviewPlaceholderName))
  455. {
  456. imageUrl += "&ph=" + functionPreviewPlaceholderName;
  457. }
  458. if (!string.IsNullOrEmpty(functionPreviewCssSelector))
  459. {
  460. imageUrl += "&css=" + functionPreviewCssSelector;
  461. }
  462. if (viewWidth > 0)
  463. {
  464. imageUrl += "&width=" + viewWidth;
  465. }
  466. if (editable)
  467. {
  468. imageUrl += "&editable=true";
  469. }
  470. return UrlUtils.ResolvePublicUrl(imageUrl);
  471. }
  472. private XElement GetImageTagForDynamicDataFieldReference(DataFieldDescriptor dataField, DataTypeDescriptor dataTypeDescriptor)
  473. {
  474. string fieldLabel = dataField.Name;
  475. if (dataField.FormRenderingProfile != null && dataField.FormRenderingProfile.Label != null)
  476. {
  477. fieldLabel = StringResourceSystemFacade.ParseString(dataField.FormRenderingProfile.Label);
  478. }
  479. return GetImageTagForDynamicDataFieldReference(dataField.Name, fieldLabel, dataTypeDescriptor.Name, dataTypeDescriptor.TypeManagerTypeName);
  480. }
  481. private XElement GetImageTagForDynamicDataFieldReference(string fieldName, string fieldLabel, string typeName, string uiFriendlyTypeName)
  482. {
  483. string imageUrl = string.Format("services/WysiwygEditor/FieldImage.ashx?name={0}&groupname={1}",
  484. HttpUtility.UrlEncode(fieldLabel, Encoding.UTF8),
  485. HttpUtility.UrlEncode(typeName, Encoding.UTF8));
  486. return new XElement(Namespaces.Xhtml + "img",
  487. new XAttribute("alt", string.Format("{0}", fieldName)),
  488. new XAttribute("src", Composite.Core.WebClient.UrlUtils.ResolveAdminUrl(imageUrl)),
  489. new XAttribute("class", "compositeFieldReferenceWysiwygRepresentation"),
  490. new XAttribute("data-markup", HttpUtility.UrlEncode(string.Format("{0}\\{1}", uiFriendlyTypeName, fieldName), Encoding.UTF8))
  491. );
  492. }
  493. private XElement GetImageTagForFunctionCall(
  494. XElement functionElement,
  495. Guid pageId,
  496. Guid pageTemplateId,
  497. string functionPreviewPlaceholderName,
  498. string functionPreviewCssSelector,
  499. int viewWidth)
  500. {
  501. string title;
  502. StringBuilder description = new StringBuilder();
  503. string compactMarkup = functionElement.ToString(SaveOptions.DisableFormatting);
  504. bool error = false;
  505. bool hasParameters = false;
  506. try
  507. {
  508. FunctionRuntimeTreeNode functionNode = (FunctionRuntimeTreeNode)FunctionFacade.BuildTree(functionElement);
  509. string functionName = functionNode.GetCompositeName();
  510. title = MakeTitleFromName(functionName);
  511. // description.AppendLine("[{0}]".FormatWith(functionName));
  512. string functionDescription = functionNode.GetDescription();
  513. if (!functionDescription.IsNullOrEmpty())
  514. {
  515. functionDescription = StringResourceSystemFacade.ParseString(functionDescription);
  516. description.AppendLine(functionDescription);
  517. }
  518. var setParams = functionNode.GetSetParameters().ToList();
  519. if (setParams.Any()) description.Append("\n");
  520. ICollection<ParameterProfile> parameterProfiles = FunctionFacade.GetFunction(functionName).ParameterProfiles.Evaluate();
  521. foreach (var setParam in setParams.Take(10))
  522. {
  523. AddParameterInformation(description, setParam, parameterProfiles);
  524. }
  525. hasParameters = parameterProfiles.Any();
  526. if (setParams.Count > 10)
  527. {
  528. description.AppendLine("....");
  529. }
  530. }
  531. catch (Exception ex)
  532. {
  533. // TODO: should be localized?
  534. title = "Invalid function call";
  535. description.AppendLine(ex.Message);
  536. error = true;
  537. }
  538. string functionBoxUrl = error ? GetFunctionBoxImageUrl("warning", title, description.ToString())
  539. : GetFunctionBoxImageUrl_Markup("function", title, description.ToString(), functionElement.ToString(),
  540. pageId,
  541. pageTemplateId,
  542. functionPreviewPlaceholderName,
  543. functionPreviewCssSelector,
  544. viewWidth,
  545. hasParameters);
  546. XElement imagetag = new XElement(Namespaces.Xhtml + "img"
  547. , new XAttribute("alt", _markupWysiwygRepresentationAlt)
  548. , new XAttribute("data-markup", compactMarkup)
  549. , new XAttribute("data-src", functionBoxUrl)
  550. , new XAttribute("onload", "this.className += ' loaded';")
  551. , new XAttribute("class", "compositeFunctionWysiwygRepresentation" + (hasParameters ? " editable" : ""))
  552. );
  553. return imagetag;
  554. }
  555. private void AddParameterInformation(StringBuilder description, BaseParameterRuntimeTreeNode parameter, IEnumerable<ParameterProfile> parameterProfiles)
  556. {
  557. ParameterProfile parameterProfile = parameterProfiles.FirstOrDefault(f => f.Name == parameter.Name);
  558. if (parameter.ContainsNestedFunctions || parameter is FunctionParameterRuntimeTreeNode || parameterProfile.Type.IsLazyGenericType() || parameterProfile.Type.IsAssignableFrom(typeof(XhtmlDocument)))
  559. {
  560. description.AppendLine("{0} = ....".FormatWith(parameter.Name));
  561. return;
  562. }
  563. try
  564. {
  565. object rawValue = parameter.GetValue();
  566. string paramValue = rawValue.ToString();
  567. string paramLabel = parameter.Name;
  568. try
  569. {
  570. if (parameterProfile != null)
  571. {
  572. paramLabel = parameterProfile.LabelLocalized;
  573. if (typeof(IDataReference).IsAssignableFrom(parameterProfile.Type))
  574. {
  575. IDataReference dataReference = ValueTypeConverter.Convert(parameter.GetValue(), parameterProfile.Type) as IDataReference;
  576. if (dataReference != null)
  577. {
  578. paramValue = dataReference.Data.GetLabel();
  579. }
  580. }
  581. else if (parameterProfile.Type == typeof(XhtmlDocument) || parameterProfile.Type == typeof(Lazy<XhtmlDocument>))
  582. {
  583. var serialized = parameter.Serialize();
  584. var textNodes = serialized.DescendantNodes().Where(n => !n.Ancestors().Any(a => a.Name == Namespaces.Xhtml + "head")).OfType<XText>().Where(t=>!t.Value.IsNullOrEmpty());
  585. if (!textNodes.Any())
  586. {
  587. paramValue = "(HTML)";
  588. }
  589. else
  590. {
  591. paramValue = "HTML: " + string.Join(" ", textNodes.Take(5)).Replace( Environment.NewLine, " " ).Trim();
  592. }
  593. }
  594. else if (rawValue is XNode || rawValue is IEnumerable<XNode>)
  595. {
  596. paramValue = "...";
  597. }
  598. }
  599. }
  600. catch (Exception)
  601. {
  602. // just fall back to listing param names and raw values...
  603. paramValue = "[error]";
  604. }
  605. Guid tempGuid;
  606. if (!paramValue.IsNullOrEmpty() && Guid.TryParse(paramValue, out tempGuid))
  607. {
  608. paramValue = "...";
  609. }
  610. if(paramValue.Length > 45)
  611. {
  612. paramValue = paramValue.Substring(0, 42) + "...";
  613. }
  614. description.AppendLine("{0} = {1}".FormatWith(paramLabel, paramValue));
  615. }
  616. catch (Exception)
  617. {
  618. description.AppendLine("{0} = ...".FormatWith(parameter.Name));
  619. }
  620. }
  621. private string WrapInnerBody(string innerBodyMarkup)
  622. {
  623. innerBodyMarkup = XmlUtils.RemoveXmlDeclaration(innerBodyMarkup);
  624. if (innerBodyMarkup.StartsWith("<html") && innerBodyMarkup.Contains(Namespaces.Xhtml.NamespaceName))
  625. {
  626. return innerBodyMarkup;
  627. }
  628. return string.Format("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>None</title></head><body>{0}</body></html>", innerBodyMarkup);
  629. }
  630. private string MakeTitleFromName(string name)
  631. {
  632. string[] nameParts = name.Split('.');
  633. string titleBase = nameParts[nameParts.Length - 1];
  634. StringBuilder sb = new StringBuilder(titleBase.Substring(0, 1).ToUpper());
  635. bool lastWasUpper = true;
  636. for (int i = 1; i < titleBase.Length; i++)
  637. {
  638. string letter = titleBase.Substring(i, 1);
  639. if (letter != letter.ToLowerInvariant())
  640. {
  641. bool nextLetterIsLower = (i < titleBase.Length - 1) && (titleBase.Substring(i + 1, 1).ToLowerInvariant() == titleBase.Substring(i + 1, 1));
  642. if (!lastWasUpper || nextLetterIsLower)
  643. {
  644. sb.Append(" ");
  645. }
  646. lastWasUpper = true;
  647. }
  648. else
  649. {
  650. lastWasUpper = false;
  651. }
  652. sb.Append(letter);
  653. }
  654. return sb.ToString();
  655. }
  656. }
  657. }