Przekonwertuj ciąg na ciąg szablonu

147

Czy jest możliwe utworzenie ciągu szablonu jako zwykłego ciągu?

let a="b:${b}";

a następnie przekonwertuj go na łańcuch szablonu

let b=10;
console.log(a.template());//b:10

bez eval, new Functioni inne środki dynamicznego generowania kodu?

KOLANICH
źródło
5
znalazłeś sposób, aby to osiągnąć? Być może pewnego dnia będę musiał to zrobić i jestem ciekawy, do czego doszedłeś.
Bryan Rayner
@BryanRayner powiedzmy, że Twój program js próbuje pobrać dane z REST API, którego adres URL znajduje się w pliku config.js jako ciąg „/ resources / <resource_id> / update /” i wstawiasz „resource_id” dynamicznie z programu . Jeśli nie chcesz podzielić tego adresu URL na części i zapisać w różnych obszarach, potrzebujesz pewnego rodzaju przetwarzania szablonu ciągu.
Ryu_hayabusa,
Zamiast używać eval, lepiej jest użyć do wyrażenia regularnego Eval, nie jest to zalecane i bardzo odradzane, więc nie używaj go developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…! niech b = 10; let a = "b: $ {b}"; let response = a.replace (/ \ $ {\ w +} /, b); conssole.log (odpowiedź);
Vijay Palaskar

Odpowiedzi:

79

Ponieważ ciąg szablonu musi pobierać odwołanie do bzmiennej dynamicznie (w czasie wykonywania), więc odpowiedź brzmi: NIE, nie da się obejść bez dynamicznego generowania kodu.

Ale evalto całkiem proste:

let tpl = eval('`'+a+'`');
alexpods
źródło
7
eval jest niebezpieczny, podobnie jak inne sposoby dynamicznego generowania kodu
KOLANICH
8
@KOLANICH Dla szczególności sprawa - cytuje ucieczka z powrotem w aciągu i będzie znacznie mniej niebezpieczny: let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. Myślę, że ważniejsze jest to, aby evaluniemożliwić kompilatorowi optymalizację kodu. Ale myślę, że nie ma to związku z tym pytaniem.
alexpods
3
W rzeczywistości możesz również uruchamiać funkcje wewnątrz ciągów szablonów.
KOLANICH
9
@KOLANICH Przepraszamy, że nie lubisz eval. Należy jednak pamiętać, że literał szablonu sam w sobie jest formą eval. Dwa przykłady: var test = Result: ${alert('hello')}; var test = Result: ${b=4}; Oba kończą się wykonaniem dowolnego kodu w kontekście skryptu. Jeśli chcesz zezwolić na dowolne ciągi znaków, równie dobrze możesz zezwolić eval.
Manngo
6
Bądź ostrożny. Ponieważ coś takiego jak Babel tego nie transponuje, ten kod NIE będzie działał w IE
cgsd
79

W moim projekcie stworzyłem coś takiego w ES6:

String.prototype.interpolate = function(params) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}

const template = 'Example text: ${text}';
const result = template.interpolate({
  text: 'Foo Boo'
});
console.log(result);

UPDATE Usunąłem zależność lodash, ES6 ma równoważne metody pobierania kluczy i wartości.

Mateusz Moska
źródło
1
Cześć, Twoje rozwiązanie działa świetnie, ale kiedy użyłem go w React Native (tryb budowania), wyświetla błąd: Nieprawidłowy znak '' ' , chociaż działa, gdy uruchamiam się w trybie debugowania. Wygląda na to, problem z Babel, jakaś pomoc?
Mohit Pandey
@MohitPandey Otrzymałem ten sam błąd, gdy testowałem ten kod pod PhantomJS i przechodził pod chrome. Jeśli tak jest, myślę, że jest w drodze nowa wersja beta PhantomJS z lepszym wsparciem dla ES6, możesz spróbować ją zainstalować.
Mateusz Moska
1
Niestety to nie działa i zapisałem wyrażenie regularne dla tego samego. Dodano również jako odpowiedź.
Mohit Pandey
to rozwiązanie działa tylko wtedy, gdy znak odwrotnego zaznaczenia "` "nie występuje w ciągu szablonu
SliverNinja - MSFT
Kiedy próbuję, mam ReferenceError: _ is not defined. Czy jest to kod nie ES6, ale lodashkonkretny, czy ...?
xpt
29

