Make image set insertion work in more cases

In case there was a background with multiple layers (background: url(...),
url(...);), code would just replace first URL occurrence with -webkit-image-set
rule. Handles multiple layers in case there are only url() functions. Still
won't work if value contains more values than just url()s.

Additionally, use \s for matching white space, so that Windows line endings are
also handled.

And allow line breaks between 'content|background[...]:' and the following
value. Code would previously stop matching if there was a line break between
the colon and the value.

Code review at https://codereview.chromium.org/23591022/
Patch from issue 23591022, by rchlodnicki@opera.com


git-svn-id: http://grit-i18n.googlecode.com/svn/trunk@135 7262f16d-afe8-6277-6482-052fa10e57b1
diff --git a/grit/gather/chrome_html.py b/grit/gather/chrome_html.py
index 840b251..e7469bf 100644
--- a/grit/gather/chrome_html.py
+++ b/grit/gather/chrome_html.py
@@ -31,18 +31,22 @@
 # Matches a chrome theme source URL.
 _THEME_SOURCE = lazy_re.compile(
     '(?P<baseurl>chrome://theme/IDR_[A-Z0-9_]*)(?P<query>\?.*)?')
-# Matches CSS image urls with the capture group 'filename'.
+# Pattern for matching CSS url() function.
+_CSS_URL_PATTERN = 'url\((?P<quote>"|\'|)(?P<filename>[^"\'()]*)(?P=quote)\)'
+# Matches CSS url() functions with the capture group 'filename'.
+_CSS_URL = lazy_re.compile(_CSS_URL_PATTERN)
+# Matches one or more CSS image urls used in given properties.
 _CSS_IMAGE_URLS = lazy_re.compile(
-    '(?P<attribute>content|background|[\w-]*-image):[ ]*' +
-    'url\((?P<quote>"|\'|)(?P<filename>[^"\'()]*)(?P=quote)')
+    '(?P<attribute>content|background|[\w-]*-image):\s*' +
+        '(?P<urls>(' + _CSS_URL_PATTERN + '\s*,?\s*)+)')
 # Matches CSS image sets.
 _CSS_IMAGE_SETS = lazy_re.compile(
     '(?P<attribute>content|background|[\w-]*-image):[ ]*' +
         '-webkit-image-set\((?P<images>' +
-        '([,\r\n ]*url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*[0-9.]*x)*)\)',
+        '(\s*,?\s*url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*[0-9.]*x)*)\)',
     re.MULTILINE)
 # Matches a single image in a CSS image set with the capture group scale.
