diff --git a/src/main/java/ch/njol/skript/effects/EffReplace.java b/src/main/java/ch/njol/skript/effects/EffReplace.java index ef2faf44282..4a16ba6e88b 100644 --- a/src/main/java/ch/njol/skript/effects/EffReplace.java +++ b/src/main/java/ch/njol/skript/effects/EffReplace.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript.effects; import ch.njol.skript.Skript; @@ -39,61 +21,74 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; +import java.util.regex.Pattern; @Name("Replace") -@Description("Replaces all occurrences of a given text with another text. Please note that you can only change variables and a few expressions, e.g. a message or a line of a sign.") -@Examples({"replace \"\" in {textvar} with \"%item%\"", - "replace every \"&\" with \"§\" in line 1", - "# The following acts as a simple chat censor, but it will e.g. censor mass, hassle, assassin, etc. as well:", - "on chat:", - " replace all \"kys\", \"idiot\" and \"noob\" with \"****\" in the message", - " ", - "replace all stone and dirt in player's inventory and player's top inventory with diamond"}) -@Since("2.0, 2.2-dev24 (replace in multiple strings and replace items in inventory), 2.5 (replace first, case sensitivity)") +@Description( + "Replaces all occurrences of a given text or regex with another text. Please note that you can only change " + + "variables and a few expressions, e.g. a message or a line of a sign." +) +@Examples({ + "replace \"\" in {_msg} with \"[%name of player's tool%]\"", + "replace every \"&\" with \"§\" in line 1 of targeted block", + "", + "# Very simple chat censor", + "on chat:", + "\treplace all \"idiot\" and \"noob\" with \"****\" in the message", + "\tregex replace \"\\b(idiot|noob)\\b\" with \"****\" in the message # Regex version using word boundaries for better results", + "", + "replace all stone and dirt in player's inventory and player's top inventory with diamond" +}) +@Since("2.0, 2.2-dev24 (multiple strings, items in inventory), 2.5 (replace first, case sensitivity), INSERT VERSION (regex)") public class EffReplace extends Effect { static { Skript.registerEffect(EffReplace.class, - "replace (all|every|) %strings% in %strings% with %string% [(1¦with case sensitivity)]", - "replace (all|every|) %strings% with %string% in %strings% [(1¦with case sensitivity)]", - "replace first %strings% in %strings% with %string% [(1¦with case sensitivity)]", - "replace first %strings% with %string% in %string% [(1¦with case sensitivity)]", - "replace (all|every|) %itemtypes% in %inventories% with %itemtype%", - "replace (all|every|) %itemtypes% with %itemtype% in %inventories%"); + "replace [(all|every)|first:[the] first] %strings% in %strings% with %string% [case:with case sensitivity]", + "replace [(all|every)|first:[the] first] %strings% with %string% in %strings% [case:with case sensitivity]", + "(replace [with|using] regex|regex replace) %strings% in %strings% with %string%", + "(replace [with|using] regex|regex replace) %strings% with %string% in %strings%", + "replace [all|every] %itemtypes% in %inventories% with %itemtype%", + "replace [all|every] %itemtypes% with %itemtype% in %inventories%"); } - - @SuppressWarnings("null") + private Expression haystack, needles, replacement; - private boolean replaceString = true; - private boolean replaceFirst = false; + private boolean replaceString; + private boolean replaceRegex; + private boolean replaceFirst; private boolean caseSensitive = false; - @SuppressWarnings({"null"}) @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - haystack = exprs[1 + matchedPattern % 2]; + public boolean init(Expression[] expressions, int matchedPattern, + Kleenean isDelayed, ParseResult parseResult) { + haystack = expressions[1 + matchedPattern % 2]; replaceString = matchedPattern < 4; - replaceFirst = matchedPattern > 1 && matchedPattern < 4; + replaceFirst = parseResult.hasTag("first"); + replaceRegex = matchedPattern == 2 || matchedPattern == 3; + if (replaceString && !ChangerUtils.acceptsChange(haystack, ChangeMode.SET, String.class)) { - Skript.error(haystack + " cannot be changed and can thus not have parts replaced."); + Skript.error(haystack + " cannot be changed and can thus not have parts replaced"); return false; } - if (SkriptConfig.caseSensitive.value() || parseResult.mark == 1) { + + if (SkriptConfig.caseSensitive.value() || parseResult.hasTag("case")) { caseSensitive = true; } - needles = exprs[0]; - replacement = exprs[2 - matchedPattern % 2]; + + needles = expressions[0]; + replacement = expressions[2 - matchedPattern % 2]; return true; } - - @SuppressWarnings("null") + @Override protected void execute(Event event) { Object[] needles = this.needles.getAll(event); - if (haystack instanceof ExpressionList) { - for (Expression haystackExpr : ((ExpressionList) haystack).getExpressions()) { + if (haystack instanceof ExpressionList list) { + for (Expression haystackExpr : list.getExpressions()) { replace(event, needles, haystackExpr); } } else { @@ -104,27 +99,46 @@ protected void execute(Event event) { private void replace(Event event, Object[] needles, Expression haystackExpr) { Object[] haystack = haystackExpr.getAll(event); Object replacement = this.replacement.getSingle(event); + if (replacement == null || haystack == null || haystack.length == 0 || needles == null || needles.length == 0) return; + if (replaceString) { - if (replaceFirst) { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replaceFirst((String)haystack[x], (String)n, Matcher.quoteReplacement((String)replacement), caseSensitive); + String stringReplacement = (String) replacement; + if (replaceRegex) { // replace all/first - regex + List patterns = new ArrayList<>(needles.length); + for (Object needle : needles) { + try { + patterns.add(Pattern.compile((String) needle)); + } catch (Exception ignored) { } + } + for (int i = 0; i < haystack.length; i++) { + for (Pattern pattern : patterns) { + Matcher matcher = pattern.matcher((String) haystack[i]); + if (replaceFirst) // first + haystack[i] = matcher.replaceFirst(stringReplacement); + else // all + haystack[i] = matcher.replaceAll(stringReplacement); + } + } + } else if (replaceFirst) { // replace first - string + for (int i = 0; i < haystack.length; i++) { + for (Object needle : needles) { + haystack[i] = StringUtils.replaceFirst((String) haystack[i], (String) needle, Matcher.quoteReplacement(stringReplacement), caseSensitive); } - } else { - for (int x = 0; x < haystack.length; x++) - for (Object n : needles) { - assert n != null; - haystack[x] = StringUtils.replace((String) haystack[x], (String) n, (String) replacement, caseSensitive); + } + } else { // replace all - string + for (int i = 0; i < haystack.length; i++) { + for (Object needle : needles) { + haystack[i] = StringUtils.replace((String) haystack[i], (String) needle, stringReplacement, caseSensitive); } + } } haystackExpr.change(event, haystack, ChangeMode.SET); } else { - for (Inventory inv : (Inventory[]) haystack) - for (ItemType needle : (ItemType[]) needles) - for (Map.Entry entry : inv.all(needle.getMaterial()).entrySet()) { + for (Inventory inventory : (Inventory[]) haystack) { + for (ItemType needle : (ItemType[]) needles) { + for (Map.Entry entry : inventory.all(needle.getMaterial()).entrySet()) { int slot = entry.getKey(); ItemStack itemStack = entry.getValue(); @@ -132,13 +146,15 @@ private void replace(Event event, Object[] needles, Expression haystackExpr) ItemStack newItemStack = ((ItemType) replacement).getRandom(); if (newItemStack != null) { newItemStack.setAmount(itemStack.getAmount()); - inv.setItem(slot, newItemStack); + inventory.setItem(slot, newItemStack); } } } + } + } } } - + @Override public String toString(@Nullable Event event, boolean debug) { SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug); @@ -146,6 +162,8 @@ public String toString(@Nullable Event event, boolean debug) { builder.append("replace"); if (replaceFirst) builder.append("the first"); + if (replaceRegex) + builder.append("regex"); builder.append(needles, "in", haystack, "with", replacement); if (caseSensitive) builder.append("with case sensitivity"); diff --git a/src/test/skript/tests/syntaxes/effects/EffReplace.sk b/src/test/skript/tests/syntaxes/effects/EffReplace.sk index 26c38b89690..63828c0218c 100644 --- a/src/test/skript/tests/syntaxes/effects/EffReplace.sk +++ b/src/test/skript/tests/syntaxes/effects/EffReplace.sk @@ -48,3 +48,26 @@ test "replace strings": assert {_list::1} is "(replaced)" with "Incorrect ExpressionList replacement value (1)" assert {_list::2} is "2" with "Incorrect ExpressionList replacement value (2)" assert {_list::3} is "(replaced)2" with "Incorrect ExpressionList replacement value (3)" + + # Regex replacing + set {_x} to "abc123aACAB" + regex replace "123a" in {_x} with "123" + assert {_x} is "abc123ACAB" with "regex replace failed" + regex replace "\d" with "x" in {_x} + assert {_x} is "abcxxxACAB" with "regex replace failed" + regex replace "^[acb]+" in {_x} with "y" + assert {_x} is "yxxxACAB" with "regex replace failed" + regex replace "(?i)[bAc]" in {_x} with "z" + assert {_x} is "yxxxzzzz" with "regex replace failed" + + set {_x} to "a" parsed as number + regex replace "a" in {_x} with "b" + assert {_x} is not set with "regex replace with invalid value succeeded" + + set {_y} to "a" + regex replace {_x} in {_y} with "b" + assert {_y} is "a" with "regex replace with invalid pattern succeeded" + regex replace {_x} in {_y} with {_x} + assert {_y} is "a" with "regex replace with invalid pattern and replacement succeeded" + regex replace "a" in {_y} with {_x} + assert {_y} is "a" with "regex replace with invalid pattern succeeded"