O co tu prosisz:

//non working code quoted from the question
let b=10;
console.log(a.template());//b:10

jest dokładnie równoważne (pod względem mocy i, hm, bezpieczeństwa) do eval: możliwości pobrania łańcucha zawierającego kod i wykonania tego kodu; a także możliwość zobaczenia przez wykonywany kod zmiennych lokalnych w środowisku wywołującego.

W JS nie ma możliwości, aby funkcja mogła zobaczyć zmienne lokalne w swoim programie wywołującym, chyba że ta funkcja jest eval(). Nawet Function()nie mogę tego zrobić.


Kiedy słyszysz, że w JavaScript pojawia się coś, co nazywa się „ciągami szablonów”, naturalne jest założenie, że jest to wbudowana biblioteka szablonów, taka jak Mustache. Tak nie jest. To głównie interpolacja ciągów znaków i ciągi wielowierszowe dla JS. Myślę jednak, że przez jakiś czas będzie to powszechne nieporozumienie. :(

Jason Orendorff
źródło
2
TBH myślałem, że to było. Byłoby bardzo, bardzo przydatne.
Bryan Rayner,
Czy to (nadal) działa? Dostaję template is not a function.
Ionică Bizău
2
Blok kodu na górze tej odpowiedzi to cytat z pytania. To nie działa.
Jason Orendorff,
27

Nie, nie da się tego zrobić bez dynamicznego generowania kodu.

Jednak stworzyłem funkcję, która zamieni zwykły ciąg znaków w funkcję, do której można dostarczyć mapę wartości, używając wewnętrznie ciągów szablonów.

Wygeneruj streszczenie ciągu szablonów

/**
 * Produces a function which uses template strings to do simple interpolation from objects.
 * 
 * Usage:
 *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
 * 
 *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
 *    // Logs 'Bryan is now the king of Scotland!'
 */
var generateTemplateString = (function(){
    var cache = {};

    function generateTemplate(template){
        var fn = cache[template];

        if (!fn){
            // Replace ${expressions} (etc) with ${map.expressions}.

            var sanitized = template
                .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                    return `\$\{map.${match.trim()}\}`;
                    })
                // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

            fn = Function('map', `return \`${sanitized}\``);
        }

        return fn;
    }

    return generateTemplate;
})();

Stosowanie:

var kingMaker = generateTemplateString('${name} is king!');

console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.

Mam nadzieję, że to komuś pomoże. Jeśli znajdziesz problem z kodem, prosimy o aktualizację Gist.

Bryan Rayner
źródło
Dzięki! Użyłem tego zamiast rozwiązania sprintf javascript.
seangwright
1
nie działa dla każdego var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))zwrotu szablonów/api/bar//
Guillaume Vincent
Dzięki, naprawione. Wyrażenie regularne zawierało jedno dopasowanie $ {param1} / $ {param2}, podczas gdy powinno to być dwa dopasowania.
Bryan Rayner
Zauważ, że ten nie działa w IE11 z powodu braku wsparcia dla back ticków.
s.meijer
1
Oczywiście, jeśli ciągi szablonów nie są obsługiwane przez przeglądarkę, ta metoda nie zadziała. Jeśli chcesz używać ciągów szablonów w nieobsługiwanych przeglądarkach, polecam użycie języka takiego jak TypeScript lub transpilera, takiego jak Babel; To jedyny sposób na zainstalowanie ES6 w starych przeglądarkach.
Bryan Rayner
9

TLDR: https://jsfiddle.net/w3jx07vt/

