5

I write in Fountain markdown http://fountain.io/ in Google Docs. Fountain is used for writing screenplays. I want to make writing in fountain a little friendlier by auto-capitalizing certain elements (on open or with a button, whatever).

Here is a correctly formatted screenplay (in fountain):

EXT. GAS STATION - DAY

Susie steps out of her car and walks toward the station attendant.

SUSIE
Hey, Tommy.

TOMMY
Where you been, Sue?  Come on in.

They walk toward the station entrance together.

INT. GAS STATION - NIGHT

etc...

As you can see there is a ton of CAPS-LOCK and SHIFT business in screenwriting, and it gets tedious.

Which is why I want to write in lower-case (ie. int. gas station - day) and have javascript/GAS find that text and uppercase it. Same with when a character speaks:

susie
Hey, Tommy.

would become

SUSIE
Hey, Tommy.

Characters speaking always have an empty line above their name and text on the next line. And scene headings ALWAYS start with either EXT. or INT.

I have had some kind help so far on Stackoverflow, but I'm still struggling to get this to work at all. I was given a great regex string that finds character names but GAS has a limited regex implementation. That regex is [\n][\n]([^\n]+)[\n][^\n|\s]/gi. I have had no luck replacing text with regex. My JS skill is newborn baby, but I have completed CodeAcademy's beginner's JS course for what that's worth.

I would be grateful for any help in the right direction.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
wthman
  • 97
  • 1
  • 2
  • 7
  • It seems you can't use capture groups and mode modifiers in the regex, so this wouldn't be very easy. – Amal Murali Jul 20 '14 at 04:39
  • Just wondering... Why would you want to write in Google Docs without the WISYWYG editor? – cregox Jul 29 '14 at 14:37
  • 1
    I write in fountain markdown in google docs. But fountain parsers require certain elements to be in UPPERCASE. The javascript function Modsdad created allows a lazy writer (me) to write in lower-case, and automatically uppercase crap later. – wthman Jul 29 '14 at 21:30

2 Answers2

6

To change the text in a Google Doc, you need to get the individual elements and operate on them. There's a fair bit of work to do, digging into the document, before the Money Shot:

paragraphText.toUpperCase();

The following script is part of a document add-on, source available in this gist, in changeCase.js.

Code.gs

/**
 * Scan Google doc, applying fountain syntax rules.
 * Caveat: this is a partial implementation.
 *
 * Supported:
 *  Character names ahead of speech.
 *
 * Not supported:
 *  Everything else. See http://fountain.io/syntax
 */
function fountainLite() {
  // Private helper function; find text length of paragraph
  function paragraphLen( par ) {
    return par.asText().getText().length;
  }

  var doc = DocumentApp.getActiveDocument();
  var paragraphs = doc.getBody().getParagraphs();
  var numParagraphs = paragraphs.length;

  // Scan document
  for (var i=0; i<numParagraphs; i++) {

    /*
    ** Character names are in UPPERCASE.
    ** Dialogue comes right after Character.
    */
    if (paragraphLen(paragraphs[i]) > 0) {
      // This paragraph has text. If the preceeding one was blank and the following
      // one has text, then this paragraph might be a character name.
      if ((i==0 || paragraphLen(paragraphs[i-1]) == 0) && (i < numParagraphs && paragraphLen(paragraphs[i+1]) > 0)) {
        var paragraphText = paragraphs[i].asText().getText();
        // If no power-user overrides, convert Character to UPPERCASE
        if (paragraphText.charAt(0) != '!' && paragraphText.charAt(0) != '@') {
          var convertedText = paragraphText.toUpperCase(); 
          var regexEscaped = paragraphText.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); // http://stackoverflow.com/a/3561711/1677912
          paragraphs[i].replaceText(regexEscaped, convertedText);
        }
      }
    }
  }
}
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
  • Insane! It's like magic. Thank you so much! I never understood that every line in a document body is a paragraph, which I believe is the case here? Your code mostly makes sense to me, but a couple of questions. When you do the conversion at the end, is there a special reason why you're looking for '!' and '@'? And, how is the `regexEscaped` working? Is it a workaround because you can't simply put the regex into the replaceText()? – wthman Jul 20 '14 at 18:50
  • Yes, any time you hit return in your doc, you create a new paragraph. Re '!' and '@', in [the spec for fountain](http://fountain.io/syntax), they describe two cases which can be used to have a Character Name left as-is. (e.g. A name like McCLOUD.) If a line starts with either character, this script expects that you knew what you were doing, and leaves it alone. The purpose for `regexEscaped` is to allow the text to contain regex special characters like `*`, `$` etc. By escaping them first, they are treated as text - without them, a name like `BRICK ^` or `EDWARD (V.O.)` won't work. – Mogsdad Jul 21 '14 at 00:04
  • ... and names like "a string with a period at the end." (which you've already run into!) – Mogsdad Jul 21 '14 at 17:07
  • Maybe off-topic, but I added a bunch more business to the `fountainLite()` function (right-aligning transitions, bolding description lines, uppercasing descriptions), and it works on a small document - slowly. But with a large doc (50 pages) Google is giving me an error "Service invoked too many times in a short time: properties. Try Utilities.sleep(1000) between calls". Not really experienced, but it seems like running so many IF statements per paragraph is slowing it down. – wthman Jul 28 '14 at 22:22
  • Any pure javascript is very fast, so an if statement is unlikely to be the culprit. Look at google service calls - in this case, it sounds like you are accessing the `properties` service in a loop. Tfocus on changing to reduce the number of service calls. – Mogsdad Jul 29 '14 at 03:37
  • OIC. I was retrieving `userproperties` within a bunch of IF statements. Now I put the user properties into vars at the top of the `FountainLite()` function and tested those values instead. Thanks. – wthman Jul 29 '14 at 17:15
  • Cleaned it up, but now GAS is giving an error: `TypeError: Cannot call method "asText" of undefined.` on your function: `function paragraphLen(par) { return par.asText().getText().length; }`. Of course my script is way diff than yours at this point. In any case the function runs all of the way through correctly, yet gives this error. – wthman Jul 30 '14 at 23:01
  • That's because `par` is undefined. Check it, first: `if (!par) return 0; else ...`. – Mogsdad Jul 30 '14 at 23:22
0
var par2 = table.getCell(i,j).getChild(0).asParagraph();
     
      if(i>=1&&j>=3){
      var paragraphText = par2.asText().getText();
      var newexp = paragraphText.toUpperCase();
      table.getRow(i).getChild(j).asText().setText(newexp);
      }
    

THIS IS WHAT IS USED TO CAPITALISE AN ELEMENT OF A TABLE IN DOCS !!