Error executing template "Designs/Swift-v2/Paragraph/Swift-v2_ProductLongDescription.cshtml" System.NullReferenceException: Object reference not set to an instance of an object. at CompiledRazorTemplates.Dynamic.RazorEngine_08ebdfdf87da4db28ec8a59814afcf4f.ExecuteAsync() at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader) at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.DynamicWrapperService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.RunCompile(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer) at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter) at RazorEngine.Templating.RazorEngineServiceExtensions.RunCompile(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag) at RazorEngine.Templating.RazorEngineServiceExtensions.RunCompile(IRazorEngineService service, String templateSource, String name, Type modelType, Object model, DynamicViewBag viewBag) at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template) at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template) at Dynamicweb.Rendering.Template.RenderRazorTemplate()
1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using System 3 @using System.Text.RegularExpressions 4 @using Dynamicweb.Ecommerce.ProductCatalog 5 6 @* PDP LONG DESCRIPTION — M4 / TASK-0096. A SHADOW OVERRIDE of vendor's 7 Paragraph/Swift-v2_ProductLongDescription.cshtml, mounted :ro by path. 8 9 Vendor's file is not edited. Shadowing by path is the sanctioned pattern here and this 10 design already does it for six vendor paragraph templates (ProductMedia, ProductHeader, 11 ProductPrice, ProductDefaultImage, ProductListItemRepeater, the page template itself). 12 13 BLAST RADIUS TODAY IS ZERO, measured not assumed: there is no paragraph of this item type 14 anywhere on this box. Nothing renders differently until one is placed. 15 16 ── WHY A SHADOW AND NOT CSS ───────────────────────────────────────────────────────────── 17 One thing only: vendor HAS NO EMPTINESS GATE. Its condition is `@@if (product is object)`, 18 which is true on every product page, so a product with no long description emits 19 20 <div class="h-100 item_..."><div class="mw-75ch" itemprop="description"></div></div> 21 22 an empty block with an itemprop on it. 89 of 378 active ENU products have no long 23 description, so that is not a corner case. The packet asks for the element to be OMITTED, 24 and omission is a template decision -- CSS can hide a block but cannot stop it being 25 emitted, and `:empty` cannot see it anyway because Razor leaves whitespace text nodes 26 inside (the reason mouldshop.css's empty-row rule is written on `:not(:has(> *))`). 27 28 ── EMPTY IS NOT string.IsNullOrWhiteSpace ─────────────────────────────────────────────── 29 LongDescription is rich text. An editor who types a description and deletes it leaves 30 "<p></p>" or "<p> </p>" behind -- non-empty strings that draw a blank band. Emptiness 31 is therefore decided on the VISIBLE characters, after tags and entities are removed. Same 32 rule, and the same reason, as Paragraph/Mouldshop_InfoStripe/Default.cshtml. 33 34 THE TEST STRIPS; THE OUTPUT DOES NOT. Headings, lists and links in the description survive 35 to the page untouched -- that is the packet's item 3, and it is the whole reason the 36 stripping happens in a separate function rather than on the value that gets rendered. 37 38 Note `@@` does not HTML-encode in DW's Razor host, so writing the field raw is vendor's 39 deliberate behaviour for rich text, not an accident inherited here. 40 41 ── WHAT IS KEPT FROM VENDOR, DELIBERATELY ─────────────────────────────────────────────── 42 Title, HideTitle, TitleFontSize and TextReadability all still work exactly as vendor wrote 43 them. They are editor-facing controls and this packet has no mandate to remove any of 44 them: CLAUDE.md's standing rule is that an existing element is not overridden or deleted. 45 46 TextReadability defaults to "max-width-on", which caps the TEXT MEASURE at 75 characters 47 inside an otherwise full-width block. That is vendor's default and good typography, so it 48 is left alone rather than overridden -- and it is REPORTED rather than silently changed, 49 because making a live admin control inert is the defect Q-0148 records. The operator sets 50 it to off if the text should run the full 1288. 51 52 THE TITLE GOES WITH THE DESCRIPTION. When there is nothing to describe, a heading with 53 nothing under it is worse than no section at all -- the same reasoning as the attachments 54 block in the buy box, whose heading and divider live inside its one @@if. *@ 55 56 @functions { 57 // Verbatim strings throughout. A normal literal turns \b into a backspace and rejects \s 58 // outright, and in DW a template compile error is a page-down on first request rather 59 // than a build failure. Recorded in the info stripe's header after it happened there. 60 private static bool MsHasVisibleText(string rich) 61 { 62 if (string.IsNullOrWhiteSpace(rich)) { return false; } 63 var s = Regex.Replace(rich, @"<(script|style)[^>]*>.*?</\1\s*>", " ", 64 RegexOptions.IgnoreCase | RegexOptions.Singleline); 65 s = Regex.Replace(s, @" ?", " ", RegexOptions.IgnoreCase); 66 s = Regex.Replace(s, @"<[^>]*>", " ", RegexOptions.Singleline); 67 s = System.Net.WebUtility.HtmlDecode(s); 68 return s.Trim().Length > 0; 69 } 70 71 // TASK-0115: the same reduction as MsHasVisibleText, returning the visible length, for 72 // the two-column threshold below. Whitespace runs count as one character. 73 private static int MsVisibleLength(string rich) 74 { 75 if (string.IsNullOrWhiteSpace(rich)) { return 0; } 76 var s = Regex.Replace(rich, @"<(script|style)[^>]*>.*?</\1\s*>", " ", 77 RegexOptions.IgnoreCase | RegexOptions.Singleline); 78 s = Regex.Replace(s, @" ?", " ", RegexOptions.IgnoreCase); 79 s = Regex.Replace(s, @"<[^>]*>", " ", RegexOptions.Singleline); 80 s = System.Net.WebUtility.HtmlDecode(s); 81 s = Regex.Replace(s, @"\s+", " "); 82 return s.Trim().Length; 83 } 84 85 // ONE PARAGRAPH OF LINE BREAKS -> PARAGRAPHS (operator, 2026-09-28: the E-BIG TOUR 400EQ 86 // description stayed in the LEFT column with no gaps). Its four variants store the whole 87 // description as ONE <p> whose sections are separated by <br>. The two-column rule keeps a 88 // paragraph whole (break-inside: avoid, mouldshop-pdp-top.css), so a single paragraph can never 89 // reach the second column. Only that exact shape is touched: the whole value is one <p>...</p> 90 // with no other <p> inside and at least one <br>. It becomes one <p> per <br>-separated section, 91 // empty sections dropped; the text, and every tag in it, is untouched. Anything else - proper 92 // paragraphs, lists, headings, text without a <p> - is returned as it came. 93 private static string MsSplitBreakParagraph(string rich) 94 { 95 if (string.IsNullOrWhiteSpace(rich)) { return rich; } 96 var m = Regex.Match(rich, @"^\s*<p>((?:(?!</?p[\s>]).)*)</p>\s*$", 97 RegexOptions.IgnoreCase | RegexOptions.Singleline); 98 if (!m.Success || !Regex.IsMatch(m.Groups[1].Value, @"<br\s*/?>", RegexOptions.IgnoreCase)) { return rich; } 99 var parts = Regex.Split(m.Groups[1].Value, @"\s*<br\s*/?>\s*", RegexOptions.IgnoreCase); 100 var kept = new System.Collections.Generic.List<string>(); 101 foreach (var part in parts) 102 { 103 if (MsHasVisibleText(part)) { kept.Add("<p>" + part.Trim() + "</p>"); } 104 } 105 return kept.Count > 1 ? string.Join("\n", kept) : rich; 106 } 107 } 108 109 @{ 110 ProductViewModel msProduct = Dynamicweb.Context.Current.Items.Contains("ProductDetails") 111 ? Dynamicweb.Context.Current.Items["ProductDetails"] as ProductViewModel 112 : null; 113 114 @* Vendor's visual-editor fixture, kept so the block is still authorable in admin on a 115 page with no product in context. Its text is vendor's own. *@ 116 if (msProduct == null && Pageview.IsVisualEditorMode) 117 { 118 msProduct = new ProductViewModel(); 119 msProduct.LongDescription = Translate("Explore our new arrivals that blend style and sustainability. Made from eco-friendly materials, our products not only enhances your space but also supports a greener planet. Discover designs that make a difference."); 120 } 121 122 bool msHasText = msProduct != null && MsHasVisibleText(msProduct.LongDescription); 123 // TASK-0115: TWO COLUMNS ON DESKTOP, only when there is enough text to fill them. Below 124 // 600 visible characters (~8 lines at the 75ch measure) a second column would hold a line 125 // or two and read as a layout fault, so short descriptions stay one column. The class is 126 // only a hook; the columns themselves exist only >= 992 (mouldshop-pdp-top.css). 127 bool msTwoCols = msHasText && MsVisibleLength(msProduct.LongDescription) >= 600; 128 // Two-column mode only: a one-paragraph-of-<br> description becomes paragraphs, so it can 129 // flow into the second column (MsSplitBreakParagraph). One-column output is unchanged. 130 string msBody = msTwoCols ? MsSplitBreakParagraph(msProduct.LongDescription) : msProduct.LongDescription; 131 132 bool msHideTitle = Model.Item.GetBoolean("HideTitle"); 133 string msTitle = Model.Item.GetString("Title"); 134 string msTitleSize = Model.Item.GetRawValueString("TitleFontSize", "display-4"); 135 136 string msReadability = Model.Item.GetRawValueString("TextReadability", "max-width-on"); 137 string msMeasure = msReadability == "max-width-on" ? "mw-75ch d-inline-block" 138 : msReadability == "max-width-off" ? string.Empty 139 : msReadability; 140 } 141 142 @* id="ms-specification" is the buy box SPECIFICATION button's target (its SpecTargetId 143 default, Mouldshop_ProductBuyBox_Custom). Nothing on the PDP carried it, so the button 144 went nowhere. Operator, 2026-09-25: "on press should scroll page to specification". *@ 145 @if (msHasText) 146 { 147 <div id="ms-specification" class="ms-pdp-longdesc h-100 item_@Model.Item.SystemName.ToLower()" data-ms-pdp-longdesc> 148 @if (!msHideTitle && !string.IsNullOrEmpty(msTitle)) 149 { 150 <div class="ms-pdp-longdesc-head text-start pb-2 pb-lg-4"> 151 <h2 class="@msTitleSize @msMeasure">@msTitle</h2> 152 </div> 153 } 154 <div class="ms-pdp-longdesc-body@(msTwoCols ? " ms-pdp-longdesc-body--cols" : "") @msMeasure" itemprop="description"> 155 @msBody 156 </div> 157 </div> 158 } 159