Brain dump in text format

Technology / Software / Hacks by Jason Mobarak

Google Apps Script: Insert Header Numbers

| Comments

Google Apps Script presents an interesting solution to extend the features of Google applications. The usual way to do this is with client side browser extensions, but they are not portable (that is, specific to a browser, and specific to the web)– and cannot run unattended.

Google’s apps (and similarly, 99% of other web apps) have long lacked scriptability that native applications enjoy. Apps script is a play to resolve that deficiency.

TL;DR

Jump to the solution for the script that adds header numbers.

The problem

Google docs has undergone some interesting simplifications in recent times, and it’d be interesting to learn why certain features got chopped.

In particular, at one point it was possible to insert numeric headers into a Google doc, though it looks like this was a CSS hack. Since Google docs isn’t exposing the underlying CSS of the document anymore, this method no longer works. You can also turn your headers into numbered lists … but this is really cumbersome.

Why do you need numeric headers? Well, it’s a pretty standard word processor feature– if you’re packaging information into a fairly static format (say a PDF), it’s useful for communication purposes to reference a specific part of the document; for example, if you were writing a technical review of someone’s software.

Background

Google offers several different types of scripts:

The biggest distinction appears to be if a script is bound to a container or not – that is, whether the script lives inside a doc, form, spreadsheet, or if it sits outside one of these (sitting in the script editor). Scripts can also become a standalone WebApp.

As first, standalone scripts seemed useful for testing things out, but the inability to interact with the user quickly made a standalone scripts useless… I found myself thinking that as I developed useful things with standalone scripts (macros!) that it’d be nice to share these, and simply insert some silly UI prompts to make the script generic– it’s unclear how to do this without making the script specific to a container, then sharing it in the document specific script gallery – right now, there’s no script gallery for text documents.

Power users, macro sharing, and the apps script gallery

The apps script gallery seems sadly lacking… why is the script gallery buried inside the document? This hardly seems like the “happening” place to showcase cool macros:

I did manage to find apps script “gallery” at one point, but it was full of commercial apps geared at implementing solution on top of Google Apps for businesses. This is great, but if Google wants to crowd source all the crazy features that users want by providing power-users with scripting capabilities, there should probably be something more like the chrome extensions gallery to showcase macros. That is, something easily accessible from a Google search.

The solution

The solution is to simply use the Google apps script API to create your own header numbers. It’s somewhat cumbersome, and will probably break in many cases, but it works.

There’s probably a better way to do this… for example, one improvement would be to figure out how to share this to a common repository so it could be imported into other documents.

Start by creating a new apps script in the document:

Then insert the following script (also maintained in a gist):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var state = {
  headerLevel1: 0,
  headerLevel2: 0,
  headerLevel3: 0,
}

var private = {
  getSectionNumber: function (paragraph)
  {
    if ( paragraph.getHeading() == DocumentApp.ParagraphHeading.HEADING1 )
    {
      state.headerLevel2 = 0;
      state.headerLevel3 = 0;

      return (++state.headerLevel1) + " ";
    }
    else if ( paragraph.getHeading() == DocumentApp.ParagraphHeading.HEADING2 )
    {
      if (state.headerLevel1 == 0)
        state.headerLevel1 = 1;

      state.headerLevel3 = 0;

      return state.headerLevel1 + "." + (++state.headerLevel2) + " ";
    }
    else if ( paragraph.getHeading() == DocumentApp.ParagraphHeading.HEADING3 )
    {
      if (state.headerLevel2 == 0)
        state.headerLevel2 = 1;

      return state.headerLevel1 + "." + state.headerLevel2 + "." + (++state.headerLevel3) + " ";
    }

    return "";
  },

  isNumber: function (text)
  {
    return !isNaN(text);
  },

  removeExistingHeaderNumber: function (text)
  {
    for (var i = 0; i < text.length; i++)
    {
      if ( private.isNumber(text.charAt(i)) || text.charAt(i) == "." )
          continue;

      break;
    }

    return text.substr(i);
  }
};

function addHeaderNumbers()
{
  var doc = DocumentApp.getActiveDocument();
  var body = doc.getBody()

  for (var i = 0; i < body.getNumChildren(); i++)
  {
    var element = body.getChild(i);

    if ( element.getType() != DocumentApp.ElementType.PARAGRAPH )
      continue;

    var paragraph = element.asParagraph();

    if ( paragraph.getHeading() != DocumentApp.ParagraphHeading.NORMAL )
    {
      var sectionNumber = private.getSectionNumber(paragraph);
      paragraph.setText(sectionNumber + private.removeExistingHeaderNumber(paragraph.getText()))
    }
  }

  doc.saveAndClose();
}

Save the project and return to the document. Then use the script manager to open the list of available functions to run:

Then select the ‘addHeaderNumbers’ script:

And magically your document will now have header numbering:

EOT

Comments