code: package com.taxonic.carml.logical_source_resolver;
  
  import com.taxonic.carml.model.LogicalSource;
  import com.taxonic.carml.model.XmlSource;
  import java.io.StringReader;
  import java.util.ArrayList;
  import java.util.List;
  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;
  import net.sf.saxon.s9api.XdmValue;
  
  public class XPathResolver implements LogicalSourceResolver<XdmItem> {
  	
  	private Processor xpathProcessor;
  	private XPathCompiler xpath;
  	private boolean autoNodeTextExtraction;
  	
  	public XPathResolver() {
  		this(true);
  	}
  	
  	public XPathResolver(boolean autoNodeTextExtraction) {
  		this.autoNodeTextExtraction = autoNodeTextExtraction;
  		this.xpathProcessor = new Processor(false);
  		this.xpath = xpathProcessor.newXPathCompiler();
  		this.xpath.setCaching(true);
  	}
  	
  	public boolean autoExtractsNodeText() {
  		return autoNodeTextExtraction;
  	}
  	
  	private void setNamespaces(LogicalSource logicalSource) {
  		Object source = logicalSource.getSource();
  		if (source instanceof XmlSource) {
  			((XmlSource)source).getDeclaredNamespaces()
  			.forEach(n -> xpath.declareNamespace(n.getPrefix(), n.getName()));
  		}
  	}
  	
  	@Override
  	public SourceIterator<XdmItem> getSourceIterator() {
  		return this::getIterableXpathResult;
  	}
  	
  	private Iterable<XdmItem> getIterableXpathResult(String source, LogicalSource logicalSource) {
  		DocumentBuilder documentBuilder = xpathProcessor.newDocumentBuilder();
  		StringReader reader = new StringReader(source);
  		setNamespaces(logicalSource);
  		
  		try {
  			XdmNode item = documentBuilder.build(new StreamSource(reader));
  			XPathSelector selector = xpath.compile(logicalSource.getIterator()).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);
  				XdmValue value = selector.evaluate();
  				
  				if (value.size() > 1) {
  					List<String> results = new ArrayList<>();
  					value.forEach(i -> {
  							String sValue = getItemStringValue(i, value);
  							if (sValue != null) {
  								results.add(sValue);
  							}
  						}
  					);
  					return Optional.of(results);
  				} else if (value.size() == 0) {
  					return Optional.empty();
  				}
  
  				XdmItem item = value.itemAt(0);
  				return Optional.ofNullable(getItemStringValue(item, value));
  				
  				
  			} catch (SaxonApiException e) {
  				throw new RuntimeException(String.format(
  						"Error applying XPath expression [%s] to entry [%s]", entry, expression), 
  						e);
  			}
  		};
  	}
  	
  	private String getItemStringValue(XdmItem item, XdmValue value) {
  		if (item.getStringValue().length() == 0) {
  			return null;
  		}
  
  		return autoNodeTextExtraction ? item.getStringValue() : value.toString();
  	}
  }
  