3
\$\begingroup\$

For Cardshifter TCG we pretty frequently update our list of available cards, so I tried to think of a way to make it easy to transform an exported JSON file containing all the cards into a decent HTML file that I can update on our website.

Each card's format looks like this:

{
  "MAX_HEALTH" : 10,
  "SICKNESS" : 1,
  "MANA_COST" : 30,
  "ATTACK" : 3,
  "HEALTH" : 10,
  "ATTACK_AVAILABLE" : 1,
  "flavor" : "The Great Grandfather, Emperor of all Deities, Vanquisher of Evil.",
  "name" : "JADE EMPEROR",
  "description" : "Give creatures of type Chinese owned by you on Battlefield 3 HEALTH\nGive creatures on Battlefield -1 ATTACK\n",
  "type" : "Chinese God"
}

Notice the \n character in the description field which separates multiple statements which are part of the same description.

I wrote a Groovy script to take this data and format it into HTML. I'm heavily using Groovy's GString interpolated expressions to assemble the data and HTML together. I'm using a little bit of RegEx & partial string matching so that card types like "Chinese God", "Chinese Hero" and "Chinese" all get included in one set, for example.

Bear in mind, we have not yet decided on and made styles for the tags and such, so you'll see some inline CSS; that is temporary.

import groovy.json.JsonSlurper

def filePath = "/Users/francisveilleux-gaboury/Downloads/"
def inputFileName = "game-1.json"
def outputFileName = "mythos-cards.html"

def inputFile = new File("$filePath$inputFileName")
def outputFile = new File("$filePath$outputFileName")

if (!inputFile.exists()) {
    throw new RuntimeException("File $inputFile does not exist.")
}

if (outputFile.exists()) {
    println "Overwriting existing file ${filePath}${outputFileName}"
} else {
    println "Creating file: ${filePath}${outputFileName}"
}

def nl = System.getProperty("line.separator")

outputFile.write """<html>
  <head>
    <title>Mythos card list</title>
  </head>
<body>
<h1>Mythos card list</h1>
"""

// HTML sections formatting
def cardTitle = '<h4 style ="font-size: 1.0em; color: green;">'
def endCardTitle = "</h4>"
def cardUL = "$nl<ul style=\"list-style-type: none;\">$nl"
def flavor = '<span style="font-style:italic;">'
def endFlavor = '<span>'

def json = new JsonSlurper().parse(inputFile)
def pattern
def matcher

outputFile.append "$nl<h2>Chinese mythology</h2>$nl$nl"
pattern = /(?i)^Chinese.*/

json.each {
    matcher = it."type" =~ pattern
    if (matcher.matchesPartially()) {
        outputFile.append "$cardTitle${it."name"} - ${it."ATTACK"} / ${it."HEALTH"}$endCardTitle$nl"
        outputFile.append "$cardUL"
        if (it."flavor" != null) {
            outputFile.append "  <li>$flavor\"${it."flavor"}\"$endFlavor</li>$nl"
        }
        outputFile.append "  $cardUL"
        if (it."type" != null) {
            outputFile.append "    <li>${it."type"}</li>$nl"
        }
        outputFile.append "    <li>Mana cost: ${it.MANA_COST}</li>$nl"
        if (it."ATTACK_AVAILABLE" == 1) {
            outputFile.append "    <li>Cannot attack</li>$nl"
        }
        if (it."SICKNESS" < 1) {
            outputFile.append "    <li>Rush</li>$nl"
        }
        outputFile.append "  </ul>$nl"
        if (it."description" != null) {
            outputFile.append "  <li>Effect(s): <br/>${it."description".replaceAll('\n', '<br/>')}</li>"
        }
        outputFile.append "</ul>$nl"
    }
}

outputFile.append "$nl<h2>Greek mythology</h2>$nl$nl"
pattern = /(?i)^Greek.*/

