jmustache

jmustache

Java实现的Mustache模板语言库

jmustache是Java实现的Mustache模板语言库,具有零依赖、跨平台兼容和Proguard友好等特点。该库提供简洁API,支持部分渲染、Lambda表达式和自定义格式化。jmustache性能良好,可通过Maven Central集成。作为Java模板引擎,jmustache功能强大且易于使用。

MustacheJava模板JMustache模板引擎无依赖Github开源项目

这是一个Java实现的Mustache模板语言

构建状态

动机

  • 零依赖。你可以在项目中只包含这一个小型库就可以开始使用模板。

  • 可用于多种目标平台。该实现对JVM的要求非常有限,因此可以在Android或其他受限JVM上使用。甚至可以避免使用反射,而将所有数据作为一系列嵌套映射提供。

  • ProguardJarJar友好。虽然库会反射性地访问你的数据(如果你需要),但库内部不会使用其他反射或按名称实例化类。因此,你可以使用Proguard或JarJar嵌入它而不会遇到任何烦人的意外。

  • 最小API占用。你真正只需要了解两个方法:compileexecute。在性能不重要的情况下,你甚至可以将它们链接在一起。

在上述动机的基础上,该实现还努力提供额外的好处:

  • 它可通过Maven Central获得,详情见下文。
  • 它具有相当不错的性能。模板的解析与执行是分开的。模板会根据(上下文类,名称)对来专门化其变量,这样如果变量首次解析为(例如)上下文对象的字段,那么在后续的模板调用中会直接尝试这种方式,只有在访问变量作为字段失败时才会尝试较慢的完整解析。

获取

JMustache可通过Maven Central获得,因此可以通过添加依赖com.samskivert:jmustache:1.15轻松添加到你的Maven、Ivy等项目中。或者下载预构建的jar文件

文档

除了下面的使用部分,以下文档可能有用:

使用

使用JMustache非常简单。将你的模板作为StringReader提供,就可以得到一个可以在任何上下文中执行的Template:

String text = "一、二、{{three}}。三,先生!"; Template tmpl = Mustache.compiler().compile(text); Map<String, String> data = new HashMap<String, String>(); data.put("three", "五"); System.out.println(tmpl.execute(data)); // 结果: "一、二、五。三,先生!"

如果你在做更严肃的事情,可以使用ReaderWriter:

void executeTemplate (Reader template, Writer out, Map<String, String> data) { Mustache.compiler().compile(template).execute(data, out); }

执行上下文可以是任何Java对象。变量将通过以下机制解析:

  • 如果上下文是MustacheCustomContext,将使用MustacheCustomContext.get
  • 如果上下文是Map,将使用Map.get
  • 如果存在与变量同名的非void方法,将调用该方法。
  • 如果存在名为(对于变量foo)getFoo的非void方法,将调用该方法。
  • 如果存在与变量同名的字段,将使用其内容。

示例:

class Person { public final String name; public Person (String name, int age) { this.name = name; _age = age; } public int getAge () { return _age; } protected int _age; } String tmpl = "{{#persons}}{{name}}: {{age}}\n{{/persons}}"; Mustache.compiler().compile(tmpl).execute(new Object() { Object persons = Arrays.asList(new Person("Elvis", 75), new Person("Madonna", 52)); }); // 结果: // Elvis: 75 // Madonna: 52

如你所见,字段(和方法)无需是public。在创建的匿名类中作为上下文的persons字段是可访问的。注意,在沙箱安全环境中,使用非公开字段将不起作用。

区段的行为如你所期望的:

  • Boolean值启用或禁用该区段。
  • 数组、IteratorIterable值会重复执行该区段,每次迭代使用每个元素作为上下文。空集合会导致该区段在模板中被包含零次。
  • 不可解析或null值被视为false。可以通过使用strictSections()来更改此行为。详见_默认值_。
  • 任何其他对象都会导致该区段执行一次,以该对象作为上下文。

请参阅MustacheTest.java中的代码以获取具体示例。另请参阅Mustache文档以了解模板语法的详细信息。

局部模板

