All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.thisptr.jackson.jq.internal.functions._SubImplFunction Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
package net.thisptr.jackson.jq.internal.functions;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.joni.Matcher;
import org.joni.Option;
import org.joni.Region;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.auto.service.AutoService;

import net.thisptr.jackson.jq.BuiltinFunction;
import net.thisptr.jackson.jq.Expression;
import net.thisptr.jackson.jq.Function;
import net.thisptr.jackson.jq.PathOutput;
import net.thisptr.jackson.jq.Scope;
import net.thisptr.jackson.jq.Version;
import net.thisptr.jackson.jq.exception.JsonQueryException;
import net.thisptr.jackson.jq.internal.misc.OnigUtils;
import net.thisptr.jackson.jq.internal.misc.Preconditions;
import net.thisptr.jackson.jq.path.Path;

@AutoService(Function.class)
@BuiltinFunction("_sub_impl/3")
public class _SubImplFunction implements Function {
	@Override
	public void apply(final Scope scope, final List args, final JsonNode in, final Path ipath, final PathOutput output, final Version version) throws JsonQueryException {
		Preconditions.checkInputType("_sub_impl/3", in, JsonNodeType.STRING);

		args.get(0).apply(scope, in, (regexText) -> {
			Preconditions.checkArgumentType("_sub_impl/3", 1, regexText, JsonNodeType.STRING);

			args.get(2).apply(scope, in, (flagsText) -> {
				Preconditions.checkArgumentType("_sub_impl/3", 3, flagsText, JsonNodeType.STRING);

				final OnigUtils.Pattern p = new OnigUtils.Pattern(regexText.asText(), flagsText.asText());
				final List match = match(scope.getObjectMapper(), p, in.asText());

				// This just repeats same emit()s the number of times as the number of flags. This is to emulate jq behavior (which is probably a bug).
				args.get(2).apply(scope, in, (dummy) -> {
					replaceAndConcat(scope, new Stack<>(), output, match, args.get(1), in, args.get(2));
				});
			});
		});
	}

	private void replaceAndConcat(Scope scope, Stack stack, PathOutput output, List match, Expression replaceExpr, final JsonNode in, final Expression flags) throws JsonQueryException {
		if (match.isEmpty()) {
			final StringBuilder sb = new StringBuilder();
			for (int i = stack.size() - 1; i >= 0; --i) {
				sb.append(stack.get(i));
			}
			output.emit(new TextNode(sb.toString()), null);
			return;
		}

		final JsonNode rhead = match.get(match.size() - 1);
		final List rtail = match.subList(0, match.size() - 1);

		if (rhead.isTextual()) {
			stack.push(rhead.textValue());
			replaceAndConcat(scope, stack, output, rtail, replaceExpr, in, flags);
			stack.pop();
		} else {
			replaceExpr.apply(scope, rhead, (replacement) -> {
				stack.push(replacement.asText());
				replaceAndConcat(scope, stack, output, rtail, replaceExpr, in, flags);
				stack.pop();
			});
		}
	}

	private static List match(final ObjectMapper mapper, final OnigUtils.Pattern pattern, final String inputText) {
		final List result = new ArrayList<>();

		final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
		final Matcher m = pattern.regex.matcher(inputBytes);
		int offset = 0;
		do {
			if (m.search(offset, inputBytes.length, Option.NONE) < 0)
				break;

			result.add(TextNode.valueOf(new String(inputBytes, offset, m.getBegin() - offset, StandardCharsets.UTF_8)));

			final ObjectNode captures = mapper.createObjectNode();
			final Region regions = m.getRegion();
			if (regions != null) {
				for (int i = 1; i < regions.numRegs; ++i) {
					final String name = pattern.names[i];
					if (name == null)
						continue;
					if (regions.beg[i] >= 0) {
						final String value = new String(inputBytes, regions.beg[i], regions.end[i] - regions.beg[i], StandardCharsets.UTF_8);
						captures.set(name, TextNode.valueOf(value));
					} else {
						captures.set(name, NullNode.getInstance());
					}
				}
			}

			result.add(captures);

			offset = m.getEnd();
		} while (pattern.global && offset != inputBytes.length);

		result.add(TextNode.valueOf(new String(inputBytes, offset, inputBytes.length - offset, StandardCharsets.UTF_8)));
		return result;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy