简体   繁体   中英

Generic Template String like in Python in Dart

In python, I often use strings as templates, eg

templateUrl = '{host}/api/v3/{container}/{resourceid}'  
params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}  
api.get(templateUrl.format(**params))  

This allows for easy base class setup and the like. How can I do the same in dart?

I'm assuming I will need to create a utility function to parse the template and substitute manually but really hoping there is something ready to use.

Perhaps a TemplateString class with a format method that takes a Map of name/value pairs to substitute into the string.

Note: the objective is to have a generic "format" or "interpolation" function that doesn't need to know in advance what tags or names will exist in the template.

Further clarification: the templates themselves are not resolved when they are set up. Specifically, the template is defined in one place in the code and then used in many other places.

Dart does not have a generic template string functionality that would allow you to insert values into your template at runtime .
Dart only allows you to interpolate strings with variables using the $ syntax in strings, eg var string = '$domain/api/v3/${actions.get}' . You would need to have all the variables defined in your code beforehand.

However, you can easily create your own implementation.

Implementation

You pretty much explained how to do it in your question yourself: you pass a map and use it to have generic access to the parameters using the [] operator.

To convert the template string into something that is easy to access, I would simply create another List containing fixed components, like /api/v3/ and another Map that holds generic components with their name and their position in the template string.

class TemplateString {
  final List<String> fixedComponents;
  final Map<int, String> genericComponents;

  int totalComponents;

  TemplateString(String template)
      : fixedComponents = <String>[],
        genericComponents = <int, String>{},
        totalComponents = 0 {
    final List<String> components = template.split('{');

    for (String component in components) {
      if (component == '') continue; // If the template starts with "{", skip the first element.

      final split = component.split('}');

      if (split.length != 1) {
        // The condition allows for template strings without parameters.
        genericComponents[totalComponents] = split.first;
        totalComponents++;
      }

      if (split.last != '') {
        fixedComponents.add(split.last);
        totalComponents++;
      }
    }
  }

  String format(Map<String, dynamic> params) {
    String result = '';

    int fixedComponent = 0;
    for (int i = 0; i < totalComponents; i++) {
      if (genericComponents.containsKey(i)) {
        result += '${params[genericComponents[i]]}';
        continue;
      }
      result += fixedComponents[fixedComponent++];
    }

    return result;
  }
}

Here would be an example usage, I hope that the result is what you expected:

main() {
  final templateUrl = TemplateString('{host}/api/v3/{container}/{resourceid}');
  final params = <String, dynamic>{'host': 'www.api.com', 'container': 'books', 'resourceid': 10};

  print(templateUrl.format(params)); // www.api.com/api/v3/books/10
}

Here it is as a Gist .

Here is my solution:

extension StringFormating on String {
  String format(List<String> values) {
    int index = 0;
    return replaceAllMapped(new RegExp(r'{.*?}'), (_) {
      final value = values[index];
      index++;
      return value;
    });
  }

  String formatWithMap(Map<String, String> mappedValues) {
    return replaceAllMapped(new RegExp(r'{(.*?)}'), (match) {
      final mapped = mappedValues[match[1]];
      if (mapped == null)
        throw ArgumentError(
            '$mappedValues does not contain the key "${match[1]}"');
      return mapped;
    });
  }
}

This gives you a very similar functionality to what python offers:

"Test {} with {}!".format(["it", "foo"]);
"Test {a} with {b}!".formatWithMap({"a": "it", "b": "foo"})

both return "Test it with foo!"

It's even more easy in Dart. Sample code below :

String host = "www.api.com"
String container = "books"
int resourceId = 10

String templateUrl = "$host/api/v3/$container/${resourceId.toString()}"

With the map, you can do as follows :

Map<String, String> params = {'host': 'www.api.com', 'container': 'books', 'resourceid': 10}  

String templateUrl = "${params['host']}/api/v3/${params['container']}/${params['resourceId']}"

Note : The above code defines Map as <String, String> . You might want <String, Dynamic> (and use .toString() )

Wouldn't it be simplest to just make it a function with named arguments? You could add some input validation if you wanted to.

String templateUrl({String host = "", String container = "", int resourceid = 0 }) {
    return "$host/api/v3/$container/$resourceId";
}

void main() {
    api.get(templateUrl(host:"www.api.com", container:"books", resourceid:10));    
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM