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

org.gradle.internal.xml.SimpleMarkupWriter Maven / Gradle / Ivy

There is a newer version: 8.6
Show newest version
/*
 * Copyright 2013 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
 *
 *      http://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.gradle.internal.xml;

import org.gradle.internal.SystemProperties;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.Deque;

/**
 * 

A streaming markup writer. Encodes characters and CDATA. Provides only basic state validation, and some simple indentation.

* *

This class also is-a {@link Writer}, and any characters written to this writer will be encoded as appropriate. Note, however, that * calling {@link #close()} on this object does not close the backing stream. *

*/ public class SimpleMarkupWriter extends Writer { private static final String LINE_SEPARATOR = SystemProperties.getInstance().getLineSeparator(); private enum Context { Outside, Text, CData, StartTag, ElementContent } private final Writer output; private final Deque elements = new ArrayDeque(); private Context context = Context.Outside; private int squareBrackets; private final String indent; protected SimpleMarkupWriter(Writer writer, String indent) throws IOException { this.indent = indent; this.output = writer; } @Override public void write(char[] chars, int offset, int length) throws IOException { characters(chars, offset, length); } @Override public void flush() throws IOException { output.flush(); } @Override public void close() throws IOException { // Does nothing } public SimpleMarkupWriter characters(char[] characters) throws IOException { characters(characters, 0, characters.length); return this; } public SimpleMarkupWriter characters(char[] characters, int start, int count) throws IOException { if (context == Context.CData) { writeCDATA(characters, start, count); } else { maybeStartText(); writeXmlEncoded(characters, start, count); } return this; } public SimpleMarkupWriter characters(CharSequence characters) throws IOException { if (context == Context.CData) { writeCDATA(characters); } else { maybeStartText(); writeXmlEncoded(characters); } return this; } private void maybeStartText() throws IOException { if (context == Context.Outside) { throw new IllegalStateException("Cannot write text, as there are no started elements."); } if (context == Context.StartTag) { writeRaw(">"); } context = Context.Text; } private void maybeFinishStartTag() throws IOException { if (context == Context.StartTag) { writeRaw(">"); context = Context.ElementContent; } } public SimpleMarkupWriter startElement(String name) throws IOException { if (!XmlValidation.isValidXmlName(name)) { throw new IllegalArgumentException(String.format("Invalid element name: '%s'", name)); } if (context == Context.CData) { throw new IllegalStateException("Cannot start element, as current CDATA node has not been closed."); } maybeFinishStartTag(); if (indent != null) { writeRaw(LINE_SEPARATOR); for (int i = 0; i < elements.size(); i++) { writeRaw(indent); } } context = Context.StartTag; elements.add(name); writeRaw("<"); writeRaw(name); return this; } public SimpleMarkupWriter endElement() throws IOException { if (context == Context.Outside) { throw new IllegalStateException("Cannot end element, as there are no started elements."); } if (context == Context.CData) { throw new IllegalStateException("Cannot end element, as current CDATA node has not been closed."); } if (context == Context.StartTag) { writeRaw("/>"); elements.removeLast(); } else { if (context != Context.Text && indent != null) { writeRaw(LINE_SEPARATOR); for (int i = 1; i < elements.size(); i++) { writeRaw(indent); } } writeRaw(""); } if (elements.isEmpty()) { if (indent != null) { writeRaw(LINE_SEPARATOR); } output.flush(); context = Context.Outside; } else { context = Context.ElementContent; } return this; } private void writeCDATA(char[] cdata, int offset, int count) throws IOException { int end = offset + count; for (int i = offset; i < end; i++) { writeCDATA(cdata[i]); } } private void writeCDATA(CharSequence cdata) throws IOException { int len = cdata.length(); for (int i = 0; i < len; i++) { writeCDATA(cdata.charAt(i)); } } private void writeCDATA(char ch) throws IOException { if (needsCDATAEscaping(ch)) { writeRaw("]]>"); } else if (!XmlValidation.isLegalCharacter(ch)) { writeRaw('?'); } else if (XmlValidation.isRestrictedCharacter(ch)) { writeRaw("]]>"); writeCharacterReference(ch); writeRaw("': if (squareBrackets >= 2) { squareBrackets = 0; return true; } return false; default: squareBrackets = 0; return false; } } public SimpleMarkupWriter startCDATA() throws IOException { if (context == Context.CData) { throw new IllegalStateException("Cannot start CDATA node, as current CDATA node has not been closed."); } maybeFinishStartTag(); writeRaw(""); context = Context.Text; return this; } public SimpleMarkupWriter comment(String comment) throws IOException { maybeFinishStartTag(); if (indent != null) { writeRaw(LINE_SEPARATOR); for (int i = 0; i < elements.size(); i++) { writeRaw(indent); } } writeRaw(""); return this; } public SimpleMarkupWriter attribute(String name, String value) throws IOException { if (!XmlValidation.isValidXmlName(name)) { throw new IllegalArgumentException(String.format("Invalid attribute name: '%s'", name)); } if (context != Context.StartTag) { throw new IllegalStateException("Cannot write attribute [" + name + ":" + value + "]. You should write start element first."); } writeRaw(" "); writeRaw(name); writeRaw("=\""); writeXmlAttributeEncoded(value); writeRaw("\""); return this; } private void writeRaw(char c) throws IOException { output.write(c); } protected void writeRaw(String message) throws IOException { output.write(message); } private void writeXmlEncoded(char[] message, int offset, int count) throws IOException { int end = offset + count; for (int i = offset; i < end; i++) { writeXmlEncoded(message[i]); } } private void writeXmlAttributeEncoded(CharSequence message) throws IOException { assert message != null; int len = message.length(); for (int i = 0; i < len; i++) { writeXmlAttributeEncoded(message.charAt(i)); } } private void writeXmlAttributeEncoded(char ch) throws IOException { if (ch == 9) { writeRaw(" "); } else if (ch == 10) { writeRaw(" "); } else if (ch == 13) { writeRaw(" "); } else { writeXmlEncoded(ch); } } private void writeXmlEncoded(CharSequence message) throws IOException { assert message != null; int len = message.length(); for (int i = 0; i < len; i++) { writeXmlEncoded(message.charAt(i)); } } private void writeXmlEncoded(char ch) throws IOException { if (ch == '<') { writeRaw("<"); } else if (ch == '>') { writeRaw(">"); } else if (ch == '&') { writeRaw("&"); } else if (ch == '"') { writeRaw("""); } else if (!XmlValidation.isLegalCharacter(ch)) { writeRaw('?'); } else if (XmlValidation.isRestrictedCharacter(ch)) { writeCharacterReference(ch); } else { writeRaw(ch); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy