CSS equivalent of the center tag

Because I understand the temptation to sometimes just wrap a div in a damn <center> tag instead of messing around with auto margins, translatex(-50%), or any other such nonsense — and also partly as a future reference for myself — here is what I’ve found to be the CSS equivalent of the <center> tag.

.center-dammit {
    display: block;
    margin: 0 auto;
    text-align: center;
}

Caveat: I’ve not checked in depth so I’m sure there will be about a dozen exceptions. W3C, please sort this out for CSS4!

Of Ants and Rhinos

I recently opened a Pandora’s box when investigating incorporating LESS into a web project I’ve been working on. Long story short, I found myself having to compile Rhino into a jar I could execute.

Having installed Ant to perform the task, I ran the command and got the following exception:

C:\rhino1_7R5\xmlimplsrc\build.xml:129: src 'C:\rhino1_7R5\build\tmp-xbean\xbean.zip' doesn't exist.

Which is exactly what I want to be dealing with when investigating a CSS precompiler.

In any case I got to the bottom of the issue. So without further ado, here is…

The definitive list of steps for compiling Rhino

  1. Download the source from Rhino’s website
  2. Unzip it somewhere (let’s say, C:\rhino1_7R5″)
  3. Download Ant (if you don’t already have it) from Apache’s website
  4. Install Ant (instructions here)
  5. Download the zip from the following URL: http://archive.apache.org/dist/xmlbeans/binaries/xmlbeans-2.5.0.zip
  6. Create a folder in C:\rhino1_7R5\build\” called “tmp-xbean”
  7. Paste the zip from step 5 into this folder and rename it to “xbean.zip”
  8. Open a command prompt in C:\rhino1_7R5
  9. Run the command “ant jar”

For those interested, I worked this out by taking a look in the Ant build file for “xmlimplsrc”, which was the root of the error. This build XML file actually contains the location of the zip file which it’s failing to find. My guess is that it attempts to download it (install instructions are very unclear about the fact that they require an internet connection) and when that fails, your build fails. In my case it’s most likely due to my company network’s proxy restrictions.

I hope this helps.

Gradle from behind a proxy, part deux

In July I wrote a post documenting how to build a project in Android Studio from behind a proxy. Essentially you need to tell Gradle Studio your proxy settings.

As of updating to Android Studio 1.0, the issue has come back! After a combination of swearing and research I’ve found the missing necessary steps.

So the new definitive steps for getting Gradle working from behind a proxy

  1. Navigate to the “.gradle” folder in your user directory (e.g. C:\Users\bob\.gradle)
  2. Create a “gradle.properties” file
  3. Edit the file to have the following contents (replacing your own values)
    systemProp.http.proxyHost=<proxy_host> 
    systemProp.http.proxyPort=<port> 
    systemProp.http.proxyUser=<user> 
    systemProp.http.proxyPassword=<password> 
    systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost
  4. Go to Files > Settings > HTTP Proxy
  5. Select “Manual proxy configuration”
  6. Enter the same details you filled into the grade.properties file above: host, port, etc
  7. Tick “Proxy authentication”
  8. Fill in your username and password

Tadaa. This should get you back up and running again.

Exposing a VM on hosted WiFi hotspot for Google Glass

Background

Google Glass is famously frustrating to connect to a WiFi network. It doesn’t handle captive portals, or WiFi using Enterprise WPA2. I’ve also had consistent issues using MyGlass and QR codes to connect to Wifi.

I found myself in a situation where I had to connect Glass to a WiFi network on which a virtual machine was visible. My work’s corporate network was out of the question – it uses Enterprise WPA2. I would have connected my phone to the network and shared it to Glass via Bluetooth, but that was nixed for security reasons. So I had to set up a new network exclusively for Glass.

First thought – let’s just set up an Ad-Hoc wireless network on my laptop! But no, alas Android doesn’t support connecting to ad-hoc networks. Universe, why do you hate me so?

The solution

The solution involves a wireless-enabled Windows machine and VirtualBox – a program for running virtual machines.

An alternative to creating an ad-hoc network on your machine is to create a Wireless Hosted Network. This basically allows your Windows machine to act as a WiFi hotspot which can pass through a (wired) internet connection if you so wish.

My approach was to create a hosted network, connect my phone to it, then pass on the connection via Bluetooth. An admittedly insane chain of connections, but it’s all I had.

Create the WiFi network

  1. Open the Start menu and type “cmd”
  2. Right click and select “Run as Administrator”
  3. Type the following command:
     netsh wlan set hostednetwork mode=allow ssid=mynetworkid key=mypassword

Start the network

You’ll have to do this every time you start the machine, or alternatively set up a batch script to run this on startup.

  1. Type the following command:
     netsh wlan start hostednetwork

You should now see “mynetworkid” (or whatever you called it) in your list of wireless networks.

Create your VirtualBox image

  1. Install VirtualBox
  2. Create a new virtual machine (steps available here) – I’ve chosen to use a linux install
  3. Once your VM is created, go to Settings and choose Network
  4. Set up a Bridged Adapter
    1. Enable a network adapter
    2. Select Attached To: “Bridged Adapter”
    3. Select “Microsoft Virtual WiFi Miniport Adapter” as the Name – this is where the network you’ve just created is hosted.
  5. Start your VM up

Check connectivity

  1. On your Windows machine, open a command line and type:
     ipconfig

    A list of networks should be shown – one of which should be called “Wireless LAN adapter Wireless Network Connection”. Note down the first 9 digits of the IP address listed under this e.g. 192.168.173

  2. In your VM, run the following command:
     /sbin/ifconfig

    And note the networks and IPs that are listed. If no other networks were set up, your eth0 network should have an IP where the first 9 digits of the IP address matches those you noted down earlier. That means that they’re running on the network.

  3. For a laugh, ping your VM from your Windows machine, using the IP address from the previous step. This will confirm that your VM is addressable and contactable from your Windows machine. Given that your Windows machine is now broadcasting a wireless network, that means that the VM should be contactable to anyone on the network too.

Ping your VM from your Android device

As the penultimate proof, try pinging your VM from your Android device.

  1. Install an app which will let you run a ping command (I used PingTools)
  2. Ping the IP address you pinged previously

This should also show a response. And now we’re on the home straight.

Write some Glass code to contact your VM

You can do this however you wish. I wrote some very simple ping code to prove that Glass – once paired to my phone – was sharing the same internet connection as the phone, and the Windows machine, and as the VM.

There are tons of ways of doing it – for example:

InetAddress server = InetAddress.getByName("192.168.173.123");
if (server.isReachable(10000))
{
         // It’s contactable!
         celebrate();
}

Conclusions

That was much, much harder than it should have been. Alternatives I didn’t attempt:

  • Buying a router and broadcasting my corportate ethernet through it
  • Getting a company-approved phone that could have connected to the Enterprise network
  • Binning my Google Glass

PS – Many thanks to this post which acted as a great reference. It’s also worth checking out if you need to pass through internet via your hosted network.

How to invoke a SAS macro stored in a catalog

Having not done the Advanced Base SAS certification, this was a nightmare to work out. I’m documenting it here for my own future use, and to help anyone else who found themselves in the same situation as me.

What situation was that?

SAS Social Network Analysis can create networks from input data, and to do so it makes use of a pre-compiled “link macro” which is bundled with SNA. This link macro needs to be invoked from a base SAS program, but to do that, you need to tell SAS where to find it.

Note – There were literally zero Google hits for the exact name of this link macro. In case you’re curious, it’s called % sfs_net_main_link_macros.

Anyway I eventually found the location of these macros, in a catalog file.

Note – Not easy to find, and not documented. If anyone is in the same situation as me, it was in my <SASHome>\SASFoundation\9.3\snamva\macros folder. The macros are compiled into the sasmacr.sasb7cat file.

So I have a catalog file, how do I invoke the macro held in it?

Once you know, it’s very very simple.

  1. Copy the catalog file into your working directory
  2. Add a libname statement pointing to this working directory
  3. Use the SASMSTORE option

In other words your code should have the following statements:

libname mylib "D:\mylocation";
OPTIONS MSTORED SASMSTORE=mylib;

