1 module vayne.source.compress; 2 3 4 import std.array; 5 import std.regex; 6 import std.string; 7 8 9 enum CompressOptions : uint { 10 none = 0, 11 removeMultiSpaces = 1 << 0, // replace multiple consecutive spaces with a single space 12 removeLineBreaks = 1 << 1, // replace line breaks with a space 13 removeHTMLComments = 1 << 2, // remove any html comments - preserving conditional comments 14 removeTagSpaces = 1 << 3, // remove unnecessary spaces around = in tags i.e. <div id = "foo"> -> <div id="foo"> 15 removeTagQuotes = 1 << 4, // remove unnecessary quotes around attribute values <div id="foo"> -> <div id=foo> 16 removeTagSurroundSpaces = 1 << 5, // remove spaces around some tags i.e <ul> <li> 17 18 defaults = ~0U, 19 } 20 21 22 auto compress(string content, CompressOptions options) { 23 if ((options & CompressOptions.removeLineBreaks) == 0) 24 content = content.replaceAll(linebreaks, "%%~LB~%%"); 25 26 if (options & CompressOptions.removeMultiSpaces) 27 content = content.replaceAll(multispaces, " "); 28 29 if (options & CompressOptions.removeHTMLComments) 30 content = content.replaceAll(htmlComments, ""); 31 32 if (options & CompressOptions.removeTagSpaces) { 33 content = content.replaceAll(tagSpaces, "$1="); 34 35 static string removeEndSpaces(Captures!(string) capture) { 36 // keep space if attribute is unquoted before trailing slash 37 return ((capture[2][0] == '/') && (!matchAll(capture[1], tagSpacesEndLastQuote).empty)) ? (capture[1] ~ " " ~ capture[2]) : (capture[1] ~ capture[2]); 38 } 39 40 content = content.replaceAll!removeEndSpaces(tagSpacesEnd); 41 } 42 43 if (options & CompressOptions.removeTagQuotes) { 44 static string removeQuotes(Captures!(string) capture) { 45 return (capture[3].strip.empty) ? ("=" ~ capture[2]) : format("=%s %s", capture[2], capture[3]); 46 } 47 48 content = content.replaceAll!removeQuotes(tagQuotes); 49 } 50 51 if (options & CompressOptions.removeTagSurroundSpaces) 52 content = content.replaceAll(tagSurround, "$1"); 53 54 if ((options & CompressOptions.removeLineBreaks) == 0) 55 content = content.replace("%%~LB~%%", "\n"); 56 57 return content; 58 } 59 60 61 private __gshared { 62 auto multispaces = regex(`\s+`, "i"); 63 auto linebreaks = regex(`(?:\r\n)|(?:\n)`, "i"); 64 auto htmlComments = regex(`<!---->|<!--[^\[].*?-->`, "i"); 65 auto tagSpaces = regex(`(\s\w+)\s*=\s*(?=[^<]*?>)`, "i"); 66 auto tagSpacesEnd = regex(`(<\w+(?:\s+[a-z0-9-_]+(?:\s*=\s*(?:(?:[a-z0-9-_]+)|(?:"[^"]*")|(?:'[^']*')))?)*)(?:\s+?)(/?>)`, "i"); 67 auto tagSpacesEndLastQuote = regex(`"=\s*[a-z0-9-_]+$"`, "i"); 68 auto tagQuotes = regex(`\s*=\s*(["'])([a-z0-9-_]+?)\1(/?)(?=[^<]*?>)`, "i"); 69 auto tagSurround = regex(`\s*(</?(?:html|head|link|script|style|body|br|p|div|center|dl|form|hr|ol|ul|table|tbody|tr|td|th|tfoot|thead)(?:>|[\s/][^>]*>))\s*`, "i"); 70 }