-_CSS_IMAGE_SET_IMAGE = lazy_re.compile('[,\r\n ]*' +
+_CSS_IMAGE_SET_IMAGE = lazy_re.compile('\s*,?\s*' +
     'url\((?P<quote>"|\'|)[^"\'()]*(?P=quote)\)[ ]*(?P<scale>[0-9.]*x)',
     re.MULTILINE)
 _HTML_IMAGE_SRC = lazy_re.compile(
@@ -121,10 +125,10 @@
   return "-webkit-image-set(%s)" % (', '.join(imageset))
 
 
-def InsertImageSet(
+def UrlToImageSet(
     src_match, base_path, scale_factors, distribution,
     filename_expansion_function=None):
-  """Regex replace function which inserts -webkit-image-set.
+  """Regex replace function which replaces url() with -webkit-image-set.
 
   Takes a regex match for url('path'). If the file is local, checks for
   files of the same name in folders corresponding to the supported scale
@@ -134,6 +138,36 @@
   scale factor.
 
   Args:
+    src_match: regex match object from _CSS_URLS
+    base_path: path to look for relative file paths in
+    scale_factors: a list of the supported scale factors (i.e. ['2x'])
+    distribution: string that should replace %DISTRIBUTION%.
+
+  Returns:
+    string
+  """
+  quote = src_match.group('quote')
+  filename = src_match.group('filename')
+  image_list = GetImageList(
+      base_path, filename, scale_factors, distribution,
+      filename_expansion_function=filename_expansion_function)
+
+  # Don't modify the source if there is only one image.
+  if len(image_list) == 1:
+    return src_match.group(0)
+
+  return GenerateImageSet(image_list, quote)
+
+
+def InsertImageSet(
+    src_match, base_path, scale_factors, distribution,
+    filename_expansion_function=None):
+  """Regex replace function which inserts -webkit-image-set rules.
+
+  Takes a regex match for `property: url('path')[, url('path')]+`.
+  Replaces one or more occurances of the match with image set rules.
+
+  Args:
     src_match: regex match object from _CSS_IMAGE_URLS
     base_path: path to look for relative file paths in
     scale_factors: a list of the supported scale factors (i.e. ['2x'])
@@ -142,18 +176,13 @@
   Returns:
     string
   """
-  quote = src_match.group('quote')
-  filename = src_match.group('filename')
   attr = src_match.group('attribute')
-  image_list = GetImageList(
-      base_path, filename, scale_factors, distribution,
-      filename_expansion_function=filename_expansion_function)
+  urls = _CSS_URL.sub(
+      lambda m: UrlToImageSet(m, base_path, scale_factors, distribution,
+                              filename_expansion_function),
+      src_match.group('urls'))
 
-  # Don't modify the source if there is only one image.
-  if len(image_list) == 1:
-    return src_match.group(0)
-
-  return "%s: %s" % (attr, GenerateImageSet(image_list, quote)[:-1])
+  return "%s: %s" % (attr, urls)
 
 
 def InsertImageStyle(
diff --git a/grit/gather/chrome_html_unittest.py b/grit/gather/chrome_html_unittest.py
index 55597a4..9b17c11 100644
--- a/grit/gather/chrome_html_unittest.py
+++ b/grit/gather/chrome_html_unittest.py
@@ -246,6 +246,121 @@
       '''))
     tmp_dir.CleanUp()
 
+  def testFileResourcesMultipleBackgrounds(self):
+    '''Tests inlined image file resources with two url()s.'''
+
+    tmp_dir = util.TempDir({
+      'test.css': '''
+      .image {
+        background: url(test.png), url(test.png);
+      }
+      ''',
+
+      'test.png': 'PNG DATA',
+
+      '2x/test.png': '2x PNG DATA',
+    })
+
+    html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
+    html.SetDefines({'scale_factors': '2x'})
+    html.SetAttributes({'flattenhtml': 'true'})
+    html.Parse()
+    self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
+                         StandardizeHtml('''
+      .image {
+        background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x), -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
+      }
+      '''))
+    tmp_dir.CleanUp()
+
+  def testFileResourcesMultipleBackgroundsWithNewline1(self):
+    '''Tests inlined image file resources with line break after first url().'''
+
+    tmp_dir = util.TempDir({
+      'test.css': '''
+      .image {
+        background: url(test.png),
+                    url(test.png);
+      }
+      ''',
+
+      'test.png': 'PNG DATA',
+
+      '2x/test.png': '2x PNG DATA',
+    })
+
+    html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
+    html.SetDefines({'scale_factors': '2x'})
+    html.SetAttributes({'flattenhtml': 'true'})
+    html.Parse()
+    self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
+                         StandardizeHtml('''
+      .image {
+        background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
+                    -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
+      }
+      '''))
+    tmp_dir.CleanUp()
+
+  def testFileResourcesMultipleBackgroundsWithNewline2(self):
+    '''Tests inlined image file resources with line break before first url()
+    and before second url().'''
+
+    tmp_dir = util.TempDir({
+      'test.css': '''
+      .image {
+        background:
+          url(test.png),
+          url(test.png);
+      }
+      ''',
+
+      'test.png': 'PNG DATA',
+
+      '2x/test.png': '2x PNG DATA',
+    })
+
+    html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
+    html.SetDefines({'scale_factors': '2x'})
+    html.SetAttributes({'flattenhtml': 'true'})
+    html.Parse()
+    self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
+                         StandardizeHtml('''
+      .image {
+        background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x),
+          -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
+      }
+      '''))
+    tmp_dir.CleanUp()
+
+  def testFileResourcesCRLF(self):
+    '''Tests inlined image file resource when url() is preceded by a Windows
+    style line break.'''
+
+    tmp_dir = util.TempDir({
+      'test.css': '''
+      .image {
+        background:\r\nurl(test.png);
+      }
+      ''',
+
+      'test.png': 'PNG DATA',
+
+      '2x/test.png': '2x PNG DATA',
+    })
+
+    html = chrome_html.ChromeHtml(tmp_dir.GetPath('test.css'))
+    html.SetDefines({'scale_factors': '2x'})
+    html.SetAttributes({'flattenhtml': 'true'})
+    html.Parse()
+    self.failUnlessEqual(StandardizeHtml(html.GetData('en', 'utf-8')),
+                         StandardizeHtml('''
+      .image {
+        background: -webkit-image-set(url(data:image/png;base64,UE5HIERBVEE=) 1x, url(data:image/png;base64,MnggUE5HIERBVEE=) 2x);
+      }
+      '''))
+    tmp_dir.CleanUp()
+
   def testThemeResources(self):
     '''Tests inserting high DPI chrome://theme references.'''