package com.taxonic.carml.logical_source_resolver;

import java.io.StringReader;
import java.util.Optional;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.s9api.DocumentBuilder;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;

public class XPathResolver implements LogicalSourceResolver<XdmItem> {
	
	Processor xpathProcessor;
	XPathCompiler xpath;
	
	public XPathResolver() {
		this.xpathProcessor = new Processor(false);
		this.xpath = xpathProcessor.newXPathCompiler();
		this.xpath.setCaching(true);
	}

	@Override
	public SourceIterator<XdmItem> getSourceIterator() {
		return this::getIterableXpathResult;
	}
	
	private Iterable<XdmItem> getIterableXpathResult(String source, String iteratorExpression) {
		DocumentBuilder documentBuilder = xpathProcessor.newDocumentBuilder();
		StringReader reader = new StringReader(source);
		try {
			XdmNode item = documentBuilder.build(new StreamSource(reader));
			XPathSelector selector = xpath.compile(iteratorExpression).load();
			selector.setContextItem(item);
			return selector;
		} catch (SaxonApiException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public ExpressionEvaluatorFactory<XdmItem> getExpressionEvaluatorFactory() {
		return entry -> expression -> {
			try {
				XPathSelector selector = xpath.compile(expression).load();
				selector.setContextItem(entry);
				XdmItem value = selector.evaluateSingle();
				return Optional.ofNullable(value.getStringValue());
			} catch (SaxonApiException e) {
				throw new RuntimeException(String.format(
						"Error applying XPath expression [%s] to entry [%s]", entry, expression), 
						e);
			}
		};
	}
}

@@ -501,16 +501,24 @@
 m%3E %7B%0A%09%0A%09
+private 
 Processo
@@ -536,16 +536,24 @@
 essor;%0A%09
+private 
 XPathCom
@@ -570,34 +570,206 @@
 h;%0A%09
-%0A%09public XPathResolver() %7B
+private boolean autoNodeTextExtraction;%0A%09%0A%09public XPathResolver() %7B%0A%09%09this(true);%0A%09%7D%0A%09%0A%09public XPathResolver(boolean autoNodeTextExtraction) %7B%0A%09%09this.autoNodeTextExtraction = autoNodeTextExtraction;
 %0A%09%09t
@@ -891,24 +891,104 @@
 g(true);%0A%09%7D%0A
+%09%0A%09public boolean autoExtractsNodeText() %7B%0A%09%09return autoNodeTextExtraction;%0A%09%7D%0A%09
 %0A%09@Override%0A
@@ -1863,59 +1863,199 @@
 %09%09%09%09
-return Optional.ofNullable(value.getStringValue());
+%0A%09%09%09%09if (value == null) %7B%0A%09%09%09%09%09return Optional.empty();%0A%09%09%09%09%7D%0A%09%09%09%09%0A%09%09%09%09String result = autoNodeTextExtraction ? value.getStringValue() : value.toString();%0A%09%09%09%09return Optional.of(result);%0A%09%09%09%09
 %0A%09%09%09

add null check and auto node text extraction control