groue / GRMustache.swift

Flexible Mustache templates for Swift
http://mustache.github.com/
MIT License
597 stars 155 forks source link

Question: Apply filter without having value in JSON #30

Closed SentoCrespo closed 8 years ago

SentoCrespo commented 8 years ago

Let me put the simplest possible case:

I have something like this: {{ myMethod(THIS_IS_A_STRING) }}

And my JSON is empty: { }

If i define this filter:

let myMethod = Filter { (rendering: Rendering) in
                let value = String(rendering.string) **// Value is always "" !!**
                let result = value.WHATEVER_TRANSFORMATION_HERE
                return Rendering(result, rendering.contentType)
            }

I correctly register for the context like: template.registerInBaseContext("myMethod", Box(myMethod))

But when debugging, I don't get any value.

Maybe is a parser problem that if it doesn't match with something from the json is not passed to the filter. I really don't know but I don't know how I should implement this with this framework.

Maybe is this in MustacheBox.swift where is already empty if it's not present in the JSON:

if let value = self.value {
                        // Use the built-in Swift String Interpolation:
                        return Rendering("\(value)", .Text)
                    } else {
                        return Rendering("", .Text)
                    }

Any help would be much much appreciated.

groue commented 8 years ago

Hello @vCrespoP,

I'm not sure I fully understand your question.

You have a Swift variable named myMethod, a {{ myMethod(THIS_IS_A_STRING) }} mustache snippet, and you register something named translate under the name "translate". This does not match, so I'll assume that you instead register the myMethod Swift filter under the "myMethod" name. This makes more sense, I hope you'll agree.

OK so {{ myMethod(THIS_IS_A_STRING) }} triggers your filter, and you always get the empty string inside the filter function. As a conclusion, THIS_IS_A_STRING has an empty rendering.

Are your surprised by this? Did you expect THIS_IS_A_STRING to have a non-empty rendering? What did you expect? Did you try to confirm your expectation by rendering {{THIS_IS_A_STRING}} (without filter)?

SentoCrespo commented 8 years ago

Hi, sorry I updated my method you're completely right about the naming.

Maybe I didn't explain well enough and maybe I'm using incorrectly filters (most probably) too. What I think I really need is to define or override a parser. I have some keys that are not specified in the JSON I'm using, but I still need to make some transformations on them, such as custom translation for example. I know there's a goodie but I need some extra things there.

Of course if I render {{ VAR }} without filter works and gets displayed..

Let my try to rephrase myself: I need to process something raw inside {{ RAW_TEXT }} with some kind of method to get a result when parsing the template that doesn't depend on the given JSON data.

Could you point me to what to use to my current problem? I hope it's clearer now and I'd like to thank you for the quick response @groue

groue commented 8 years ago

OK I think I have a solution for you.

It is a box that has a value for any requested key.

let missingKeyHandler = MustacheBox(keyedSubscript: { (key: String) in
    return Box("<\(key)>")
})
let template = try! Template(string: "Hello {{ name }} {{ RAW_TEXT }} {{{ RAW_TEXT }}}")
template.extendBaseContext(missingKeyHandler)
// Hello Arthur &lt;RAW_TEXT&gt; <RAW_TEXT>
let rendering = try! template.render(Box(["name": "Arthur"]))

You can return any box from the missing key handler (a boxed string in the example). You'll get HTML-escaping, so be sure to use triple mustache if you want raw HTML rendering.

It will trigger only for missing keys.

SentoCrespo commented 8 years ago

Hello again mate, thanks once again for your time and help.

I saw what you did there but it doesn't really suit my needs. What I ended up doing was:

var rawContent: String { get }

Implement that in VariableTag.swift

    var rawContent: String {
        switch token.type {
        case .EscapedVariable(let content, _):
            return content
        default:
            return token.templateString
        }
    }

And in SectionTag.swift the one that may suit, I really let an empty getter.

Afterwards, I create an extension on Tag with the method I want to use:

extension Tag { 
    var translationKey: String {
        let content = rawContent
        let result = content.regexMatches(".*translate\\((.*)\\).*").first
        return result ?? ""
    }
}

And I can use as many methods as I want there.

A possible improvement would be to extend Tag to support method notation with a passed parameter like:

func getKeyForMethod(methodName: String) -> String {
        let content = rawContent
        let regexExpresion = ".*" + methodName + "\\((.*)\\).*"
        let result = content.regexMatches(regexExpresion).first
        return result ?? ""
    }
}

Then you'd use the following filter:

let translate = Filter { (dummy: Int?, info: RenderingInfo) in
                let tag = info.tag.translationKey
                let result = TRNLocalizer.localizedString(tag)
                return Rendering(result)
            }

I suppose this could be highly improved, but for now serves its purpose :)

I hope this is useful for anybody that needs something like I did.

Thanks again @groue and keep the good work up! Feel free to close this issue whenever you feel is fine :D

groue commented 8 years ago

OK @vCrespoP Have a nice day!