Wydaje się, że wszyscy martwią się o dostęp do zmiennych, dlaczego po prostu ich nie przekazać? Jestem pewien, że nie będzie zbyt trudno uzyskać kontekst zmiennej w programie wywołującym i przekazać go. Użyj tego https://stackoverflow.com/a/6394168/6563504, aby pobrać props z obj. Nie mogę teraz testować dla ciebie, ale to powinno działać.

function renderString(str,obj){
    return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}

Przetestowany. Oto pełny kod.

function index(obj,is,value) {
    if (typeof is == 'string')
        is=is.split('.');
    if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

function renderString(str,obj){
    return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)})
}

renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas
renderString('abc${a.c}asdas',{a:{c:22,d:55},b:44}) //abc22asdas
M3D
źródło
@ s.meijer, czy mógłbyś to rozwinąć? Z powodzeniem używam tego kodu. jsfiddle.net/w3jx07vt
M3D
1
Lepsze wyrażenie regularne pozwoliłoby ci nie wybierać ${}znaków. Spróbuj:/(?!\${)([^{}]*)(?=})/g
Eric Hodonsky,
@Relic jsfiddle.net/w3jx07vt/2 Nie mogłem sprawić, żeby to działało, możesz mi pomóc, a ja zaktualizuję swój post? :)
M3D,
Więc sposób, w jaki próbujesz to złapać, nie pomoże zbytnio, zamiast tego wykonałem zamianę ciągu. Zamiast dodawać krok interpolacji, mogę użyć łańcucha jako interpolacji lub łańcucha. Nie wyszukane, ale zadziałało.
Eric Hodonsky
8

Problem polega na tym, aby mieć funkcję, która ma dostęp do zmiennych swojego wywołującego. Właśnie dlatego widzimy bezpośrednie evalwykorzystanie do przetwarzania szablonów. Możliwym rozwiązaniem byłoby wygenerowanie funkcji pobierającej parametry formalne nazwane przez właściwości słownika i wywołującej ją z odpowiednimi wartościami w tej samej kolejności. Alternatywnym sposobem byłoby posiadanie czegoś prostego:

var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());

A dla każdego używającego kompilatora Babel musimy stworzyć zamknięcie, które zapamięta środowisko, w którym zostało utworzone:

console.log(new Function('name', 'return `' + message + '`;')(name));
didinko
źródło
Twój pierwszy fragment jest gorszy niż evaldlatego, że działa tylko ze namezmienną globalną
Bergi
@Bergi Twoje oświadczenie jest ważne - zakres funkcji zostanie utracony. Chciałem przedstawić proste rozwiązanie problemu i podałem uproszczony przykład tego, co można zrobić. Aby rozwiązać ten problem, można po prostu wymyślić: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return ;')();}
``
Nie, to jest dokładnie to, czego nie praca - new Functionnie ma dostępu do var namew templatefunkcji.
Bergi
Drugi wycinek rozwiązał mój problem ... Głosuj ode mnie! Dzięki, pomogło to rozwiązać tymczasowy problem, który mieliśmy z dynamicznym routingiem do elementu iframe :)
Kris Boyd,
7

Istnieje wiele dobrych rozwiązań, ale jeszcze żadne nie wykorzystywałoby metody ES6 String.raw . Oto moja inicjatywa. Ma ważne ograniczenie polegające na tym, że akceptuje tylko właściwości z przekazanego obiektu, co oznacza, że ​​żadne wykonanie kodu w szablonie nie zadziała.

function parseStringTemplate(str, obj) {
    let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
    let args = str.match(/[^{\}]+(?=})/g) || [];
    let parameters = args.map(argument => obj[argument] || (obj[argument] === undefined ? "" : obj[argument]));
    return String.raw({ raw: parts }, ...parameters);
}
let template = "Hello, ${name}! Are you ${age} years old?";
let values = { name: "John Doe", age: 18 };