json.each {
    matcher = it."type" =~ pattern
    if (matcher.matchesPartially()) {
        outputFile.append "$cardTitle${it."name"} - ${it."ATTACK"} / ${it."HEALTH"}$endCardTitle$nl"
        outputFile.append "$cardUL"
        if (it."flavor" != null) {
            outputFile.append "  <li>$flavor\"${it."flavor"}\"$endFlavor</li>$nl"
        }
        outputFile.append "  $cardUL"
        if (it."type" != null) {
            outputFile.append "    <li>${it."type"}</li>$nl"
        }
        outputFile.append "    <li>Mana cost: ${it.MANA_COST}</li>$nl"
        if (it."ATTACK_AVAILABLE" == 1) {
            outputFile.append "    <li>Cannot attack</li>$nl"
        }
        if (it."SICKNESS" < 1) {
            outputFile.append "    <li>Rush</li>$nl"
        }
        outputFile.append "  </ul>$nl"
        if (it."description" != null) {
            outputFile.append "  <li>Effect(s): <br/>${it."description".replaceAll('\n', '<br/>')}</li>"
        }
        outputFile.append "</ul>$nl"
    }
}

// etc. for each other Mythology

It feels very repetitive to me, I'm hoping you can help DRY it. In the end, the output HTML looks like this:

<html>
  <head>
    <title>Mythos card list</title>
  </head>
<body>
<h1>Mythos card list</h1>

<h2>Chinese mythology</h2>


<h4 style ="font-size: 1.0em; color: green;">JADE EMPEROR - 3 / 10</h4>

<ul style="list-style-type: none;">
  <li><span style="font-style:italic;">"The Great Grandfather, Emperor of all Deities, Vanquisher of Evil."<span></li>
  <li><ul style="list-style-type: none;">
    <li>Chinese God</li>
    <li>Mana cost: 30</li>
    <li>Cannot attack</li>
  </li></ul>
  <li>Effect(s): <br/>Give creatures of type Chinese owned by you on Battlefield 3 HEALTH<br/>Give creatures on Battlefield -1 ATTACK<br/></li>
</ul>

<!-- etc. -->

Rendered:

enter image description here

\$\endgroup\$
1
\$\begingroup\$

The first thing that comes to my mind when I read your code is "this would be a good job for a template engine".

File constructors

def inputFile = new File("$filePath$inputFileName")
def outputFile = new File("$filePath$outputFileName")

Java's File class has a constructor that takes two string arguments, one for the path and one for the file name. Using this constructor is much more preferred in this case:

def inputFile = new File(filePath, inputFileName)
def outputFile = new File(filePath, outputFileName)

Getting the file path

Once you have created the file object, you can use getAbsolutePath() to get the full path, or as we're using Groovy - use the absolutePath property:

if (outputFile.exists()) {
    println "Overwriting existing file ${outputFile.absolutePath}"
} else {
    println "Creating file: ${outputFile.absolutePath}"
}

### var."property"

It's not necessary to use double quotes around the name of the property here. Simply using var.property should be enough. For example, it.description instead of it."description" (It shouldn't be necessary, but maybe it is necessary in this case)

Template Engine

As I mentioned, this would really be a good job for a template engine. Groovy comes with a couple of them, and I think that the MarkupTemplateEngine would be a good choice here.

I would at least use a template for the json card data, maybe a template for the full HTML site as well. Read the template documentation and play around with it. You can also see an example of how I have used it and a template I have written

There's so much to write about how to use a template engine in Groovy that I can't really write it all here, so I'm just pushing you in that direction this time.

\$\endgroup\$
  • \$\begingroup\$ Nice answer, thanks for all the information! On one note, I'm pretty sure I have tried to reference fields in the JSON without quotation marks and I was getting errors, so I read up on the JsonSlurper and found out apparently you do need quotation marks \$\endgroup\$ – Phrancis Jul 15 '15 at 15:07
3
\$\begingroup\$

To avoid the repetition in generating the html for each mythology, create a function that takes a title and a pattern and move inside the code that is currently duplicated for Chinese and Greek, something like this:

def appendMythos(title, pattern) {
    // the currently duplicated code
}

That way, you can generate the texts as simply as:

appendMythos("Greek mythology", /(?i)^Greek/)
appendMythos("Chinese mythology", /(?i)^Chinese/)

You don't need to declare variables line this:

def pattern
def matcher

It's better to declare them where you assign to them.

The way you match patterns is a bit tedious. You can simplify like this:

def pattern = /(?i)^Greek/
if (it."type" =~ pattern) {

Because in this expression the =~ operator will behave as a predicate, call find on the returned Matcher and coerce into a boolean.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.