This will make the next invocation of the macro succeed, since SAS now knows it’s in your libname directory.

Removing duplicate rows in base SAS

If you ever need to remove duplicate rows from a SAS dataset, here’s an approach I use quite often.

Get your data.

Let’s assume it’s in the following format:

ID Name
123 John
456 Bob
123 John

Sort your data.

/* Step 1 - Sort data */
proc sort data=my_lib.my_dataset;

   /* Sort by a field which you want to be unique, 
   and which will be the same for duplicate rows */
   by id; 

run;

Which should give you the following:

ID Name
123 John
123 John
456 Bob


Remove Duplicates

Now that the data is in order, we can remove the duplicates, by only ever keeping the first entry which matches our unique ID.
-.

/* Step 2 - Get rid of duplicates */
data my_lib.my_dataset;

   /* Iterate through this dataset row by row */
   set my_lib.my_dataset;
   /* Grouping each row by the field we sorted on */ 
   by id; 
   /* And only keep a row if it’s the first */
   if first.id; 

run;

ID Name
123 John
456 Bob


Tadaa!

What happened there?

This approach has 3 facets:

  1. Grouping
  2. SAS’ special first.variable
  3. SAS’ feature of only appending (or “outputting”) a row to a dataset if there are no non-assignment statements which evaluate to false.


Grouping: So we effectively rearranged our data so that all identical IDs were grouped together. Given that the rows are identical and you only want to keep one of them, we choose to keep the first of each group.

First.variable: During execution, SAS will iterate through each row of my_data_set and adding it to a new dataset (which it will eventually overwrite my_data_set with). During each iteration, if it hits a row with the first use of an ID value (for example, 123), it will set first.id to true. On the next run, because it’s already seen the value 123 before, first.id is set to false. This gives us a handy flag which will only ever be toggled on unique rows.

Funny Statement Stuff: So how do we flag to SAS that when this value is set to true, to keep the row? When evaluating a data row if at any point we make any floating statement (i.e. not assigning a variable, or in an if or do loop) which evaluates to false, SAS will take that as a sign that it shouldn’t output that row i.e. in this case, it shouldn’t keep it.

So in simple terms, we’re saying – if you’ve seen this value before, don’t save it again.

SAS Dashboard – Fixing the “too many dials” issue

There’s an interesting feature of SAS BI Dashboard that caught me out when trying to put together some KPI gauges.

We wanted a set of dials that would show us the counts for several types of public security offences. Naturally we configured indicators using count as the measure, but found that the dashboard was showing an indicator per record in our system:

kpi1

 

With a bit of faffing around, I eventually found that this was due to the columns I had selected from my data. Specifically, the “uniqueID” column. This led to my big discovery:

The number of dials = The number of unique combinations of column values

In other words, because I had selected a column where the value was always unique, I got a dial for every row in my table. If I selected just the “category” column, I got the aggregated view I expected:

kpi2
Perhaps this just shows my SAS Dashboard naivety, but I thought I’d document it anyway.

 

SAS – How to Export/Import packages

It’s also possible to import/export metadata with the Wizard equivalent

My team and I have been developing a solution which involves a degree of SAS reports and related metadata. I set up a scheduled, automated backup of our information maps, reports, etc for posterity, and out of general paranoia. For this I used SAS’s command line export and import capabiltiies, which probably weren’t designed to be used that way, but which turned out to be really useful.

It took a wee bit of trial and error, so I thought I’d document it here.

(Handy reference link)

Note: If you’re puttying into your SAS server, make sure that your putty session has “Enable X11” ticked.

 Export

(Ignore any new lines in the text below – I’ve added those for readability)

/usr/local/SASHome/SASPlatformObjectFramework/9.3/ExportPackage -host “mysasmachine” -port 8561 -user myuser@saspw -password mypassword -package “myPackage.spk” -objects “/Shared Data/mySourceFolder(Folder)” -includeDep -subprop

  •  includeDep means that all objects that the export depends on are also exported
  • You can also specify “-types” with the types of files you wish to export
  • Without specifying “(Folder)” on mySourceFolder, all files will be exported “flat” i.e. without their folder hierarchy

 Import

 /usr/local/SASHome/SASPlatformObjectFramework/9.3/ImportPackage -host “mysasmachine” -port 8561 -user myuser@saspw -password mypassword -package “myPackage.spk” -target “/Shared Data/myTargetFolder(Folder)”  -subprop myPackage.subprop

  •  Without specifying “(Folder)” on myTargetFolder it would create a new folder with the name of the old parent folder in the new parent folder (e.g. /myTargetFolder/mySourceFolder)