如果你想使用局部模板(例如{{>subtmpl}}),在创建编译器时必须提供一个Mustache.TemplateLoader。例如:

final File templateDir = ...; Mustache.Compiler c = Mustache.compiler().withLoader(new Mustache.TemplateLoader() { public Reader getTemplate (String name) { return new FileReader(new File(templateDir, name)); } }); String tmpl = "...{{>subtmpl}}..."; c.compile(tmpl).execute();

上面的代码片段在编译模板时将加载new File(templateDir, "subtmpl")

Lambda表达式

JMustache通过向你传递一个Template.Fragment实例来实现lambda表达式,你可以用它来执行传递给lambda的模板片段。你可以装饰片段执行的结果,如标准Mustache文档中关于lambda的示例所示:

String tmpl = "{{#bold}}{{name}}真棒。{{/bold}}"; Mustache.compiler().compile(tmpl).execute(new Object() { String name = "威利"; Mustache.Lambda bold = new Mustache.Lambda() { public void execute (Template.Fragment frag, Writer out) throws IOException { out.write("<b>"); frag.execute(out); out.write("</b>"); } }; }); // 结果: <b>威利真棒。</b>

你还可以获取片段执行的结果来进行国际化或缓存等操作:

Object ctx = new Object() { Mustache.Lambda i18n = new Mustache.Lambda() { public void execute (Template.Fragment frag, Writer out) throws IOException { String key = frag.execute(); String text = // 在i18n系统中查找key out.write(text); } }; }; // 模板可能看起来像这样: <h2>{{#i18n}}title{{/i18n}}</h2> {{#i18n}}welcome_msg{{/i18n}}

还有对反编译(取消执行)模板并获取区段中包含的原始Mustache模板文本的有限支持。有关限制的详细信息,请参阅Template.Fragment的文档。

默认值

默认情况下,每当无法解析变量或变量解析为null时(区段除外,见下文),都会抛出异常。你可以通过两种方式更改此行为。如果你想在所有此类情况下提供一个值,请使用defaultValue():

String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?"; Mustache.compiler().defaultValue("什么").compile(tmpl).execute(new Object() { String exists = "说"; String nullValued = null; // String doesNotExist }); // 结果: 说 什么 什么?

如果你只想为解析为null的变量提供默认值,并希望在无法解析变量的情况下保留异常,请使用nullValue():

String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?"; Mustache.compiler().nullValue("什么").compile(tmpl).execute(new Object() { String exists = "说"; String nullValued = null; // String doesNotExist }); // 执行模板时会抛出MustacheException,因为无法解析doesNotExist

当使用Map作为上下文时,nullValue()仅在map包含映射到null的情况下使用。如果map缺少给定变量的映射,则认为它不可解析并抛出异常。

Map<String,String> map = new HashMap<String,String>(); map.put("exists", "说"); map.put("nullValued", null); // 没有"doesNotExist"的映射 String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?"; Mustache.compiler().nullValue("什么").compile(tmpl).execute(map); // 执行模板时会抛出MustacheException,因为无法解析doesNotExist

不要在编译器配置中同时使用defaultValuenullValue。每个都会覆盖另一个,所以无论你最后调用哪个,都会得到那个行为。即使你意外地做对了,代码也会很混乱,所以不要同时调用两者,使用其中一个。

区段

区段不受nullValue()defaultValue()设置的影响。它们的行为由单独的配置strictSections()控制。

默认情况下,不可解析或解析为null的区段将被省略(相反,不可解析或解析为null的反向区段将被包含)。如果你使用strictSections(true),引用不可解析值的区段将始终抛出异常。无论strictSections()设置如何,引用可解析但为null值的区段永远不会抛出异常。

扩展

JMustache用一些额外的功能扩展了基本的Mustache模板语言。这些附加功能列举如下:

默认不转义HTML

你可以在获取编译器时更改默认的HTML转义行为:

Mustache.compiler().escapeHTML(false).compile("{{foo}}").execute(new Object() { String foo = "<bar>"; }); // 结果: <bar> // 而不是: &lt;bar&gt;

用户定义的对象格式化

默认情况下,JMustache使用String.valueOf在渲染模板时将对象转换为字符串。你可以通过实现Mustache.Formatter接口来自定义此格式化:

Mustache.compiler().withFormatter(new Mustache.Formatter() { public String format (Object value) { if (value instanceof Date) return _fmt.format((Date)value); else return String.valueOf(value); } protected DateFormat _fmt = new SimpleDateFormat("yyyy/MM/dd"); }).compile("{{msg}}: {{today}}").execute(new Object() { String msg = "日期"; Date today = new Date(); }) // 结果: 日期: 2013/01/08

用户定义的转义规则

你可以在获取编译器时更改转义行为,以支持HTML和纯文本以外的文件格式。

如果你只需要替换文本中的固定字符串,可以使用Escapers.simple:

String[][] escapes = {{ "[", "[[" }, { "]", "]]" }}; Mustache.compiler().withEscaper(Escapers.simple(escapes)). compile("{{foo}}").execute(new Object() { String foo = "[bar]"; }); // 结果: [[bar]]

或者你可以直接实现Mustache.Escaper接口以更好地控制转义过程。

特殊变量

this

你可以使用特殊变量this来引用上下文对象本身,而不是它的某个成员。这在迭代列表时特别有用。

Mustache.compiler().compile("{{this}}").execute("你好"); // 返回: 你好 Mustache.compiler().compile("{{#names}}{{this}}{{/names}}").execute(new Object() { List<String> names () { return Arrays.asList("汤姆", "迪克", "哈利"); } }); // 结果: 汤姆迪克哈利

请注意,你也可以使用特殊变量.来表示相同的意思。

Mustache.compiler().compile("{{.}}").execute("你好"); // 返回: 你好 Mustache.compiler().compile("{{#names}}{{.}}{{/names}}").execute(new Object() { List<String> names () { return Arrays.asList("汤姆", "迪克", "哈利"); } }); // 结果: 汤姆迪克哈利

.显然受其他Mustache实现支持,尽管它没有出现在官方文档中。

-first和-last

你可以使用特殊变量-first-last对列表元素进行特殊处理。当在处理列表元素中的第一个元素的区段内时,-first解析为true。在所有其他时候,它解析为false。当在处理列表元素中的最后一个元素的区段内时,`-last

Mustache.compiler().compile("你好 {{field.who}}!").execute(new Object() { public Object field = new Object() { public String who () { return "世界"; } } }); // 结果: 你好 世界!

通过利用反射和Bean属性风格的查找,你可以做一些奇怪的事情:

Mustache.compiler().compile("你好 {{class.name}}!").execute(new Object()); // 结果: 你好 java.lang.Object!

注意,复合变量本质上是使用单一部分的简写形式。上面的例子也可以表示为:

你好 {{#field}}{{who}}{{/field}}!
你好 {{#class}}{{name}}{{/class}}!

还要注意,嵌套的单一部分和复合变量之间存在一个语义差异:在解析复合变量的第一个组件的对象后,在解析子组件时不会搜索父上下文。

换行符修剪

如果开始或结束部分标签是一行中唯一的内容,则会修剪标签周围的任何空白和后面的行终止符。这允许文明的模板,比如:

最喜欢的食物: <ul> {{#people}} <li>{{first_name}} {{last_name}} 喜欢 {{favorite_food}}。</li> {{/people}} </ul>

它会产生如下输出:

最喜欢的食物: <ul> <li>埃尔维斯 普雷斯利 喜欢 花生酱。</li> <li>圣雄 甘地 喜欢 阿卢杜姆。</li> </ul>

而不是:

最喜欢的食物: <ul> <li>埃尔维斯 普雷斯利 喜欢 花生酱。</li> <li>圣雄 甘地 喜欢 阿卢杜姆。</li> </ul>

如果没有换行符修剪,就会产生这样的结果。

嵌套上下文

如果在嵌套上下文中找不到变量,它会在外层上下文中解析。这允许以下用法:

String template = "{{outer}}:\n{{#inner}}{{outer}}.{{this}}\n{{/inner}}"; Mustache.compiler().compile(template).execute(new Object() { String outer = "foo"; List<String> inner = Arrays.asList("bar", "baz", "bif"); }); // 结果: // foo: // foo.bar // foo.baz // foo.bif

注意,如果变量在内部上下文中定义,它会覆盖外部上下文中的同名变量。目前没有办法访问外部上下文中的变量。

可反转的Lambda

对于某些应用程序,Lambda在反向部分执行而不是完全省略该部分可能很有用。这允许在将模板静态转换为其他语言或上下文时进行适当的条件替换:

String template = "{{#condition}}结果为真{{/condition}}\n" + "{{^condition}}结果为假{{/condition}}"; Mustache.compiler().compile(template).execute(new Object() { Mustache.InvertibleLambda condition = new Mustache.InvertibleLambda() { public void execute (Template.Fragment frag, Writer out) throws IOException { // 当Lambda在正常部分中被引用时执行此方法 out.write("if (condition) {console.log(\""); out.write(toJavaScriptLiteral(frag.execute())); out.write("\")}"); } public void executeInverse (Template.Fragment frag, Writer out) throws IOException { // 当Lambda在反向部分中被引用时执行此方法 out.write("if (!condition) {console.log(\""); out.write(toJavaScriptLiteral(frag.execute())); out.write("\")}"); } private String toJavaScriptLiteral (String execute) { // 注意:这不是JavaScript字符串字面量转义的完整实现 return execute.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\""); } }; }); // 结果: // if (condition) {console.log("结果为真")} // if (!condition) {console.log("结果为假")}

当然,你不仅限于条件替换 - 只要需要具有两种操作模式的单个函数,就可以使用InvertibleLambda。

标准模式

在创建编译器时,可以禁用这些扩展中更具侵入性的部分,特别是搜索父上下文和使用复合变量,如下所示:

Map<String,String> ctx = new HashMap<String,String>(); ctx.put("foo.bar", "baz"); Mustache.compiler().standardsMode(true).compile("{{foo.bar}}").execute(ctx); // 结果: baz

线程安全

JMustache在内部是线程安全的,但有以下注意事项:

  • 编译:编译模板会调用各种辅助类: Mustache.Formatter, Mustache.Escaper, Mustache.TemplateLoader, Mustache.Collector。这些类的默认实现是线程安全的,但如果你提供自定义实例,则必须确保你的自定义实例是线程安全的。

  • 执行:执行模板可能会调用一些辅助类:Mustache.Lambda, Mustache.VariableFetcher。这些类的默认实现是线程安全的,但如果你提供自定义实例,则必须确保你的自定义实例是线程安全的。

  • 上下文数据:如果在执行模板时修改传递给模板执行的上下文数据,则会引入竞态条件。理论上可以使用线程安全的映射(ConcurrentHashMapCollections.synchronizedMap)作为上下文数据,这将允许你在基于该数据渲染模板的同时修改数据,但这样做是在玩火。我不建议这样做。如果你的数据是作为POJO提供的,其中字段或方法通过反射被调用以填充模板,那么volatile字段和同步方法同样可以用来支持同时读取和修改,但是你很容易犯错误引入竞态条件或在执行模板时导致奇怪的行为。当通过同时运行的线程渲染相同的模板时,最安全的方法是将不可变/不变的数据作为每次执行的上下文传递。

  • VariableFetcher缓存:模板执行使用一个内部缓存来存储已解析的VariableFetcher实例(因为解析变量获取器是昂贵的)。这个缓存通过使用ConcurrentHashMap来实现线程安全。如果两个线程同时解析同一个变量,可能会做一些额外的工作,但它们不会相互冲突,它们只会都解析该变量,而不是一个解析变量而另一个使用缓存的解析结果。

所以总结是:只要你提供的所有辅助类都是线程安全的(或者你使用默认值),在线程之间共享一个Mustache.Compiler实例来编译模板是安全的。如果你在执行时向模板传递不可变数据,那么让多个线程同时执行单个Template实例是安全的。

限制

为了简单起见,省略或简化了Mustache的一些功能:

  • {{= =}}只支持一个或两个字符的分隔符。这只是因为我很懒,它简化了解析器。

编辑推荐精选

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

下拉加载更多