Using Base64 to create a data URI

Given a CSS file, we want to replace all instances of a particular image URL with a data URI to reduce the number of requests. Let's write a small D program to do this replacement.

How to do it…

Let's create a data URI by executing the following steps:

  1. Load the image file with std.file.read().
  2. Create the data URI with std.base64 as shown in the following code:
    pure char[] makeDataUri(string contentType, in void[] data) {
        import std.base64;
        return "data:" ~ contentType ~ ";base64," ~ Base64.encode(cast(const(ubyte[]))) data;
    }
  3. Load the CSS file with std.file.readText.
  4. Use std.array.replace to replace the URL with the data URI.
  5. Save the CSS file with std.file.write.

Putting it all together in main, you will have the following code:

void main() {
    import std.file, std.array;
    auto imageData = std.file.read("image.png"); // step 1
    string dataUri = makeDataUri("image/png", imageData); 
    // step 2
    auto cssFile = std.file.readText("style.css"); // step 3
    cssFile = cssFile.replace("image.png", dataUri); // step 4
    std.file.write("style-combined.css", cssFile); // step 5
}

How it works…

Phobos' std.file module, in addition to directory listing functions, also includes high-level file functions. The functions read, readText, and write give one-step access to file contents. The read(filename) function reads the entire file in one go, returning void[], which must be cast to another type (for example, char[] or ubyte[]) to be used. The readText(filename) function validates that the file is UTF-8 and returns a string. The write(filename, contents) function writes the contents directly to the file, overwriting it if it already exists.

Phobos' std.array module includes several generic functions to operate on arrays that are callable with member-style syntax thanks to UFCS. Since a string in D is simply an array of characters, all these functions also work on strings. The replace function searches the haystack for all occurrences of the needle and replaces them with the replacement argument, returning the new data without modifying the original.

Our makeDataUri function uses std.base64 to create a data URI that is usable in CSS files. The std.base64 module uses a template with value parameters to customize the behavior from the following source:

alias Base64Impl!('+', '/') Base64;
alias Base64Impl!('-', '_') Base64URL;
template Base64Impl(char Map62th, char Map63th, char Padding = '=')

D's templates can take values as compile-time parameters, which can be used the same way as regular runtime parameters in the code. Here, the implementation can have three characters swapped out so the code can be easily used by other Base64 variants. The alias lines give a convenient name to different parameterizations. The aliases are different types, similar to subclasses of Base64, which parameterize it with virtual functions or in the constructor. However, unlike classes, there's no runtime cost here. The compile-time values are exactly equivalent to character literals in the generated code.

As Base64.encode takes ubyte[] and our makeDataUrl function takes the generic array type void[], we had to cast the data to use it. We take the parameter of type in void[] because we inspect the data but don't store or modify it, and it can be any type. All arrays implicitly cast to const(void)[], making it (or in void[]) ideal for functions that just generically need data. The data argument of std.file.write is also const(void)[].

Finally, you might have noticed that makeDataUrl returns a mutable reference yet was assigned to a string, which is an immutable character array. Normally, that wouldn't compile, so why does it work here? The key is because makeDataUri is marked pure. Pure functions are not allowed to modify anything outside their own arguments. This means the return value can be assumed to be unique. A related function is assumeUnique from std.exception, which is used in a Phobos idiom to convert mutable data to immutable data. It should only be used when you are sure that the reference is indeed unique and that no other pointers exist into the data. After calling it, you should stop using the original reference. Pure functions use this same principle implicitly: as long as the reference was not passed in through its argument list (the function would be called strongly pure in D), there can be no other references. So, it is safe to assume that it is unique. This means if you stop using the old reference, it is guaranteed to be immutable! We'll learn more about pure functions later in this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.9.169