Converting custom date formats in SAS Information Map Studio

Background

Let’s assume you have some dates in a custom format:

  date20131007 month102013

SAS Reports need to be able to a) present dates in a human readable format and b) understand dates to allow filtering and other funky stuff.

For that reason we need a way of translating these custom dates into SAS dates.

Step 1 – Get an Information Map with a date field

Use an existing one, or see http://support.sas.com/kb/35/471.html for more information on creating an Information Map.

Step 2 – Edit the Expression of your date field

  1. Open the Properties for your date field.
  2. Then on the Definition tab, click “Edit” in the “Expression Settings” section.

Step 3 – Magic

  1. Change the Type to Date so that SAS can treat it as a date field from now on.
  2. Then change the Expression Text to something like this:
input(substr(<<mytable.mydatefield>>,5,8), yymmdd8.)

wat

What’s happening here is that we’re translating the custom date string into a SAS date using what’s known as an INFORMAT.

SUBSTR (string, 5, 8) – Takes a substring from the given string, starting at character 5, with a length of 8 characters. In other words extracting the string date20131007 month102013

INPUT (string, yymmdd8.) – Takes a string and interprets it using the informat “yymmdd8.”. That informat is provided by default with SAS. What we’re doing here is saying to SAS “Here’s a string, but I want you to start treating it as a date. So that you know which part is the year, and which part is the month etc, use this informat as a guide”. Then SAS can know that dd = 07, mm = 10, and yy = 2013.

We’ve effectively translated a string in custom format into a SAS date.

My mind is blown. Now what?

Interpreting the dates as dates means we can now make it human-friendly in our reports, and also allows us to do some excellent SAS-native date filtering.

Formatting

What we specified earlier was an INFORMAT – in other words an interpretation format. What we can do, now that the date is stored as a SAS date, is specify an OUTPUT format, so that we can represent the date in a variety of ways in our reports.

  1. Go back to the Properties dialog for your date field
  2. On the Classifications tab is a “Formats” section. Change the “Format Type” to “Date/Time” and look at the available formats.

Selecting a format will take your date and represent it as a different string depending on the format you choose. Some examples:

Format Output
DATE 07OCT13
DAY 7
WEEKDAY 1
MONNAME October
DDMMYY 07/10/13
DOWNAME Monday

Filtering

We can also now use SAS to filter dates in a very cool way. For example I can now filter all records which were created in 2013, or all records created after a certain date, or on a certain date, etc.

  1. Create a new Filter and choose your date field as the Data Item.
  2. Then set your Condition to “Year to date” – this will filter all your results to only show ones where the date falls between 1 Jan 2013 and today.
  3. Click OK

A note on filtering

It’s always preferable to apply a filter at the Information Map level, rather than typing in a manual filter when you’re creating your Web Report. A filter on the Information Map will mean the data is filtered at the source, rather than decoding a bunch of information and only filtering once we get to the report level.*

* My understanding is that this is only with certain databases. Some allow optimisation by passing through filtering into the queries they make against the source databases. Still a good practice if you can do it.

How to restart SAS server

Photo by nimCC BY
Photo by nim CC BY

More detailed instructions can be found here, but below are the steps I use.

Note – All instructions assume the SAS is installed on a linux box, in /usr/local/, since that’s where it was installed on my machine.

Stop all JBoss server instances

/usr/local/jboss-5.1.0.GA/bin/SASServer1.sh stop

Stop SAS processes

/usr/local/config/Lev1/sas.servers stop

Start SAS processes

/usr/local/config/Lev1/sas.servers start

(wait a couple of minutes)

Start JBoss server instances

/usr/local/jboss-5.1.0.GA/bin/SASServer1.sh start

(wait about 5, 10 minutes)