parseStringTemplate(template, values);
// output: Hello, John Doe! Are you 18 years old?
  1. Podziel ciąg na nieargumentowe części tekstowe. Zobacz wyrażenie regularne .
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Podziel ciąg na nazwy właściwości. Pusta tablica, jeśli dopasowanie nie powiedzie się.
    args: ["name", "age"]
  3. Odwzoruj parametry z objwedług nazwy właściwości. Rozwiązanie jest ograniczone przez płytkie mapowanie jednopoziomowe. Niezdefiniowane wartości są zastępowane pustym łańcuchem, ale akceptowane są inne fałszywe wartości.
    parameters: ["John Doe", 18]
  4. Wykorzystaj String.raw(...)i zwróć wynik.
pekaaw
źródło
Z ciekawości, jaką wartość faktycznie dostarcza tutaj String.raw? Wygląda na to, że wykonujesz całą pracę polegającą na parsowaniu łańcucha i śledzeniu, jakie są podmiany. Czy to znacznie różni się od zwykłego .replace()wielokrotnego dzwonienia ?
Steve Bennett
Słuszna uwaga, @SteveBennett. Miałem problemy z przekształceniem zwykłego ciągu znaków w łańcuch wzorcowy i znalazłem rozwiązanie, samodzielnie budując surowy obiekt. Wydaje mi się, że redukuje String.raw do metody konkatenacji, ale myślę, że działa całkiem dobrze. Chciałbym jednak zobaczyć fajne rozwiązanie .replace():) Uważam, że czytelność jest ważna, więc sam używając wyrażeń regularnych staram się je nazywać, żeby wszystko to
zrozumiało
6

Podobna do odpowiedzi Daniela (i s.meijer za GIST ), ale bardziej czytelny:

const regex = /\${[^{]+}/g;

export default function interpolate(template, variables, fallback) {
    return template.replace(regex, (match) => {
        const path = match.slice(2, -1).trim();
        return getObjPath(path, variables, fallback);
    });
}

//get the specified property or nested property of an object
function getObjPath(path, obj, fallback = '') {
    return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}

Uwaga: To nieznacznie poprawia oryginał s.meijer, ponieważ nie będzie pasował do rzeczy takich jak ${foo{bar}(wyrażenie regularne dopuszcza tylko znaki niekręcone w nawiasach klamrowych wewnątrz ${i }).


AKTUALIZACJA: Poproszono mnie o przykład za pomocą tego, więc proszę:

const replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.', replacements)
Matt Browne
źródło
Czy możesz opublikować przykład faktycznie używający tego? Ten javascript jest trochę poza mną. Sugerowałbym wyrażenie regularne /\$\{(.*?)(?!\$\{)\}/g(do obsługi nawiasów klamrowych). Mam działające rozwiązanie, ale nie jestem pewien, czy jest tak przenośne, jak Twoje, więc chciałbym zobaczyć, jak należy to zaimplementować na stronie. Mój też używa eval().
Zwykły Joe
Poszedłem dalej i opublikowałem również odpowiedź i bardzo chciałbym poznać Twoją opinię na temat tego, jak zwiększyć bezpieczeństwo i wydajność: stackoverflow.com/a/48294208 .
Zwykły Joe
@RegularJoe Dodałem przykład. Moim celem było uproszczenie, ale masz rację, że jeśli chcesz obsługiwać zagnieżdżone nawiasy klamrowe, musisz zmienić wyrażenie regularne. Jednak nie mogę wymyślić dla tego przypadku użycia podczas oceny zwykłego ciągu, tak jakby był to literał szablonu (cały cel tej funkcji). Co miałeś na myśli?
Matt Browne,
Nie jestem też ekspertem w dziedzinie wydajności ani bezpieczeństwa; moja odpowiedź to po prostu połączenie dwóch poprzednich odpowiedzi. Ale powiem, że używanie evalpozostawia cię bardziej otwartym na możliwe błędy, które mogłyby spowodować problemy z bezpieczeństwem, podczas gdy moja wersja szuka właściwości obiektu ze ścieżki oddzielonej kropkami, która powinna być bezpieczna.
Matt Browne,
5

Możesz na przykład użyć prototypu łańcucha

String.prototype.toTemplate=function(){
    return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10

Ale odpowiedź na pierwotne pytanie nie jest możliwa.

sarkiroka
źródło
5

Spodobała mi się odpowiedź s.meijera i na jego podstawie napisałem własną wersję:

function parseTemplate(template, map, fallback) {
    return template.replace(/\$\{[^}]+\}/g, (match) => 
        match
            .slice(2, -1)
            .trim()
            .split(".")
            .reduce(
                (searchObject, key) => searchObject[key] || fallback || match,
                map
            )
    );
}
Daniel
źródło
1
Schludny! Naprawdę schludne!
xpt
4

Wymagałem tej metody z obsługą Internet Explorera. Okazało się, że tylne tiki nie są obsługiwane nawet przez IE11. Również; używanie evallub odpowiednik Functionnie wydaje się właściwe.

Dla tego, który zauważy; Używam również znaków odwrotnych, ale te są usuwane przez kompilatory, takie jak babel. Metody sugerowane przez inne metody zależą od nich w czasie wykonywania. Jak powiedziano wcześniej; jest to problem w IE11 i niższych.

Oto co wymyśliłem:

function get(path, obj, fb = `$\{${path}}`) {
  return path.split('.').reduce((res, key) => res[key] || fb, obj);
}

function parseTpl(template, map, fallback) {
  return template.replace(/\$\{.+?}/g, (match) => {
    const path = match.substr(2, match.length - 3).trim();
    return get(path, map, fallback);
  });
}

Przykładowe dane wyjściowe:

const data = { person: { name: 'John', age: 18 } };

parseTpl('Hi ${person.name} (${person.age})', data);
// output: Hi John (18)

parseTpl('Hello ${person.name} from ${person.city}', data);
// output: Hello John from ${person.city}

parseTpl('Hello ${person.name} from ${person.city}', data, '-');
// output: Hello John from -
s.meijer
źródło
„Używanie eval lub jego odpowiednika Funkcja nie wygląda dobrze”. … Tak… Zgadzam się, ale myślę, że jest to jeden z nielicznych przypadków użycia, w których można by powiedzieć „mmhkay, użyjmy tego”. Proszę sprawdzić jsperf.com/es6-string-tmpl - to mój praktyczny przypadek użycia. Używając swojej funkcji (z tym samym wyrażeniem regularnym co mój) i mine (eval + literały łańcuchowe). Dzięki! :)
Andrea Puddu,
@AndreaPuddu, Twoja wydajność jest rzeczywiście lepsza. Ale potem znowu; ciągi szablonów nie są obsługiwane w IE. Więc eval('`' + taggedURL + '`')po prostu nie działa.
s.meijer,
„Wydaje się” lepiej, powiedziałbym, ponieważ jest testowany w izolacji… Jedynym celem tego testu było sprawdzenie potencjalnych problemów z wydajnością eval. Odnośnie do literałów szablonu: dzięki za ponowne zwrócenie uwagi. Używam Babel do transpozycji mojego kodu, ale moja funkcja najwyraźniej nadal nie działa 😐
Andrea Puddu
3

Obecnie nie mogę komentować istniejących odpowiedzi, więc nie mogę bezpośrednio skomentować doskonałej odpowiedzi Bryana Raynora. Tak więc ta odpowiedź będzie aktualizować jego odpowiedź, wprowadzając niewielką korektę.

Krótko mówiąc, jego funkcja nie potrafi w rzeczywistości buforować utworzonej funkcji, więc zawsze zostanie odtworzona, niezależnie od tego, czy wcześniej widziała szablon. Oto poprawiony kod:

    /**
     * Produces a function which uses template strings to do simple interpolation from objects.
     * 
     * Usage:
     *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
     * 
     *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
     *    // Logs 'Bryan is now the king of Scotland!'
     */
    var generateTemplateString = (function(){
        var cache = {};

        function generateTemplate(template){
            var fn = cache[template];

            if (!fn){
                // Replace ${expressions} (etc) with ${map.expressions}.

                var sanitized = template
                    .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                        return `\$\{map.${match.trim()}\}`;
                    })
                    // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                    .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

                fn = cache[template] = Function('map', `return \`${sanitized}\``);
            }

            return fn;
        };

        return generateTemplate;
    })();
