mirror of
https://github.com/octoleo/plantuml.git
synced 2024-11-19 11:45:09 +00:00
Merge pull request #755 from arittner/aritner-loadJSON
Introduction of an internal function %loadJSON to load JSON data
This commit is contained in:
commit
dec2eae422
@ -106,6 +106,7 @@ import net.sourceforge.plantuml.tim.stdlib.InvokeProcedure;
|
|||||||
import net.sourceforge.plantuml.tim.stdlib.IsDark;
|
import net.sourceforge.plantuml.tim.stdlib.IsDark;
|
||||||
import net.sourceforge.plantuml.tim.stdlib.IsLight;
|
import net.sourceforge.plantuml.tim.stdlib.IsLight;
|
||||||
import net.sourceforge.plantuml.tim.stdlib.Lighten;
|
import net.sourceforge.plantuml.tim.stdlib.Lighten;
|
||||||
|
import net.sourceforge.plantuml.tim.stdlib.LoadJson;
|
||||||
import net.sourceforge.plantuml.tim.stdlib.LogicalNot;
|
import net.sourceforge.plantuml.tim.stdlib.LogicalNot;
|
||||||
import net.sourceforge.plantuml.tim.stdlib.Lower;
|
import net.sourceforge.plantuml.tim.stdlib.Lower;
|
||||||
import net.sourceforge.plantuml.tim.stdlib.Newline;
|
import net.sourceforge.plantuml.tim.stdlib.Newline;
|
||||||
@ -177,6 +178,7 @@ public class TContext {
|
|||||||
functionsSet.addFunction(new Hex2dec());
|
functionsSet.addFunction(new Hex2dec());
|
||||||
functionsSet.addFunction(new Dec2hex());
|
functionsSet.addFunction(new Dec2hex());
|
||||||
functionsSet.addFunction(new HslColor());
|
functionsSet.addFunction(new HslColor());
|
||||||
|
functionsSet.addFunction(new LoadJson());
|
||||||
functionsSet.addFunction(new Chr());
|
functionsSet.addFunction(new Chr());
|
||||||
functionsSet.addFunction(new Size());
|
functionsSet.addFunction(new Size());
|
||||||
// %standard_exists_function
|
// %standard_exists_function
|
||||||
|
186
src/net/sourceforge/plantuml/tim/stdlib/LoadJson.java
Normal file
186
src/net/sourceforge/plantuml/tim/stdlib/LoadJson.java
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
* PlantUML : a free UML diagram generator
|
||||||
|
* ========================================================================
|
||||||
|
*
|
||||||
|
* (C) Copyright 2009-2021, Arnaud Roques
|
||||||
|
*
|
||||||
|
* Project Info: http://plantuml.com
|
||||||
|
*
|
||||||
|
* If you like this project or if you find it useful, you can support us at:
|
||||||
|
*
|
||||||
|
* http://plantuml.com/patreon (only 1$ per month!)
|
||||||
|
* http://plantuml.com/paypal
|
||||||
|
*
|
||||||
|
* This file is part of PlantUML.
|
||||||
|
*
|
||||||
|
* PlantUML is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PlantUML distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||||
|
* USA.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Original Author: Arnaud Roques
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package net.sourceforge.plantuml.tim.stdlib;
|
||||||
|
|
||||||
|
import net.sourceforge.plantuml.FileSystem;
|
||||||
|
import net.sourceforge.plantuml.FileUtils;
|
||||||
|
import net.sourceforge.plantuml.LineLocation;
|
||||||
|
import net.sourceforge.plantuml.json.Json;
|
||||||
|
import net.sourceforge.plantuml.json.JsonValue;
|
||||||
|
import net.sourceforge.plantuml.json.ParseException;
|
||||||
|
import net.sourceforge.plantuml.security.SFile;
|
||||||
|
import net.sourceforge.plantuml.security.SURL;
|
||||||
|
import net.sourceforge.plantuml.tim.*;
|
||||||
|
import net.sourceforge.plantuml.tim.expression.TValue;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads JSON data from file or URL source.
|
||||||
|
* <p>
|
||||||
|
* Supports three parameters for datasource, default JSON value and charset. The datasource will be checked against
|
||||||
|
* the security rules.
|
||||||
|
* <p>
|
||||||
|
* Examples:</br>
|
||||||
|
* <pre>
|
||||||
|
* @ startuml
|
||||||
|
* ' loads a local file
|
||||||
|
* !$JSON_LOCAL_RELATIVE=%loadJSON("file.json")
|
||||||
|
*
|
||||||
|
* ' loads a local file from an absolute file path
|
||||||
|
* !$JSON_LOCAL_ABS=%loadJSON("c:/loaded/data/file.json")
|
||||||
|
*
|
||||||
|
* ' tries to load a local file and returns an empty JSON
|
||||||
|
* !$JSON_LOCAL_REL_EMPTY=%loadJSON("file-not-existing.json")
|
||||||
|
*
|
||||||
|
* ' tries to load a local file and returns an default JSON
|
||||||
|
* !$DEF_JSON={"status":"No data found"}
|
||||||
|
* !$JSON_LOCAL_REL_DEF=%loadJSON("file-not-existing.json", $DEF_JSON)
|
||||||
|
*
|
||||||
|
* ' loads a local file with a specific charset (default is UTF-8)
|
||||||
|
* !$JSON_LOCAL_RELATIVE_CHARSET=%loadJSON("file.json", "{}", "iso-8859-1")
|
||||||
|
*
|
||||||
|
* ' loads a remote JSON from an endpoint (and default, if not reachable)
|
||||||
|
* !$STATUS_NO_CONNECTION={"status": "No connection"}
|
||||||
|
* !$JSON_REMOTE_DEF=%loadJSON("https://localhost:7778/management/health", $STATUS_NO_CONNECTION)
|
||||||
|
* status -> $JSON_REMOTE_DEF.status
|
||||||
|
* @ enduml
|
||||||
|
* </pre>
|
||||||
|
* @author Aljoscha Rittner
|
||||||
|
*/
|
||||||
|
public class LoadJson extends SimpleReturnFunction {
|
||||||
|
|
||||||
|
private static final String VALUE_CHARSET_DEFAULT = "UTF-8";
|
||||||
|
|
||||||
|
private static final String VALUE_DEFAULT_DEFAULT = "{}";
|
||||||
|
|
||||||
|
public TFunctionSignature getSignature() {
|
||||||
|
return new TFunctionSignature("%loadJSON", 3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canCover(int nbArg, Set<String> namedArgument) {
|
||||||
|
return nbArg == 1 || nbArg == 2 || nbArg == 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue executeReturnFunction(TContext context, TMemory memory, LineLocation location, List<TValue> values,
|
||||||
|
Map<String, TValue> named) throws EaterException, EaterExceptionLocated {
|
||||||
|
String path = values.get(0).toString();
|
||||||
|
try {
|
||||||
|
String data = loadStringData ( path, getCharset (values) );
|
||||||
|
if ( data == null ) {
|
||||||
|
data = getDefaultJson(values);
|
||||||
|
}
|
||||||
|
JsonValue jsonValue = Json.parse(data);
|
||||||
|
return TValue.fromJson(jsonValue);
|
||||||
|
} catch (ParseException pe) {
|
||||||
|
pe.printStackTrace ();
|
||||||
|
throw EaterException.unlocated ( "JSON parse issue in source "
|
||||||
|
+ path + " on location " + pe.getLocation () );
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace ();
|
||||||
|
throw EaterException.unlocated ( "JSON encoding issue in source "
|
||||||
|
+ path + ": " + e.getMessage () );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JSON default, if the data source contains no data.
|
||||||
|
* @param values value parameters
|
||||||
|
* @return the defined default JSON or {@code "{}"}
|
||||||
|
*/
|
||||||
|
private String getDefaultJson(List<TValue> values) {
|
||||||
|
if (values.size() > 1 ) {
|
||||||
|
return values.get(1).toString ();
|
||||||
|
}
|
||||||
|
return VALUE_DEFAULT_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the charset name (if set)
|
||||||
|
* @param values value parameters
|
||||||
|
* @return defined charset or {@code "UTF-8"}
|
||||||
|
*/
|
||||||
|
private String getCharset(List<TValue> values) {
|
||||||
|
if ( values.size() == 3) {
|
||||||
|
return values.get (2).toString ();
|
||||||
|
}
|
||||||
|
return VALUE_CHARSET_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads String data from a data source {@code path} (file or URL) and expects the data encoded in {@code charset}.
|
||||||
|
* @param path path to data source (http(s)-URL or file).
|
||||||
|
* @param charset character set to encode the string data
|
||||||
|
* @return the decoded String from the data source
|
||||||
|
* @throws EaterException if something went wrong on reading data
|
||||||
|
*/
|
||||||
|
private String loadStringData(String path, String charset) throws EaterException, UnsupportedEncodingException {
|
||||||
|
|
||||||
|
byte[] byteData = null;
|
||||||
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||||
|
final SURL url = SURL.create(path);
|
||||||
|
if (url == null) {
|
||||||
|
throw EaterException.located("load JSON: Invalid URL " + path);
|
||||||
|
}
|
||||||
|
byteData = url.getBytes();
|
||||||
|
if (byteData != null && byteData.length == 0) {
|
||||||
|
// no length, no data (we want the default)
|
||||||
|
byteData = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
SFile file = FileSystem.getInstance().getFile(path);
|
||||||
|
if (file != null && file.exists() && file.canRead() && !file.isDirectory()) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(1024 * 8);
|
||||||
|
FileUtils.copyToStream(file, out);
|
||||||
|
byteData = out.toByteArray();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw EaterException.located("load JSON: Cannot read file "
|
||||||
|
+ path + ". " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (byteData != null) {
|
||||||
|
return new String(byteData, charset);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
140
test/net/sourceforge/plantuml/LoadJsonTest.java
Normal file
140
test/net/sourceforge/plantuml/LoadJsonTest.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package net.sourceforge.plantuml;
|
||||||
|
|
||||||
|
import net.sourceforge.plantuml.security.SFile;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static net.sourceforge.plantuml.test.TestUtils.writeUtf8File;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.util.Lists.newArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the load of a JSON file.
|
||||||
|
* <p>
|
||||||
|
* Current limitation: Only local file tests, not tests to a http rest endpoint
|
||||||
|
*/
|
||||||
|
class LoadJsonTest {
|
||||||
|
|
||||||
|
private static final String[] COMMON_OPTIONS = {"-tutxt"};
|
||||||
|
|
||||||
|
private static final String JSON = "{\"jsonTestKey\": \"exampleValue\"}";
|
||||||
|
|
||||||
|
private static final String DEF_JSON = "{\"jsonTestKey\": \"exampleDefaultValue\"}";
|
||||||
|
|
||||||
|
// ************ Test DSL data
|
||||||
|
private static final String DIAGRAM = "" +
|
||||||
|
"@startuml\n" +
|
||||||
|
"!$JSON_DATA=%loadJSON(test.json)\n" +
|
||||||
|
|
||||||
|
// title should have the value from the JSON file
|
||||||
|
"title $JSON_DATA.jsonTestKey\n" +
|
||||||
|
"a -> b\n" +
|
||||||
|
"@enduml\n";
|
||||||
|
|
||||||
|
private static final String DIAGRAM_DEF = "" +
|
||||||
|
"@startuml\n" +
|
||||||
|
"!$DEF_JSON=" + DEF_JSON + "\n" +
|
||||||
|
"!$JSON_DATA=%loadJSON(\"test-notfound.json\", $DEF_JSON)\n" +
|
||||||
|
|
||||||
|
// title should have the value from the default (because the file doesn't exist)
|
||||||
|
"title $JSON_DATA.jsonTestKey\n" +
|
||||||
|
"a -> b\n" +
|
||||||
|
"@enduml\n";
|
||||||
|
|
||||||
|
private static final String DIAGRAM_DEF_EMPTY = "" +
|
||||||
|
"@startuml\n" +
|
||||||
|
"!$JSON_DATA=%loadJSON(\"test-notfound.json\")\n" +
|
||||||
|
// JSON_DATA is defined, but empty (loadJSON default). So, title contains only "xx yy".
|
||||||
|
"title xx $JSON_DATA.jsonTestKey yy\n" +
|
||||||
|
"a -> b\n" +
|
||||||
|
"@enduml\n";
|
||||||
|
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the current directory.
|
||||||
|
*/
|
||||||
|
@AfterAll
|
||||||
|
static void cleanUp() {
|
||||||
|
FileSystem.getInstance().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares test JSON file and sets the current directory for each test.
|
||||||
|
*
|
||||||
|
* @throws Exception hopefully not
|
||||||
|
*/
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeEach() throws Exception {
|
||||||
|
writeUtf8File(tempDir.resolve("test.json"), JSON);
|
||||||
|
FileSystem.getInstance().setCurrentDir(new SFile(tempDir.toFile().getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests, if the loadJSON is loading the JSON file from test tmp folder.
|
||||||
|
*
|
||||||
|
* @throws Exception if something went wrong in this test
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testLoadJsonSimple() throws Exception {
|
||||||
|
String rendered = render(DIAGRAM);
|
||||||
|
assertThat(rendered).doesNotContain("syntax").contains("exampleValue");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests, if the loadJSON is using the default JSON given as parameter.
|
||||||
|
*
|
||||||
|
* @throws Exception if something went wrong in this test
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testLoadJsonNotFoundWithDefaultParameter() throws Exception {
|
||||||
|
String rendered = render(DIAGRAM_DEF);
|
||||||
|
assertThat(rendered).doesNotContain("syntax").contains("exampleDefaultValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests, if the loadJSON is using the default JSON.
|
||||||
|
*
|
||||||
|
* @throws Exception if something went wrong in this test
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testLoadJsonNotFoundWithDefaultEmpty() throws Exception {
|
||||||
|
String rendered = render(DIAGRAM_DEF_EMPTY);
|
||||||
|
assertThat(rendered).doesNotContain("syntax").contains("xx yy");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] optionArray(String... extraOptions) {
|
||||||
|
final List<String> list = newArrayList(COMMON_OPTIONS);
|
||||||
|
Collections.addAll(list, extraOptions);
|
||||||
|
return list.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String render(String diagram, String... extraOptions) throws Exception {
|
||||||
|
|
||||||
|
final Option option = new Option(optionArray(extraOptions));
|
||||||
|
|
||||||
|
final ByteArrayInputStream bais = new ByteArrayInputStream(diagram.getBytes(UTF_8));
|
||||||
|
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
final Pipe pipe = new Pipe(option, new PrintStream(baos), bais, option.getCharset());
|
||||||
|
|
||||||
|
pipe.managePipe(ErrorStatus.init());
|
||||||
|
|
||||||
|
return new String(baos.toByteArray(), UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user