All posts by Torleif

jQuery: How to extract a tag from an HTML response

Making a website, and using ajax for some things. Sometimes things fail and return custom error pages. I made them to be helpful, but since you can only see them in the browser developer console, they were a bit of a hassle to look at.

To see what the error was much easier, I figured I could just parse the returned HTML, extract the message I knew was there, and insert it into the page that way.

And you’d think the following would work fine:

$(document).ajaxError(function(event, jqxhr, settings, error)
{
    // Find the message in the response HTML
    var m = $(jqxhr.responseText)
        .find('#message');

    // Except .find() doesn't find anything

    // And we replace our DOM with nothing
    $('#content')
        .replaceWith(m);
});

But… No… Apparently, since the response was a complete HTML page, i.e. including html, head and body tags, jQuery was getting a bit tricked up when trying to parse it. Actually not sure if it’s jQuery or native browser parsing behind that’s causing it, but where there’s a will, there’s a way:

$(document).ajaxError(function(event, jqxhr, settings, error)
{
    // Find the inner HTML of the body tag
    var body = /<body.*>([\s\S]+)<\/body>/
        .exec(jqxhr.responseText);

    // Parse the HTML
    body = $.parseHTML(body[1])

    // Append the HTML to a non-special root tag
    body = $('<output>').append(body);

    // And *now* we can finally find our message
    var message = body.find('#message');

    // And add it to our DOM
    $('#content')
        .replaceWith(m);
});

¯\_(ツ)_/¯

PHP: preg_match_all_callback

There are several PCRE functions available, but today I looked for one that just wasn’t there: preg_match_all_callback().

Could’ve maybe used preg_replace_callback(), but felt wrong since I didn’t actually want to do any replacing. I just needed my function to be called for each match.

So I wrote it myself. Noting it here, in case I (or someone else) needs it again.

<?php
/**
 * Perform a global regular expression match
 * and calls the callback for each match.
 */

function preg_match_all_callback(
        string $pattern,
        string $subject,
        callable $callback)
{
    $r = preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER);
    foreach($matches ?? [] as $match)
        $callback($match);
    return $r;
}

And, in case someone reads this post and knows it actually does exist… and if that someone is you, please do leave a comment!

And, yes, I could’ve just written those 3 lines where I needed them, but what’s the fun in that? And besides, the shorter the code where it counts, the easier what counts is to read.

Usage

preg_match_all_callback('/(\w)\w*/', 'Hello World', 'var_dump');
array (size=2)
  0 => string 'Hello' (length=5)
  1 => string 'H' (length=1)

array (size=2)
  0 => string 'World' (length=5)
  1 => string 'W' (length=1)

PHP: Get headers with actual HEAD request

PHP has a function called get_headers which, as you’d guess, gives you the headers returned from an HTTP request. However it actually uses a GET, rather than HEAD, request.

Figured out you can change this by setting a stream context, so wrapped it in a function. And posting it here in case I need it again.

Also added a cleanup of the returned array, as I found it a bit ugly when the request included redirects. See difference below code.

Note: I silence the get_headers call because it throws several warnings, e.g. if the hostname fails lookup, and I’m not really interested in why it fails.

function get_head(string $url, array $opts = [])
{
    // Store previous default context
    $prev = stream_context_get_options(stream_context_get_default());

    // Set new one with head and a small timeout
    stream_context_set_default(['http' => $opts +
        [
            'method' => 'HEAD',
            'timeout' => 2,
        ]]);

    // Do the head request
    $req = @get_headers($url, true);
    if( ! $req)
        return false;

    // Make more sane response
    foreach($req as $h => $v)
    {
        if(is_int($h))
            $headers[$h]['Status'] = $v;
        else
        {
            if(is_string($v))
                $headers[0][$h] = $v;
            else
                foreach($v as $x => $y)
                    $headers[$x][$h] = $y;
        }

    }

    // Restore previous default context and return
    stream_context_set_default($prev);
    return $headers;
}

