blob: eb044eba38ac534d5a206ef5470964ea628b42bb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.commons.scxml2.io;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
* The ContentParser provides utility methods for cleaning content strings and parsing them into "raw" content model Objects
*/
public class ContentParser {
public static final ContentParser DEFAULT_PARSER = new ContentParser();
/**
* Jackson JSON ObjectMapper
*/
private ObjectMapper jsonObjectMapper;
/**
* Default constructor initializing a Jackson ObjectMapper allowing embedded comments, including YAML style
*/
public ContentParser() {
this.jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
jsonObjectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
}
/**
* Constructor with a custom configured Jackson ObjectMapper
* @param jsonObjectMapper custom configured Jackson ObjectMapper
*/
public ContentParser(final ObjectMapper jsonObjectMapper) {
this.jsonObjectMapper = jsonObjectMapper;
}
/**
* Trim pre/post-fixed whitespace from content string
* @param content content to trim
* @return trimmed content
*/
public static String trimContent(final String content) {
if (content != null) {
int start = 0;
int length = content.length();
while (start < length && isWhiteSpace(content.charAt(start))) {
start++;
}
while (length > start && isWhiteSpace(content.charAt(length - 1))) {
length--;
}
if (start == length) {
return "";
}
return content.substring(start, length);
}
return null;
}
/**
* Space normalize content string, trimming pre/post-fixed whitespace and collapsing embedded whitespaces to
* single space.
* @param content content to space-normalize
* @return space-normalized content
*/
public static String spaceNormalizeContent(final String content) {
if (content != null) {
int index = 0;
int length = content.length();
StringBuilder buffer = new StringBuilder(length);
boolean whiteSpace = false;
while (index < length) {
if (isWhiteSpace(content.charAt(index))) {
if (!whiteSpace && buffer.length() > 0) {
buffer.append(' ');
whiteSpace = true;
}
}
else {
buffer.append(content.charAt(index));
whiteSpace = false;
}
index++;
}
if (whiteSpace) {
buffer.setLength(buffer.length()-1);
}
return buffer.toString();
}
return null;
}
/**
* Check if a character is whitespace (space, tab, newline, cr) or not
* @param c character to check
* @return true if character is whitespace
*/
public static boolean isWhiteSpace(final char c) {
return c==' ' || c=='\n' || c=='\t' || c=='\r';
}
/**
* Check if content starts with JSON object '{' or array '[' marker
* @param content text to check
* @return true if content start with '{' or '[' character
*/
public static boolean hasJsonSignature(final String content) {
final char c = content.length() > 0 ? content.charAt(0) : 0;
return c == '{' || c == '[';
}
/**
* Check if content indicates its an XML document
* @param content content to check
* @return true if content indicates its an XML document
*/
public static boolean hasXmlSignature(final String content) {
return content != null && content.startsWith("<?xml ");
}
/**
* Parse and map JSON string to 'raw' Java Objects: object -> LinkedHashMap, array -> ArrayList
* @param jsonString JSON string to parse
* @return 'raw' mapped Java Object for JSON string
* @throws IOException In case of parsing exceptions
*/
public Object parseJson(final String jsonString) throws IOException {
return jsonObjectMapper.readValue(jsonString, Object.class);
}
/**
* Parse an XML String and return the document element
* @param xmlString XML String to parse
* @return document element
* @throws IOException
*/
public Node parseXml(final String xmlString) throws IOException {
Document doc = null;
try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlString);
} catch (SAXException e) {
throw new IOException(e);
} catch (ParserConfigurationException e) {
throw new IOException(e);
}
return doc != null ? doc.getDocumentElement() : null;
}
/**
* Parse a string into a content object, following the SCXML rules as specified for the ECMAscript (section B.2.1) Data Model
* <ul>
* <li>if the content can be interpreted as JSON, it will be parsed as JSON into an 'raw' object model</li>
* <li>if the content can be interpreted as XML, it will be parsed into a XML DOM element</li>
* <li>otherwise the content will be treated (cleaned) as a space-normalized string literal</li>
* </ul>
* @param content the content to parse
* @return the parsed content object
* @throws IOException In case of parsing exceptions
*/
public Object parseContent(final String content) throws IOException {
if (content != null) {
String src = trimContent(content);
if (hasJsonSignature(src)) {
return parseJson(src);
}
else if (hasXmlSignature(src)) {
return parseXml(src);
}
return spaceNormalizeContent(src);
}
return null;
}
/**
* Load a resource (URL) as an UTF-8 encoded content string to be parsed into a content object through {@link #parseContent(String)}
* @param resourceURL Resource URL to load content from
* @return the parsed content object
* @throws IOException In case of loading or parsing exceptions
*/
public Object parseResource(final String resourceURL) throws IOException {
InputStream in = null;
try {
in = new URL(resourceURL).openStream();
String content = IOUtils.toString(in, "UTF-8");
return parseContent(content);
}
finally {
IOUtils.closeQuietly(in);
}
}
}