From a21c9000b495451393cef70bd1e56c88fa45333c Mon Sep 17 00:00:00 2001 From: benbek Date: Tue, 5 Jan 2016 10:09:53 +0200 Subject: [PATCH 1/3] Added option to inject CSS Styles into the generated HTML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Puts all CSS styles into the HTML as “style” attribute. This approach is well suited to environments where one cannot have HTML with an accompanying CSS file. Shortcomings: - Not all CSS rules are convertible to HTML Style attributes. For example, no pseudo-selectors (such as ’:hover’, ‘:before’, ‘:after’) can be rendered. --- js/panel.js | 7 + js/processors/HTMLStylesCombiner.js | 138 ++++++++++++++++++ js/{tools => processors}/SameRulesCombiner.js | 0 panel.html | 9 +- 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 js/processors/HTMLStylesCombiner.js rename js/{tools => processors}/SameRulesCombiner.js (100%) diff --git a/js/panel.js b/js/panel.js index e5bdb75..70f833b 100644 --- a/js/panel.js +++ b/js/panel.js @@ -8,6 +8,7 @@ webkitPropertiesFilter = new WebkitPropertiesFilter(), defaultValueFilter = new DefaultValueFilter(), sameRulesCombiner = new SameRulesCombiner(), + htmlStylesCombiner = new HTMLStylesCombiner(), inspectedContext = new InspectedContext(), loader = $('#loader'), @@ -23,6 +24,7 @@ combineSameRulesInput = $('#combine-same-rules'), fixHTMLIndentationInput = $('#fix-html-indentation'), includeAncestors = $('#include-ancestors'), + combineCssIntoHTML = $('#combine-css-to-html'), idPrefix = $('#id-prefix'), htmlTextarea = $('#html'), @@ -67,6 +69,7 @@ fixHTMLIndentationInput.on('change', persistSettingAndProcessSnapshot); combineSameRulesInput.on('change', persistSettingAndProcessSnapshot); includeAncestors.on('change', persistSettingAndProcessSnapshot); + combineCssIntoHTML.on('change', persistSettingAndProcessSnapshot); createButton.on('click', makeSnapshot); @@ -218,6 +221,10 @@ }); } + if (combineCssIntoHTML.is(':checked')) { + html = htmlStylesCombiner.process(html, styles); + } + styles = cssStringifier.process(styles); if (isValidPrefix(idPrefix.val())) { diff --git a/js/processors/HTMLStylesCombiner.js b/js/processors/HTMLStylesCombiner.js new file mode 100644 index 0000000..81291eb --- /dev/null +++ b/js/processors/HTMLStylesCombiner.js @@ -0,0 +1,138 @@ +/** + * Injects the CSS into the HTML as Style attributes. + * + * @constructor + */ +function HTMLStylesCombiner() { + "use strict"; + var cursor = 0, + stylesMap, + + // constants + ATTRIBUTE_ENCLOSING_CHARACTERS = ['"', "'"], + ID_ATTRIBUTE = "id=", + STYLE_ATTRIBUTE = "style="; + + /** + * Looks for the next 'id' attribute inside a tag. Returns -1 if not found. + */ + function getNextIdAttributePosition(html, lastCursor) { + var currentCursor, + tagStartCursor, + tagEndCursor, + idCursor; + + while (lastCursor >= 0) { + tagStartCursor = html.indexOf("<", lastCursor); + if (tagStartCursor < 0) { + return -1; + } + tagEndCursor = html.indexOf(">", tagStartCursor); + if (tagEndCursor < 0) { + return -1; + } + currentCursor = tagStartCursor; + do { + idCursor = html.indexOf(ID_ATTRIBUTE, currentCursor); + if (idCursor < 0) { + return -1; + } else if (ATTRIBUTE_ENCLOSING_CHARACTERS.indexOf(html.charAt(idCursor + ID_ATTRIBUTE.length)) < 0) { + // Not the right 'id=', look for the next + currentCursor++; + } else if (idCursor < tagEndCursor) { + // Finally! + return idCursor; + } + } while (idCursor < tagEndCursor); + lastCursor = tagEndCursor; + } + } + + /** + * Extracts the attribute value that is in the current position. + * @param html the text to extract from. + * @param attributeEnclosingChar the string/character that encloses the value. + * @returns {*} The value that relates to the closest attribute, or null if not found. + */ + function extractValueInCurrentPosition(html, attributeEnclosingChar) { + var idStartIndex, + idEndIndex; + + idStartIndex = html.indexOf(attributeEnclosingChar, cursor) + 1; + idEndIndex = html.indexOf(attributeEnclosingChar, idStartIndex + 1); + if (idStartIndex < 0 || idEndIndex < 0) { + return null; + } + + return html.substring(idStartIndex, idEndIndex); + } + + /** + * Converts SnappySnippet's CSS object into a string of CSS properties. + * @param properties The CSS object to extort. + * @returns {string} CSS properties contained in the given object. + */ + function propertiesToString(properties) { + var propertyName, + output = ""; + + for (propertyName in properties) { + if (properties.hasOwnProperty(propertyName)) { + output += propertyName + ": " + properties[propertyName] + "; "; + } + } + + return output; + } + + /** + * Injects style attribute to the current position in the HTML. + * @param html The text to use. + * @param styleId What key we are currently on. + * @param attributeEnclosingChar The string/character that encloses values. + * @returns {*} the modified string. + */ + function insertStyleAtIndex(html, styleId, attributeEnclosingChar) { + var cssStyles = stylesMap[styleId] && stylesMap[styleId].node; + + if (!cssStyles) { + return html; + } + return html.substring(0, cursor) + // The head of the string + STYLE_ATTRIBUTE + attributeEnclosingChar + // The attribute key + propertiesToString(stylesMap[styleId].node) + // The attribute value + attributeEnclosingChar + " " + // Closing the value just before the next attribute + html.substring(cursor); // The tail of the string + } + + this.process = function (html, styles) { + var currentId, + attributeEnclosingChar; + + // Sanity check + if (Boolean(html) && Boolean(styles)) { + // Prepare a lookup dictionary of styles by the respective element id + stylesMap = styles.map(function (styleObj) { + var keyValuePair = {}; + keyValuePair[styleObj.id] = styleObj; + return keyValuePair; + }).reduce(function (mergedObj, currentObj) { + return $.extend(mergedObj, currentObj); + }); + + cursor = getNextIdAttributePosition(html, 0); + while (cursor >= 0) { + // Make use of the fact that attribute value is always enclosed with the same char (either " or ') + attributeEnclosingChar = html.charAt(cursor + ID_ATTRIBUTE.length); + currentId = extractValueInCurrentPosition(html, attributeEnclosingChar); + if (currentId === null) + break; + html = insertStyleAtIndex(html, currentId, attributeEnclosingChar); + + cursor = getNextIdAttributePosition(html, cursor); + } + } + + return html; + }; +} diff --git a/js/tools/SameRulesCombiner.js b/js/processors/SameRulesCombiner.js similarity index 100% rename from js/tools/SameRulesCombiner.js rename to js/processors/SameRulesCombiner.js diff --git a/panel.html b/panel.html index a077613..fd9e480 100644 --- a/panel.html +++ b/panel.html @@ -69,9 +69,13 @@

  • +
  • - Prefix all CSS IDs with + Prefix all CSS IDs with
  • @@ -149,10 +153,11 @@

    - + + From 8ebdf820fd72bec98e2a309be438acadebf8a391 Mon Sep 17 00:00:00 2001 From: benbek Date: Wed, 13 Jan 2016 21:18:12 +0200 Subject: [PATCH 2/3] Handling string escaping inside CSS styles --- js/processors/HTMLStylesCombiner.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/processors/HTMLStylesCombiner.js b/js/processors/HTMLStylesCombiner.js index 81291eb..74270bd 100644 --- a/js/processors/HTMLStylesCombiner.js +++ b/js/processors/HTMLStylesCombiner.js @@ -10,6 +10,7 @@ function HTMLStylesCombiner() { // constants ATTRIBUTE_ENCLOSING_CHARACTERS = ['"', "'"], + ESCAPING_CHARACTER = '\\', ID_ATTRIBUTE = "id=", STYLE_ATTRIBUTE = "style="; @@ -70,15 +71,20 @@ function HTMLStylesCombiner() { /** * Converts SnappySnippet's CSS object into a string of CSS properties. * @param properties The CSS object to extort. + * @param attributeEnclosingChar The string/character that encloses values. * @returns {string} CSS properties contained in the given object. */ - function propertiesToString(properties) { + function propertiesToString(properties, attributeEnclosingChar) { var propertyName, output = ""; for (propertyName in properties) { if (properties.hasOwnProperty(propertyName)) { - output += propertyName + ": " + properties[propertyName] + "; "; + // Treat those special url() functionals, that sometimes have quotation marks although they are not required + var propertyValue = + properties[propertyName].replace(/url\("(.*)"\)/g, "url($1)").replace(/url\('(.*)'\)/g, "url($1)") + .replace(attributeEnclosingChar, ESCAPING_CHARACTER + attributeEnclosingChar); + output += propertyName + ": " + propertyValue + "; "; } } @@ -100,7 +106,7 @@ function HTMLStylesCombiner() { } return html.substring(0, cursor) + // The head of the string STYLE_ATTRIBUTE + attributeEnclosingChar + // The attribute key - propertiesToString(stylesMap[styleId].node) + // The attribute value + propertiesToString(stylesMap[styleId].node, attributeEnclosingChar) + // The attribute value attributeEnclosingChar + " " + // Closing the value just before the next attribute html.substring(cursor); // The tail of the string } From 1ca4e1b98abef046dbf255e18a17bd94ddc02ed3 Mon Sep 17 00:00:00 2001 From: benbek Date: Thu, 10 Mar 2016 20:25:32 +0200 Subject: [PATCH 3/3] Skipping comment nodes while looking for /tag Encountered this while trying to capture /html/body out of: http://www.amazon.com/SQL-Queries-Mere-Mortals-Manipulation/dp/0321992474/ref=redir_mobile_desktop?ie=UTF8&keywords=%7Bquery%7D&qid=1457617864&ref_=mp_s_a_1_1&sr=8-1 Also makes the cleaning process slightly faster. --- js/libs/jquery.htmlClean.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/libs/jquery.htmlClean.js b/js/libs/jquery.htmlClean.js index b61af68..1a4785e 100755 --- a/js/libs/jquery.htmlClean.js +++ b/js/libs/jquery.htmlClean.js @@ -35,6 +35,7 @@ options.allowEmpty = tagAllowEmpty.concat(options.allowEmpty); var tagsRE = /(<(\/)?(\w+:)?([\w]+)([^>]*)>)|