Example response:

<?php get_head('http://geekality.net');

array (size=2)
  0 =>
    array (size=8)
      'Status' => string 'HTTP/1.1 301 Moved Permanently' (length=30)
      'Date' => string 'Mon, 06 Feb 2017 01:20:48 GMT' (length=29)
      'Server' => string 'Apache' (length=6)
      'Location' => string 'http://www.geekality.net/' (length=25)
      'Vary' => string 'Accept-Encoding' (length=15)
      'Connection' => string 'close' (length=5)
      'Content-Type' => string 'text/html; charset=iso-8859-1' (length=29)
      'Link' => string '<http://www.geekality.net/wp-json/>; rel="https://api.w.org/"' (length=61)
  1 =>
    array (size=6)
      'Date' => string 'Mon, 06 Feb 2017 01:20:48 GMT' (length=29)
      'Server' => string 'Apache' (length=6)
      'Vary' => string 'Accept-Encoding' (length=15)
      'Connection' => string 'close' (length=5)
      'Content-Type' => string 'text/html; charset=UTF-8' (length=24)
      'Status' => string 'HTTP/1.1 200 OK' (length=15)

Example response without my cleanup:

<?php get_head('http://geekality.net');

array (size=9)
  0 => string 'HTTP/1.1 301 Moved Permanently' (length=30)
  'Date' =>
    array (size=2)
      0 => string 'Mon, 06 Feb 2017 01:14:00 GMT' (length=29)
      1 => string 'Mon, 06 Feb 2017 01:14:01 GMT' (length=29)
  'Server' =>
    array (size=2)
      0 => string 'Apache' (length=6)
      1 => string 'Apache' (length=6)
  'Location' => string 'http://www.geekality.net/' (length=25)
  'Vary' =>
    array (size=2)
      0 => string 'Accept-Encoding' (length=15)
      1 => string 'Accept-Encoding' (length=15)
  'Connection' =>
    array (size=2)
      0 => string 'close' (length=5)
      1 => string 'close' (length=5)
  'Content-Type' =>
    array (size=2)
      0 => string 'text/html; charset=iso-8859-1' (length=29)
      1 => string 'text/html; charset=UTF-8' (length=24)
  1 => string 'HTTP/1.1 200 OK' (length=15)
  'Link' => string '<http://www.geekality.net/wp-json/>; rel="https://api.w.org/"' (length=61)

PHP: Convert RGB to hex and back

Just another note to self, in case I need it again:

/**
 * #rrggbb or #rgb to [r, g, b]
 */

function hex2rgb(string $hex): array
{
    $hex = ltrim($hex, '#');

    if(strlen($hex) == 3)
        return [
            hexdec($hex[0].$hex[0]),
            hexdec($hex[1].$hex[1]),
            hexdec($hex[2].$hex[2]),
        ];
    else
        return [
            hexdec($hex[0].$hex[1]),
            hexdec($hex[2].$hex[3]),
            hexdec($hex[4].$hex[5]),
        ];
}


/**
 * [r, g, b] to #rrggbb
 */

function rgb2hex(array $rgb): string
{
    return '#'
        . sprintf('%02x', $rgb[0])
        . sprintf('%02x', $rgb[1])
        . sprintf('%02x', $rgb[2]);
}

Enabling query logging on MariaDB/MySQL

When developing it can sometimes be very useful to see exactly what queries are sent to the database.

Found out there’s something called a General Query Log, and enabling it was really easy. Just add the following to your my.{ini,cnf} file:

[mysqld]
general-log=1
general-log-file=queries.log
log-output=file

Restart the server and you should now find the log in your data directory (unless no queries done yet).

If you don’t know where your data directory is, just run this query:

SHOW VARIABLES WHERE variable_name = 'datadir'

Choir practicing home alone with Musescore

