/*
 * Copyright 2002-2020 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.springframework.util.xml;

import java.io.BufferedReader;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

Detects whether an XML stream is using DTD- or XSD-based validation.
Author:Rob Harrop, Juergen Hoeller, Sam Brannen
Since:2.0
/** * Detects whether an XML stream is using DTD- or XSD-based validation. * * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen * @since 2.0 */
public class XmlValidationModeDetector {
Indicates that the validation should be disabled.
/** * Indicates that the validation should be disabled. */
public static final int VALIDATION_NONE = 0;
Indicates that the validation mode should be auto-guessed, since we cannot find a clear indication (probably choked on some special characters, or the like).
/** * Indicates that the validation mode should be auto-guessed, since we cannot find * a clear indication (probably choked on some special characters, or the like). */
public static final int VALIDATION_AUTO = 1;
Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
/** * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration). */
public static final int VALIDATION_DTD = 2;
Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
/** * Indicates that XSD validation should be used (found no "DOCTYPE" declaration). */
public static final int VALIDATION_XSD = 3;
The token in a XML document that declares the DTD to use for validation and thus that DTD validation is being used.
/** * The token in a XML document that declares the DTD to use for validation * and thus that DTD validation is being used. */
private static final String DOCTYPE = "DOCTYPE";
The token that indicates the start of an XML comment.
/** * The token that indicates the start of an XML comment. */
private static final String START_COMMENT = "<!--";
The token that indicates the end of an XML comment.
/** * The token that indicates the end of an XML comment. */
private static final String END_COMMENT = "-->";
Indicates whether or not the current parse position is inside an XML comment.
/** * Indicates whether or not the current parse position is inside an XML comment. */
private boolean inComment;
Detect the validation mode for the XML document in the supplied InputStream. Note that the supplied InputStream is closed by this method before returning.
Params:
  • inputStream – the InputStream to parse
Throws:
See Also:
/** * Detect the validation mode for the XML document in the supplied {@link InputStream}. * Note that the supplied {@link InputStream} is closed by this method before returning. * @param inputStream the InputStream to parse * @throws IOException in case of I/O failure * @see #VALIDATION_DTD * @see #VALIDATION_XSD */
public int detectValidationMode(InputStream inputStream) throws IOException { // Peek into the file to look for DOCTYPE. try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { content = consumeCommentTokens(content); if (this.inComment || !StringUtils.hasText(content)) { continue; } if (hasDoctype(content)) { isDtdValidated = true; break; } if (hasOpeningTag(content)) { // End of meaningful data... break; } } return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); } catch (CharConversionException ex) { // Choked on some character encoding... // Leave the decision up to the caller. return VALIDATION_AUTO; } }
Does the content contain the DTD DOCTYPE declaration?
/** * Does the content contain the DTD DOCTYPE declaration? */
private boolean hasDoctype(String content) { return content.contains(DOCTYPE); }
Does the supplied content contain an XML opening tag. If the parse state is currently in an XML comment then this method always returns false. It is expected that all comment tokens will have consumed for the supplied content before passing the remainder to this method.
/** * Does the supplied content contain an XML opening tag. If the parse state is currently * in an XML comment then this method always returns false. It is expected that all comment * tokens will have consumed for the supplied content before passing the remainder to this method. */
private boolean hasOpeningTag(String content) { if (this.inComment) { return false; } int openTagIndex = content.indexOf('<'); return (openTagIndex > -1 && (content.length() > openTagIndex + 1) && Character.isLetter(content.charAt(openTagIndex + 1))); }
Consume all leading and trailing comments in the given String and return the remaining content, which may be empty since the supplied content might be all comment data.
/** * Consume all leading and trailing comments in the given String and return * the remaining content, which may be empty since the supplied content might * be all comment data. */
@Nullable private String consumeCommentTokens(String line) { int indexOfStartComment = line.indexOf(START_COMMENT); if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) { return line; } String result = ""; String currLine = line; if (indexOfStartComment >= 0) { result = line.substring(0, indexOfStartComment); currLine = line.substring(indexOfStartComment); } while ((currLine = consume(currLine)) != null) { if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) { return result + currLine; } } return null; }
Consume the next comment token, update the "inComment" flag and return the remaining content.
/** * Consume the next comment token, update the "inComment" flag * and return the remaining content. */
@Nullable private String consume(String line) { int index = (this.inComment ? endComment(line) : startComment(line)); return (index == -1 ? null : line.substring(index)); }
Try to consume the START_COMMENT token.
See Also:
/** * Try to consume the {@link #START_COMMENT} token. * @see #commentToken(String, String, boolean) */
private int startComment(String line) { return commentToken(line, START_COMMENT, true); } private int endComment(String line) { return commentToken(line, END_COMMENT, false); }
Try to consume the supplied token against the supplied content and update the in comment parse state to the supplied value. Returns the index into the content which is after the token or -1 if the token is not found.
/** * Try to consume the supplied token against the supplied content and update the * in comment parse state to the supplied value. Returns the index into the content * which is after the token or -1 if the token is not found. */
private int commentToken(String line, String token, boolean inCommentIfPresent) { int index = line.indexOf(token); if (index > - 1) { this.inComment = inCommentIfPresent; } return (index == -1 ? index : index + token.length()); } }