From c61563ddcea10c005d1a7f3fbb0408ca41eacb72 Mon Sep 17 00:00:00 2001 From: Jared Reisinger <jaredreisinger@hotmail.com> Date: Wed, 11 Dec 2013 18:22:15 -0800 Subject: [PATCH] Fixes #21: Added 'defaultImageQuery' attribute support to the 'watermarks' configuration. All image watermarks will have values from 'defaultImageQuery' applied to their 'imageQuery' except where 'imageQuery' has an explicit key/value. If the 'defaultImageQuery' attribute is not specified, it defaults to "scache=true". Also added general-purpose "merge defaults" to NameValueCollectionExtensions and merging-constructors to ResizeSettings and added a unit test to verify the merging behavior. --- Core/ExtensionMethods/NameValueCollection.cs | 21 +++++++++++++ Core/ResizeSettings.cs | 16 ++++++++++ Plugins/Watermark/ImageLayer.cs | 15 ++++++--- Plugins/Watermark/Watermark.cs | 32 +++++++++++++++----- Tests/Core.Tests/ResizeSettingsTest.cs | 18 +++++++++++ 5 files changed, 90 insertions(+), 12 deletions(-) diff --git a/Core/ExtensionMethods/NameValueCollection.cs b/Core/ExtensionMethods/NameValueCollection.cs index 2c328031c..0733f8db9 100644 --- a/Core/ExtensionMethods/NameValueCollection.cs +++ b/Core/ExtensionMethods/NameValueCollection.cs @@ -243,5 +243,26 @@ public static NameValueCollection Keep(NameValueCollection q, params string[] ke return c; } + + /// <summary> + /// Creates and returns a new NameValueCollection instance that contains all of the + /// keys/values from 'q', and any keys/values from 'defaults' that 'q' does not already + /// contain. + /// </summary> + /// <param name="q">The settings specific to a particular query</param> + /// <param name="defaults">Default settings to use when not overridden by 'q'.</param> + /// <returns></returns> + public static NameValueCollection MergeDefaults(NameValueCollection q, NameValueCollection defaults) + { + // Start with the defaults, and then blindly copy the keys/values + // from 'q'. Any keys in both 'defaults' and 'q' will end up having + // the value from 'q', since that one is set last. + NameValueCollection c = new NameValueCollection(defaults); + foreach (string s in q.AllKeys) { + c[s] = q[s]; + } + + return c; + } } } diff --git a/Core/ResizeSettings.cs b/Core/ResizeSettings.cs index 514e3b0d5..3bd1fae0b 100644 --- a/Core/ResizeSettings.cs +++ b/Core/ResizeSettings.cs @@ -34,6 +34,22 @@ public ResizeSettings(NameValueCollection col) : base(col) { } /// </summary> /// <param name="queryString"></param> public ResizeSettings(string queryString) : base(PathUtils.ParseQueryStringFriendlyAllowSemicolons(queryString)) { } + /// <summary> + /// Merges the specified collection with a set of defaults into a new + /// ResizeSettings instance. + /// </summary> + /// <param name="col"></param> + /// <param name="defaultSettings"></param> + public ResizeSettings(NameValueCollection col, NameValueCollection defaultSettings) + : base(NameValueCollectionExtensions.MergeDefaults(col, defaultSettings)) { } + /// <summary> + /// Parses the specified querystring into name/value pairs and merges + /// it with defaultSettings in a new ResizeSettings instance. + /// </summary> + /// <param name="queryString"></param> + /// <param name="defaultSettings"></param> + public ResizeSettings(string queryString, NameValueCollection defaultSettings) + : this(PathUtils.ParseQueryStringFriendlyAllowSemicolons(queryString), defaultSettings) { } /// <summary> /// Creates a new resize settings object with the specified resizing settings diff --git a/Plugins/Watermark/ImageLayer.cs b/Plugins/Watermark/ImageLayer.cs index db2270e50..2f589d0d5 100644 --- a/Plugins/Watermark/ImageLayer.cs +++ b/Plugins/Watermark/ImageLayer.cs @@ -12,14 +12,19 @@ namespace ImageResizer.Plugins.Watermark { public class ImageLayer:Layer { - public ImageLayer(NameValueCollection attrs, Config c) + public ImageLayer(NameValueCollection attrs, ResizeSettings defaultImageQuery, Config c) : base(attrs) { - Path = attrs["path"]; - this.c = c; - if (!string.IsNullOrEmpty(attrs["imageQuery"])) ImageQuery = new ResizeSettings(attrs["imageQuery"]); + Path = attrs["path"]; + this.c = c; + if (!string.IsNullOrEmpty(attrs["imageQuery"])) { + ImageQuery = new ResizeSettings(attrs["imageQuery"], defaultImageQuery); + } else { + ImageQuery = new ResizeSettings(defaultImageQuery); + } } - public ImageLayer(Config c) { + public ImageLayer(Config c) + { this.c = c; } protected string _path = null; diff --git a/Plugins/Watermark/Watermark.cs b/Plugins/Watermark/Watermark.cs index df3f08a30..0d60bf153 100644 --- a/Plugins/Watermark/Watermark.cs +++ b/Plugins/Watermark/Watermark.cs @@ -23,11 +23,24 @@ public class WatermarkPlugin : LegacyWatermarkFeatures, IPlugin, IQuerystringPlu public WatermarkPlugin() { } + + private ResizeSettings _defaultImageQuery = new ResizeSettings("scache=true"); + /// <summary> + /// Default querystring parameters for all image watermarks. + /// If not specified in the watermark configuration, defaults to + /// "scache=true". + /// </summary> + public ResizeSettings DefaultImageQuery + { + get { return _defaultImageQuery; } + set { _defaultImageQuery = value; } + } + ImageLayer _otherImages = new ImageLayer(null); /// <summary> /// When a &watermark command does not specify a named preset, it is assumed to be a file name. /// Set OtherImages.Path to the search folder. All watermark images (except for presets) must be in the root of the search folder. - /// The remainder of the settings affect how each watermrak will be positioned and displayed. + /// The remainder of the settings affect how each watermark will be positioned and displayed. /// </summary> public ImageLayer OtherImages { get { return _otherImages; } @@ -45,7 +58,7 @@ public IPlugin Install(Configuration.Config c) { c.Plugins.add_plugin(this); this.c = c; this.OtherImages.ConfigInstance = c; - _namedWatermarks = ParseWatermarks(c.getConfigXml().queryFirst("watermarks"), ref _otherImages); + _namedWatermarks = ParseWatermarks(c.getConfigXml().queryFirst("watermarks"), ref _defaultImageQuery, ref _otherImages); c.Pipeline.PostRewrite += Pipeline_PostRewrite; return this; } @@ -60,7 +73,13 @@ public IEnumerable<string> GetSupportedQuerystringKeys() { return new string[] { "watermark" }; } - protected Dictionary<string, IEnumerable<Layer>> ParseWatermarks(Node n, ref ImageLayer otherImageDefaults) { + protected Dictionary<string, IEnumerable<Layer>> ParseWatermarks(Node n, ref ResizeSettings defaultImageQuery, ref ImageLayer otherImageDefaults) { + // Grab the defaultImageQuery value (if it exists) from the watermarks + // node, so that we can apply them to any subsequent image watermarks. + if (n != null && !string.IsNullOrEmpty(n.Attrs["defaultImageQuery"])) { + defaultImageQuery = new ResizeSettings(n.Attrs["defaultImageQuery"]); + } + Dictionary<string, IEnumerable<Layer>> dict = new Dictionary<string, IEnumerable<Layer>>(StringComparer.OrdinalIgnoreCase); if (n == null || n.Children == null) return dict; foreach (Node c in n.Children) { @@ -77,16 +96,15 @@ protected Dictionary<string, IEnumerable<Layer>> ParseWatermarks(Node n, ref Ima } - - if (c.Name.Equals("otherimages", StringComparison.OrdinalIgnoreCase)) otherImageDefaults = new ImageLayer(c.Attrs, this.c); - if (c.Name.Equals("image", StringComparison.OrdinalIgnoreCase)) dict.Add(name, new Layer[]{new ImageLayer(c.Attrs, this.c)}); + if (c.Name.Equals("otherimages", StringComparison.OrdinalIgnoreCase)) otherImageDefaults = new ImageLayer(c.Attrs, defaultImageQuery, this.c); + if (c.Name.Equals("image", StringComparison.OrdinalIgnoreCase)) dict.Add(name, new Layer[] { new ImageLayer(c.Attrs, defaultImageQuery, this.c) }); if (c.Name.Equals("text", StringComparison.OrdinalIgnoreCase)) dict.Add(name, new Layer[] {new TextLayer(c.Attrs) }); if (c.Name.Equals("group", StringComparison.OrdinalIgnoreCase)) { List<Layer> layers = new List<Layer>(); if (c.Children != null) { foreach (Node layer in c.Children) { - if (layer.Name.Equals("image", StringComparison.OrdinalIgnoreCase)) layers.Add(new ImageLayer(layer.Attrs, this.c)); + if (layer.Name.Equals("image", StringComparison.OrdinalIgnoreCase)) layers.Add(new ImageLayer(layer.Attrs, defaultImageQuery, this.c)); if (layer.Name.Equals("text", StringComparison.OrdinalIgnoreCase)) layers.Add(new TextLayer(layer.Attrs)); } } diff --git a/Tests/Core.Tests/ResizeSettingsTest.cs b/Tests/Core.Tests/ResizeSettingsTest.cs index 26bcd366e..9071363d1 100644 --- a/Tests/Core.Tests/ResizeSettingsTest.cs +++ b/Tests/Core.Tests/ResizeSettingsTest.cs @@ -55,5 +55,23 @@ public void TestRoundTripColor(string from, string expected) Color? parsed = ParseUtils.ParseColor(from).Value; Assert.AreEqual(expected, ParseUtils.SerializeColor(parsed.Value), StringComparison.InvariantCultureIgnoreCase); } + + [Test] + [Row("a=1", "", "?a=1")] + [Row("", "b=2", "?b=2")] + [Row("a=1", "b=2", "?a=1&b=2")] + [Row("b=1", "b=2", "?b=1")] + public void TestMergingConstructor(string q, string defaults, string expected) + { + ResizeSettings defaultSettings = new ResizeSettings(defaults); + ResizeSettings mergedSettings = new ResizeSettings(q, defaultSettings); + ResizeSettings expectedSettings = new ResizeSettings(expected); + + Assert.AreEqual(expectedSettings.Count, mergedSettings.Count); + foreach (string key in expectedSettings.AllKeys) + { + Assert.AreEqual(expectedSettings[key], mergedSettings[key]); + } + } } }