user2501097
źródło
3

@Mateusz Moska, rozwiązanie działa świetnie, ale gdy użyłem go w React Native (tryb budowania), wyświetla błąd: Nieprawidłowy znak '' ' , chociaż działa, gdy uruchamiam go w trybie debugowania.

Zapisałem więc własne rozwiązanie za pomocą wyrażenia regularnego.

String.prototype.interpolate = function(params) {
  let template = this
  for (let key in params) {
    template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key])
  }
  return template
}

const template = 'Example text: ${text}',
  result = template.interpolate({
    text: 'Foo Boo'
  })

console.log(result)

Demo: https://es6console.com/j31pqx1p/

UWAGA: Ponieważ nie znam głównej przyczyny problemu, zgłosiłem zgłoszenie w repozytorium react-native, https://github.com/facebook/react-native/issues/14107 , aby gdy mogli napraw / poprowadź mnie o tym samym :)

Mohit Pandey
źródło
to obsługuje szablony zawierające znak back-tick. Jednak zamiast próbować wymyślać wzór szablonowy, prawdopodobnie lepiej będzie po prostu użyć wąsów lub podobnych . w zależności od tego, jak skomplikowane są Twoje szablony, jest to podejście brutalnej siły, które nie uwzględnia przypadków skrajnych - klucz może zawierać specjalny wzorzec wyrażenia regularnego.
SliverNinja - MSFT
2

Wciąż dynamiczny, ale wydaje się bardziej kontrolowany niż zwykłe używanie nagiego evala:

const vm = require('vm')
const moment = require('moment')


let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}'
let context = {
  hours_worked:[{value:10}],
  hours_worked_avg_diff:[{value:10}],

}


function getDOW(now) {
  return moment(now).locale('es').format('dddd')
}

function gt0(_in, tVal, fVal) {
  return _in >0 ? tVal: fVal
}



function templateIt(context, template) {
  const script = new vm.Script('`'+template+'`')
  return script.runInNewContext({context, fns:{getDOW, gt0 }})
}

console.log(templateIt(context, template))

https://repl.it/IdVt/3

Robert Moskal
źródło
1

To rozwiązanie działa bez ES6:

function render(template, opts) {
  return new Function(
    'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' +
    ').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');'
  )();
}

render("hello ${ name }", {name:'mo'}); // "hello mo"

Uwaga: Functionkonstruktor jest zawsze tworzony w zasięgu globalnym, co może potencjalnie spowodować nadpisanie zmiennych globalnych przez szablon, np.render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});

cruzanmo
źródło
0

Ponieważ wymyślamy na nowo koło w czymś, co byłoby uroczą funkcją w javascript.

Używam eval(), co nie jest bezpieczne, ale javascript nie jest bezpieczny. Chętnie przyznaję, że nie jestem doskonały z javascriptem, ale miałem potrzebę i potrzebowałem odpowiedzi, więc ją zrobiłem.

Zdecydowałem się stylizować moje zmienne na @raczej niż an $, szczególnie dlatego, że chcę używać funkcji multilinii w literałach bez oceniania, aż będzie gotowa. Tak więc składnia zmiennej to@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

Nie jestem ekspertem od javascript, więc chętnie skorzystam z porad dotyczących ulepszeń, ale ...

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.length; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

Następuje bardzo prosta implementacja

myResultSet = {totalrecords: 2,
Name: ["Bob", "Stephanie"],
Age: [37,22]};

rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.`

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.totalrecords; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

W mojej rzeczywistej realizacji decyduję się na użycie @{{variable}}. Jeszcze jeden zestaw szelek. Absurdalnie mało prawdopodobne, aby spotkał się z tym niespodziewanie. Wyglądałoby to następująco/\@\{\{(.*?)(?!\@\{\{)\}\}/g

Aby było to łatwiejsze do odczytania

\@\{\{    # opening sequence, @{{ literally.
(.*?)     # capturing the variable name
          # ^ captures only until it reaches the closing sequence
(?!       # negative lookahead, making sure the following
          # ^ pattern is not found ahead of the current character
  \@\{\{  # same as opening sequence, if you change that, change this
)
\}\}      # closing sequence.

Jeśli nie masz doświadczenia z wyrażeniem regularnym, całkiem bezpieczną zasadą jest unikanie każdego znaku niealfanumerycznego i nie zawsze niepotrzebnie unikaj litery, ponieważ wiele takich znaków ma specjalne znaczenie dla praktycznie wszystkich odmian wyrażenia regularnego.

Zwykły Joe
źródło
0

Powinieneś wypróbować ten mały moduł JS autorstwa Andrei Giammarchi z github: https://github.com/WebReflection/backtick-template

/*! (C) 2017 Andrea Giammarchi - MIT Style License */
function template(fn, $str, $object) {'use strict';
  var
    stringify = JSON.stringify,
    hasTransformer = typeof fn === 'function',
    str = hasTransformer ? $str : fn,
    object = hasTransformer ? $object : $str,
    i = 0, length = str.length,
    strings = i < length ? [] : ['""'],
    values = hasTransformer ? [] : strings,
    open, close, counter
  ;
  while (i < length) {
    open = str.indexOf('${', i);
    if (-1 < open) {
      strings.push(stringify(str.slice(i, open)));
      open += 2;
      close = open;
      counter = 1;
      while (close < length) {
        switch (str.charAt(close++)) {
          case '}': counter -= 1; break;
          case '{': counter += 1; break;
        }
        if (counter < 1) {
          values.push('(' + str.slice(open, close - 1) + ')');
          break;
        }
      }
      i = close;
    } else {
      strings.push(stringify(str.slice(i)));
      i = length;
    }
  }
  if (hasTransformer) {
    str = 'function' + (Math.random() * 1e5 | 0);
    if (strings.length === values.length) strings.push('""');
    strings = [
      str,
      'with(this)return ' + str + '([' + strings + ']' + (
        values.length ? (',' + values.join(',')) : ''
      ) + ')'
    ];
  } else {
    strings = ['with(this)return ' + strings.join('+')];
  }
  return Function.apply(null, strings).apply(
    object,
    hasTransformer ? [fn] : []
  );
}

template.asMethod = function (fn, object) {'use strict';
  return typeof fn === 'function' ?
    template(fn, this, object) :
    template(this, fn);
};

Demo (wszystkie poniższe testy zwracają prawdę):

const info = 'template';
// just string
`some ${info}` === template('some ${info}', {info});

// passing through a transformer
transform `some ${info}` === template(transform, 'some ${info}', {info});

// using it as String method
String.prototype.template = template.asMethod;

`some ${info}` === 'some ${info}'.template({info});

transform `some ${info}` === 'some ${info}'.template(transform, {info});
colxi
źródło
0

Stworzyłem własne rozwiązanie, wykonując typ z opisem jako funkcją

export class Foo {
...
description?: Object;
...
}

let myFoo:Foo = {
...
  description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}

i tak robiąc:

let myDescription = myFoo.description('Bar', 'bar');
cesarmarinhorj
źródło
0

Zamiast używać eval, lepiej jest użyć wyrażenia regularnego

Eval nie jest zalecane i bardzo odradzane, więc nie używaj go ( mdn eval ).

 let b = 10;
 let a="b:${b}";

let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);
Vijay Palaskar
źródło