Here’s a simple, hopefully helpful, guide for those who are in a choir, and maybe just got an email with a link to a bunch of Musescore-files sent to them from that weird nerd in the back row.

This is what you do.

0. Download Musescore

Go to musescore.org, download Musescore (big green button), and install it as you would any other program.

Note: If you struggle with this, and/or the next step, just ask that nerd, or a friendly one in your neighbourhood, for help 🙂

1. Get a file onto your computer

Download one of the Musescore files. You can identify them by having the extension “.mscz”, and once on your computer they might have the icon seen below. Here seen next to a PDF file, which might be more familiar to you. But yeah, you want the Musescore file now 🙂

musescore-icons

Note: If you see files whose name starts with a dot, for example “.Celtic Advent Carol.mscz”, these are temporary files that doesn’t work, so ignore them and find the one that doesn’t start with a dot 🙂

2. Open the file in Musescore and start practicing!

Once you’ve opened the file in Musescore (either via Explorer or Finder, or via File/Open inside Musescore, like you would in any other application, like Word, Pages, etc), you should have a view similar to this:

main

There might be some extra panels open, but they are mostly useful for entering sheet music. For playback, very few things are needed.

The View menu, that you see open in that image, is where you turn on and off all those various panels, including the essential Play Panel and Mixer, which we come to shortly.

Simple playback

At the middle top, you have the play-button, and a jump-back-to-the-very-start-button.

If you want to start playback from a certain place, simply click on a note to select it, and then press the play-button.

Playback with the Play Panel

In the View menu, you can open the Play Panel. It looks like this:

play

On the right you can adjust the tempo and the main volume.

The tempo can be useful in tricky parts (or if the tempo is just wrong compared to what you remember from last choir practice).

The Volume slider is mostly useful if the song has a lot of unison parts, which can sometimes result in a bit distorted sound because all the sound gets added on top of each other and ends up being too loud to function. If that happens, just nudge this one down a bit. Otherwise, just use the computer volume as you normally would.

At the bottom middle, you find the same play buttons as in the main window, but you also have some loop buttons you can play around with if you feel like it. I rarely use them, but found it useful a few times when I really struggle to find a certain note. Then I’ve set the playback to loop over that section over and over again until it sticks.

The mixer!

In the View menu you also find the mixer, which is the most important thing. It looks like this:

mixer

As you can see, you have a number of controls repeated down, one for each staff (note line) in the score. And you can fiddle with these both when playback is stopped, and while you’re listening.

Use Mute to turn one off. Use Solo to turn everything else off. Use the volume dial, by clicking on it with your mouse and dragging up/down, for more control over the volume of the various voices. You can also change panning (for example put your voice in the right speaker, and the rest in the left), or change instruments used to playback and such, but I rarely bother with that. If you double-click a dial, it resets to its default value, which is useful.

Either way, I usually do something along the lines of this:

  1. I start by turning my voice up to max.
  2. And and the rest down to a quarter or a half, depending on how secure I already am. That way it’s easy to hear what I’m supposed to sing, but I still have the others in the background.
  3. Practice until getting comfortable.
  4. Then I start turning the other voices back up towards their default value. (remember: double-click the dial to reset it).
  5. If I then have trouble finding my notes, I go back to step 1.
  6. If I’m still finding my notes ok, I start turning down my own sound to make sure I can still find my notes without leaning on what I hear.
  7. If not, practice more.
  8. And when I can get through the whole song, with basically no volume on my own voice, then I’m good to go 🙂

Which in my case usually means: Go, lay down on the couch, and memorize lyrics for an hour or two… because that’s what I personally really struggle with learning 😛

Happy practicing!

Hope this helps someone. I use it for literally every new song I get in the choir, and it helps me a lot. Especially with those tricky bits… that one note I just can’t quite find… that place with a bit of an off beat making it difficult to articulate properly without “getting it”… that crazy section where everyone sings something different and I’m not sure when to actually start and/or stop singing…