XPathParserTest.java

/*
 *    Copyright 2009-2025 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.io.Resources;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

class XPathParserTest {
  private final String resource = "nodelet_test.xml";

  // InputStream Source
  @Test
  void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {

    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream, false, null, null);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithInputStreamValidationVariables() throws IOException {
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream, false, null);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithInputStreamValidation() throws IOException {
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream, false);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithInputStream() throws IOException {
    try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
      XPathParser parser = new XPathParser(inputStream);
      testEvalMethod(parser);
    }
  }

  // Reader Source
  @Test
  void constructorWithReaderValidationVariablesEntityResolver() throws Exception {

    try (Reader reader = Resources.getResourceAsReader(resource)) {
      XPathParser parser = new XPathParser(reader, false, null, null);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithReaderValidationVariables() throws IOException {
    try (Reader reader = Resources.getResourceAsReader(resource)) {
      XPathParser parser = new XPathParser(reader, false, null);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithReaderValidation() throws IOException {
    try (Reader reader = Resources.getResourceAsReader(resource)) {
      XPathParser parser = new XPathParser(reader, false);
      testEvalMethod(parser);
    }
  }

  @Test
  void constructorWithReader() throws IOException {
    try (Reader reader = Resources.getResourceAsReader(resource)) {
      XPathParser parser = new XPathParser(reader);
      testEvalMethod(parser);
    }
  }

  // Xml String Source
  @Test
  void constructorWithStringValidationVariablesEntityResolver() throws Exception {
    XPathParser parser = new XPathParser(getXmlString(resource), false, null, null);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithStringValidationVariables() throws IOException {
    XPathParser parser = new XPathParser(getXmlString(resource), false, null);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithStringValidation() throws IOException {
    XPathParser parser = new XPathParser(getXmlString(resource), false);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithString() throws IOException {
    XPathParser parser = new XPathParser(getXmlString(resource));
    testEvalMethod(parser);
  }

  // Document Source
  @Test
  void constructorWithDocumentValidationVariablesEntityResolver() {
    XPathParser parser = new XPathParser(getDocument(resource), false, null, null);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithDocumentValidationVariables() {
    XPathParser parser = new XPathParser(getDocument(resource), false, null);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithDocumentValidation() {
    XPathParser parser = new XPathParser(getDocument(resource), false);
    testEvalMethod(parser);
  }

  @Test
  void constructorWithDocument() {
    XPathParser parser = new XPathParser(getDocument(resource));
    testEvalMethod(parser);
  }

  private Document getDocument(String resource) {
    try {
      InputSource inputSource = new InputSource(Resources.getResourceAsReader(resource));
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      String feature = null;
      try {
        feature = "http://xml.org/sax/features/external-parameter-entities";
        factory.setFeature(feature, false);

        feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
        factory.setFeature(feature, false);

        feature = "http://xml.org/sax/features/external-general-entities";
        factory.setFeature(feature, false);

        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);

        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

      } catch (ParserConfigurationException e) {
        throw new IllegalStateException("The feature '" + feature + "' is not supported by your XML processor.", e);
      }
      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      DocumentBuilder builder = factory.newDocumentBuilder();
      return builder.parse(inputSource);// already closed resource in builder.parse method
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

  private String getXmlString(String resource) throws IOException {
    try (BufferedReader bufferedReader = new BufferedReader(Resources.getResourceAsReader(resource))) {
      StringBuilder sb = new StringBuilder();
      String temp;
      while ((temp = bufferedReader.readLine()) != null) {
        sb.append(temp);
      }
      return sb.toString();
    }
  }

  enum EnumTest {
    YES, NO
  }

  private void testEvalMethod(XPathParser parser) {
    assertEquals((Long) 1970L, parser.evalLong("/employee/birth_date/year"));
    assertEquals((Long) 1970L, parser.evalNode("/employee/birth_date/year").getLongBody());
    assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
    assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
    assertEquals((Integer) 15, parser.evalNode("/employee/birth_date/day").getIntBody());
    assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
    assertEquals((Float) 5.8f, parser.evalNode("/employee/height").getFloatBody());
    assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
    assertEquals((Double) 5.8d, parser.evalNode("/employee/height").getDoubleBody());
    assertEquals((Double) 5.8d, parser.evalNode("/employee").evalDouble("height"));
    assertEquals("${id_var}", parser.evalString("/employee/@id"));
    assertEquals("${id_var}", parser.evalNode("/employee/@id").getStringBody());
    assertEquals("${id_var}", parser.evalNode("/employee").evalString("@id"));
    assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
    assertEquals(Boolean.TRUE, parser.evalNode("/employee/active").getBooleanBody());
    assertEquals(Boolean.TRUE, parser.evalNode("/employee").evalBoolean("active"));
    assertEquals(EnumTest.YES, parser.evalNode("/employee/active").getEnumAttribute(EnumTest.class, "bot"));
    assertEquals((Float) 3.2f, parser.evalNode("/employee/active").getFloatAttribute("score"));
    assertEquals((Double) 3.2d, parser.evalNode("/employee/active").getDoubleAttribute("score"));

    assertEquals("<id>\n  ${id_var}\n</id>", parser.evalNode("/employee/@id").toString().trim());
    assertEquals(7, parser.evalNodes("/employee/*").size());
    XNode node = parser.evalNode("/employee/height");
    assertEquals("employee/height", node.getPath());
    assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
  }

}